"use client";

import React, { useState, useEffect, useMemo, memo } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTabStore } from "@/stores/tabStore";
import { BatchAPI } from "@/lib/api/batch";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Trash2, Plus, ArrowLeft, Save, RefreshCw, Globe, Database, Eye, Link as LinkIcon } from "lucide-react";
import { toast } from "sonner";
import { showErrorToast } from "@/lib/utils/toastUtils";
import { cn } from "@/lib/utils";
import { BatchManagementAPI } from "@/lib/api/batchManagement";
import {
  ExternalRestApiConnectionAPI,
  ExternalRestApiConnection,
} from "@/lib/api/externalRestApiConnection";

// 타입 정의
type BatchType = "db-to-restapi" | "restapi-to-db" | "restapi-to-restapi";

interface BatchTypeOption {
  value: BatchType;
  label: string;
  description: string;
}

interface BatchConnectionInfo {
  id: number;
  name: string;
  type: string;
}

interface BatchColumnInfo {
  column_name: string;
  data_type: string;
  is_nullable: string;
}

// 통합 매핑 아이템 타입
interface ConditionalRule {
  when: string;
  then: string;
}
interface ConditionalConfig {
  rules: ConditionalRule[];
  default: string;
}
interface MappingItem {
  id: string;
  dbColumn: string;
  sourceType: "api" | "fixed" | "conditional";
  apiField: string;
  fixedValue: string;
  // 조건부 변환: source 필드값에 따라 다른 값을 저장
  //   apiField 가 평가할 API 필드명 (예: dept_name)
  //   conditionalConfig.rules: [{ when: '영업부', then: '1' }, ...]
  //   conditionalConfig.default: 매칭 안되면 사용할 기본값
  conditionalConfig?: ConditionalConfig;
}

interface RestApiToDbMappingCardProps {
  fromApiFields: string[];
  toColumns: BatchColumnInfo[];
  fromApiData: any[];
  mappingList: MappingItem[];
  setMappingList: React.Dispatch<React.SetStateAction<MappingItem[]>>;
}

interface DbToRestApiMappingCardProps {
  fromColumns: BatchColumnInfo[];
  selectedColumns: string[];
  toApiFields: string[];
  dbToApiFieldMapping: Record<string, string>;
  setDbToApiFieldMapping: React.Dispatch<React.SetStateAction<Record<string, string>>>;
  setToApiBody: (body: string) => void;
}

interface BatchManagementNewPageProps {
  adminParams?: { id?: string; edit?: string };
}

export default function BatchManagementNewPage(props: BatchManagementNewPageProps = {}) {
  const router = useRouter();
  const { openTab } = useTabStore();
  // adminUrl 파싱: /admin/batch-management-new?edit=12 형태로 들어오면 query 추출
  const searchParams = useSearchParams();
  const editIdRaw =
    props.adminParams?.edit ??
    props.adminParams?.id ??
    searchParams?.get("edit") ??
    null;
  const editId = editIdRaw && !isNaN(Number(editIdRaw)) ? Number(editIdRaw) : null;
  const isEditMode = editId !== null;
  const [editLoading, setEditLoading] = useState(false);

  // 기본 상태
  const [batchName, setBatchName] = useState("");
  const [cronSchedule, setCronSchedule] = useState("0 12 * * *");
  const [description, setDescription] = useState("");

  // 인증 토큰 설정
  const [authTokenMode, setAuthTokenMode] = useState<"direct" | "db">("direct"); // 직접입력 / DB에서 선택
  const [authServiceName, setAuthServiceName] = useState("");
  const [authServiceNames, setAuthServiceNames] = useState<string[]>([]);

  // 연결 정보
  const [connections, setConnections] = useState<BatchConnectionInfo[]>([]);
  const [toConnection, setToConnection] = useState<BatchConnectionInfo | null>(null);
  const [toTables, setToTables] = useState<string[]>([]);
  const [toTable, setToTable] = useState("");
  const [toColumns, setToColumns] = useState<BatchColumnInfo[]>([]);

  // REST API 설정 (REST API → DB용)
  const [fromApiUrl, setFromApiUrl] = useState("");
  const [fromApiKey, setFromApiKey] = useState("");
  const [fromEndpoint, setFromEndpoint] = useState("");
  const [fromApiMethod, setFromApiMethod] = useState<"GET" | "POST" | "PUT" | "DELETE">("GET");
  const [fromApiBody, setFromApiBody] = useState(""); // Request Body (JSON)
  const [dataArrayPath, setDataArrayPath] = useState(""); // 데이터 배열 경로 (예: response, data.items)

  // 등록된 REST API 연결 (외부 커넥션 관리에서 등록된 연결 선택)
  const [registeredRestApis, setRegisteredRestApis] = useState<ExternalRestApiConnection[]>([]);
  const [selectedRestApiId, setSelectedRestApiId] = useState<string>("manual"); // "manual" 또는 연결 id 문자열

  // REST API 파라미터 설정
  const [apiParamType, setApiParamType] = useState<"none" | "url" | "query">("none");
  const [apiParamName, setApiParamName] = useState(""); // 파라미터명 (예: userId, id)
  const [apiParamValue, setApiParamValue] = useState(""); // 파라미터 값 또는 템플릿
  const [apiParamSource, setApiParamSource] = useState<"static" | "dynamic">("static"); // 정적 값 또는 동적 값

  // DB → REST API용 상태
  const [fromConnection, setFromConnection] = useState<BatchConnectionInfo | null>(null);
  const [fromTables, setFromTables] = useState<string[]>([]);
  const [fromTable, setFromTable] = useState("");
  const [fromColumns, setFromColumns] = useState<BatchColumnInfo[]>([]);
  const [selectedColumns, setSelectedColumns] = useState<string[]>([]); // 선택된 컬럼들
  const [dbToApiFieldMapping, setDbToApiFieldMapping] = useState<Record<string, string>>({}); // DB 컬럼 → API 필드 매핑

  // REST API 대상 설정 (DB → REST API용)
  const [toApiUrl, setToApiUrl] = useState("");
  const [toApiKey, setToApiKey] = useState("");
  const [toEndpoint, setToEndpoint] = useState("");
  const [toApiMethod, setToApiMethod] = useState<"POST" | "PUT" | "DELETE">("POST");
  const [toApiBody, setToApiBody] = useState<string>(""); // Request Body 템플릿
  const [toApiFields, setToApiFields] = useState<string[]>([]); // TO API 필드 목록
  const [urlPathColumn, setUrlPathColumn] = useState(""); // URL 경로에 사용할 컬럼 (PUT/DELETE용)

  // API 데이터 미리보기
  const [fromApiData, setFromApiData] = useState<any[]>([]);
  const [fromApiFields, setFromApiFields] = useState<string[]>([]);

  // 응답 원본 + 빠른 테스트 (DB 테이블 선택 전에 응답 형태 확인용)
  const [rawResponse, setRawResponse] = useState<any>(null);
  const [rawResponseLoading, setRawResponseLoading] = useState(false);
  const [rawResponseError, setRawResponseError] = useState<string>("");

  // 통합 매핑 리스트
  const [mappingList, setMappingList] = useState<MappingItem[]>([]);

  // INSERT/UPSERT 설정
  const [saveMode, setSaveMode] = useState<"INSERT" | "UPSERT">("INSERT");
  const [conflictKey, setConflictKey] = useState("");

  // 행 단위 제외 필터 — 특정 API 필드 값에 해당하는 행을 동기화에서 제외
  //   예) loginId="wace" 인 통합 ERP 계정 제외
  const [rowFilterRules, setRowFilterRules] = useState<Array<{ column: string; op: "eq" | "neq"; value: string }>>([]);

  // 배치 타입 상태
  const [batchType, setBatchType] = useState<BatchType>("restapi-to-db");

  // 배치 타입 옵션
  const batchTypeOptions: BatchTypeOption[] = [
    {
      value: "restapi-to-db",
      label: "REST API → DB",
      description: "REST API에서 데이터베이스로 데이터 수집",
    },
    {
      value: "db-to-restapi",
      label: "DB → REST API",
      description: "데이터베이스에서 REST API로 데이터 전송",
    },
  ];

  // 초기 데이터 로드
  useEffect(() => {
    loadConnections();
    loadAuthServiceNames();
    loadRegisteredRestApis();
  }, []);

  // 등록된 REST API 연결 목록 로드 (외부 커넥션 관리)
  const loadRegisteredRestApis = async () => {
    try {
      const list = await ExternalRestApiConnectionAPI.getConnections({ is_active: "Y" });
      setRegisteredRestApis(list);
    } catch (error) {
      console.error("등록된 REST API 연결 목록 로드 실패:", error);
    }
  };

  // FROM API 응답 빠른 테스트 (DB 테이블 선택 전에 응답 구조 확인용)
  //  - 등록된 연결을 사용하면 testConnectionById, 직접 입력이면 testConnection 호출
  //  - 응답 본문을 그대로 보여줘서 어떤 필드가 있는지 확인 → DB 매핑 시 사용
  const runQuickResponseTest = async () => {
    setRawResponseError("");
    if (!fromApiUrl?.trim() || !fromEndpoint?.trim()) {
      setRawResponseError("API 서버 URL과 엔드포인트를 입력하거나 등록된 연결을 선택해주세요.");
      return;
    }
    setRawResponseLoading(true);
    setRawResponse(null);
    try {
      let result: any;
      if (selectedRestApiId !== "manual") {
        // 등록된 연결: 백엔드가 인증(Wehago HMAC 등)을 처리
        result = await ExternalRestApiConnectionAPI.testConnectionById(
          Number(selectedRestApiId),
          fromEndpoint,
        );
      } else {
        // 직접 입력 모드
        let parsedBody: any = undefined;
        if (
          (fromApiMethod === "POST" || fromApiMethod === "PUT" || fromApiMethod === "DELETE") &&
          fromApiBody
        ) {
          try {
            parsedBody = JSON.parse(fromApiBody);
          } catch {
            parsedBody = fromApiBody;
          }
        }
        result = await ExternalRestApiConnectionAPI.testConnection({
          base_url: fromApiUrl,
          endpoint: fromEndpoint,
          method: fromApiMethod as any,
          headers: { "Content-Type": "application/json" },
          body: parsedBody,
          auth_type: fromApiKey ? "bearer" : "none",
          auth_config: fromApiKey
            ? { token: fromApiKey.replace(/^Bearer\s+/i, "") }
            : undefined,
          timeout: 30000,
        });
      }

      if (result.success) {
        setRawResponse(result.response_data);

        // 응답에서 배열 자동 탐색해서 fromApiFields/fromApiData 도 채움 (매핑 카드 즉시 표시)
        const findArr = (o: any, depth = 0): any[] | null => {
          if (Array.isArray(o)) return o;
          if (depth >= 4 || typeof o !== "object" || o === null) return null;
          for (const v of Object.values(o)) {
            const a = findArr(v, depth + 1);
            if (a) return a;
          }
          return null;
        };
        const arr = findArr(result.response_data);
        if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
          const fields = Object.keys(arr[0]);
          setFromApiFields(fields);
          setFromApiData(arr);
          toast.success(
            `응답 수신 (${result.response_time ?? "-"}ms) — 배열 ${arr.length}건 / 필드 ${fields.length}개 추출. 아래에서 DB 컬럼과 매핑하세요.`
          );
        } else if (
          result.response_data &&
          typeof result.response_data === "object" &&
          !Array.isArray(result.response_data)
        ) {
          // 단일 객체 응답
          const fields = Object.keys(result.response_data as any);
          setFromApiFields(fields);
          setFromApiData([result.response_data]);
          toast.success(`응답 수신 (${result.response_time ?? "-"}ms) — 필드 ${fields.length}개 추출`);
        } else {
          toast.success(`응답 수신 (${result.response_time ?? "-"}ms)`);
        }
      } else {
        setRawResponseError(result.message || result.error_details || "API 호출 실패");
      }
    } catch (e: any) {
      setRawResponseError(e?.message || "API 호출 중 오류");
    } finally {
      setRawResponseLoading(false);
    }
  };

  // Amaranth (Wehago/RPS ERP) 연결 → 권장 적재 대상 테이블 매핑 (편집 모드 외 신규 생성 시 자동 셋팅)
  //   사용자 선택 즉시 TO 패널의 DB 커넥션(내부 DB) + 테이블 + 컬럼 + UPSERT/충돌키 + 매핑까지 자동 채운다
  const AMARANTH_TARGET_PRESETS: Record<
    string,
    {
      table: string;
      saveMode: "INSERT" | "UPSERT";
      conflictKey: string;
      mappings: Array<{ from: string; to: string }>;
      dataArrayPath: string;
    }
  > = {
    "Amaranth - 부서": {
      table: "dept_info",
      saveMode: "UPSERT",
      conflictKey: "dept_code",
      dataArrayPath: "data",
      mappings: [
        { from: "deptCd", to: "dept_code" },
        { from: "deptNm", to: "dept_name" },
        { from: "upDeptCd", to: "parent_dept_code" },
        { from: "deptShortNm", to: "dept_short_name" },
        { from: "deptEngNm", to: "dept_eng_name" },
        { from: "deptLvl", to: "dept_level" },
        { from: "sortSq", to: "sort_seq" },
        { from: "useYn", to: "use_yn" },
      ],
    },
    "Amaranth - 사원": {
      table: "user_info",
      saveMode: "UPSERT",
      conflictKey: "sabun",
      dataArrayPath: "data",
      mappings: [
        { from: "sabun", to: "sabun" },
        { from: "empSeq", to: "user_id" }, // user_info.user_id NOT NULL 충족 — loginId 배치가 나중에 덮어씀
        { from: "empSeq", to: "emp_seq" }, // loginId 배치의 매칭 키
        { from: "empNm", to: "user_name" },
        { from: "empNmEn", to: "user_name_eng" },
        { from: "deptCd", to: "dept_code" },
        { from: "deptNm", to: "dept_name" },
        { from: "posCd", to: "position_code" },
        { from: "posNm", to: "position_name" },
        { from: "rspofcCd", to: "rank_code" },
        { from: "rspofcNm", to: "rank_name" },
        { from: "emalAdd", to: "email" },
        { from: "outemalAdd", to: "out_email" },
        { from: "tel", to: "tel" },
        { from: "emgcTel", to: "cell_phone" },
        { from: "joinDt", to: "join_date" },
        { from: "retireDt", to: "retire_date" },
        { from: "wrkSttsCd", to: "work_status" },
        { from: "sexFg", to: "gender_fg" },
      ],
    },
    "Amaranth - Wehago 사용자": {
      table: "user_info",
      saveMode: "UPSERT",
      conflictKey: "emp_seq",
      dataArrayPath: "body.data",
      mappings: [
        { from: "empSeq", to: "emp_seq" },
        { from: "loginId", to: "user_id" },
      ],
    },
    "Amaranth - 거래처": {
      table: "customer_mng",
      saveMode: "UPSERT",
      conflictKey: "customer_code",
      dataArrayPath: "data",
      mappings: [
        { from: "trCd", to: "customer_code" },
        { from: "trNm", to: "customer_name" },
        { from: "trShortNm", to: "short_name" },
        { from: "bizNo", to: "business_number" },
        { from: "corpNo", to: "corp_number" },
        { from: "repNm", to: "ceo_name" },
        { from: "bizCondtn", to: "biz_condition" },
        { from: "bizItem", to: "biz_item" },
        { from: "zipCd", to: "zip_code" },
        { from: "addr", to: "address" },
        { from: "addrDtl", to: "address_detail" },
        { from: "tel", to: "tel" },
        { from: "faxNo", to: "fax_no" },
        { from: "hpNo", to: "hp_no" },
        { from: "emalAdd", to: "email" },
        { from: "chargeNm", to: "charge_name" },
        { from: "chargeTel", to: "charge_tel" },
        { from: "chargeEmal", to: "charge_email" },
        { from: "trFg", to: "customer_type" },
        { from: "bankNm", to: "bank_name" },
        { from: "bankAcct", to: "bank_account" },
        { from: "acctOwner", to: "account_owner" },
        { from: "natnCd", to: "nation_code" },
        { from: "currCd", to: "currency_code" },
        { from: "useYn", to: "use_yn" },
        { from: "regDt", to: "reg_date" },
      ],
    },
    "Amaranth - 창고": {
      table: "warehouse_info",
      saveMode: "UPSERT",
      conflictKey: "warehouse_code",
      dataArrayPath: "data",
      mappings: [
        { from: "lctnCd", to: "warehouse_code" },
        { from: "lctnNm", to: "warehouse_name" },
        { from: "lctnFg", to: "warehouse_type" },
        { from: "lctnFgNm", to: "warehouse_type_name" },
        { from: "upLctnCd", to: "parent_loc_code" },
        { from: "baselocFg", to: "baseloc_fg" },
        { from: "sortSq", to: "sort_seq" },
        { from: "useYn", to: "use_yn" },
      ],
    },
    "Amaranth - 계정과목": {
      table: "account_code_info",
      saveMode: "UPSERT",
      conflictKey: "account_code",
      dataArrayPath: "data",
      mappings: [
        { from: "acctCd", to: "account_code" },
        { from: "acctNm", to: "account_name" },
        { from: "acctTy", to: "account_type" },
        { from: "acctNmk", to: "account_short" },
        { from: "groupSeq", to: "group_seq" },
        { from: "groupCd", to: "group_code" },
        { from: "groupNm", to: "group_name" },
        { from: "drcrFg", to: "dr_cr_fg" },
        { from: "subDisp", to: "sub_disp" },
        { from: "subDispNm", to: "sub_disp_name" },
        { from: "chFg", to: "ch_fg" },
        { from: "chFgNm", to: "ch_fg_name" },
        { from: "budFg", to: "bud_fg" },
        { from: "budFgNm", to: "bud_fg_name" },
        { from: "attrFg", to: "attr_fg" },
        { from: "attrFgNm", to: "attr_fg_name" },
        { from: "racctCd", to: "racct_code" },
        { from: "racctNm", to: "racct_name" },
        { from: "fillYn", to: "fill_yn" },
        { from: "ctrlCds", to: "ctrl_cds" },
        { from: "extInputCd", to: "ext_input_cd" },
        { from: "useYn", to: "use_yn" },
      ],
    },
  };

  // 등록된 연결 선택 시 폼 자동 채우기 + API 호출 + 응답 필드 추출 (자동 매핑 준비)
  const applyRegisteredRestApi = async (id: string) => {
    setSelectedRestApiId(id);
    if (id === "manual") return;
    const conn = registeredRestApis.find((c) => String(c.id) === id);
    if (!conn) return;

    setFromApiUrl(conn.base_url || "");
    setFromEndpoint(conn.endpoint_path || "");
    setFromApiMethod((conn.default_method as any) || "GET");
    setFromApiBody(conn.default_body || "");

    // ── Amaranth 프리셋이면 TO DB 자동 셋팅 (편집 모드는 이미 자체 로직이 있으므로 스킵) ──
    const preset = AMARANTH_TARGET_PRESETS[conn.connection_name];
    if (preset && !isEditMode) {
      setDataArrayPath(preset.dataArrayPath);
      setSaveMode(preset.saveMode);
      setConflictKey(preset.conflictKey);
      // 내부 DB 커넥션 자동 선택 + 테이블/컬럼 로드
      const internalConn = connections.find((c) => c.type === "internal");
      if (internalConn) {
        setToConnection(internalConn);
        try {
          const tablesResult = await BatchManagementAPI.getTablesFromConnection(
            "internal",
            internalConn.id,
          );
          const tableNames = Array.isArray(tablesResult)
            ? tablesResult.map((t: any) => (typeof t === "string" ? t : t.table_name || String(t)))
            : [];
          setToTables(tableNames);
          // 대상 테이블 자동 선택 + 컬럼 로드
          if (tableNames.includes(preset.table)) {
            setToTable(preset.table);
            try {
              const cols = await BatchManagementAPI.getTableColumns(
                "internal",
                preset.table,
                internalConn.id,
              );
              if (cols && cols.length > 0) setToColumns(cols);
            } catch (e) {
              console.warn("[preset] 테이블 컬럼 로드 실패", e);
            }
          } else {
            // 테이블이 없으면 백엔드에 자동 생성 요청 (관련 테이블이 마이그레이션돼 있어야 정상)
            toast.warning(
              `대상 테이블 '${preset.table}' 이 내부 DB에 없어요. 200_erp_sync_tables 마이그레이션을 실행했는지 확인해주세요.`,
            );
          }
        } catch (e) {
          console.warn("[preset] 내부 DB 테이블 목록 로드 실패", e);
        }
      }
      // 매핑 자동 구성 — 응답 호출 후에 채우기 위해 마킹
    }

    // 인증 토큰 자동 채움 (직접 입력 모드)
    setAuthTokenMode("direct");
    setAuthServiceName("");
    if (conn.auth_type === "bearer" && conn.auth_config?.token) {
      setFromApiKey(`Bearer ${conn.auth_config.token}`);
    } else if (conn.auth_type === "api-key" && conn.auth_config?.keyValue) {
      setFromApiKey(conn.auth_config.keyValue);
    } else if (conn.auth_type === "wehago") {
      // Wehago/Amaranth: 백엔드가 매 요청마다 wehago-sign(HMAC) 자동 부착하므로 토큰 입력 불필요
      setFromApiKey("");
    } else {
      setFromApiKey("");
    }

    // 자동으로 API 호출 → 응답 본문 + 필드 추출하여 매핑 카드 즉시 활성화
    setRawResponseError("");
    setRawResponseLoading(true);
    setRawResponse(null);
    try {
      const result = await ExternalRestApiConnectionAPI.testConnectionById(
        Number(id),
        conn.endpoint_path || undefined,
      );

      if (result.success) {
        setRawResponse(result.response_data);

        const findArr = (o: any, depth = 0): any[] | null => {
          if (Array.isArray(o)) return o;
          if (depth >= 4 || typeof o !== "object" || o === null) return null;
          for (const v of Object.values(o)) {
            const a = findArr(v, depth + 1);
            if (a) return a;
          }
          return null;
        };
        const arr = findArr(result.response_data);
        if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
          const fields = Object.keys(arr[0]);
          setFromApiFields(fields);
          setFromApiData(arr);

          // Amaranth 프리셋이면 매핑 목록도 자동 구성 (편집 모드는 자체 로직이 처리)
          if (preset && !isEditMode) {
            const autoMappings: MappingItem[] = preset.mappings
              .filter((m) => fields.includes(m.from))
              .map((m, idx) => ({
                id: `auto-${idx}-${Date.now()}`,
                dbColumn: m.to,
                sourceType: "api" as const,
                apiField: m.from,
                fixedValue: "",
              }));
            if (autoMappings.length > 0) {
              setMappingList(autoMappings);
            }
          }

          toast.success(
            `'${conn.connection_name}' API 호출 완료 — 배열 ${arr.length}건 / 필드 ${fields.length}개 추출. ${preset && !isEditMode ? "매핑 자동 구성 완료." : "우측에서 DB 테이블을 선택하면 매핑 화면이 열려요."}`,
          );
        } else if (
          result.response_data &&
          typeof result.response_data === "object" &&
          !Array.isArray(result.response_data)
        ) {
          const fields = Object.keys(result.response_data as any);
          setFromApiFields(fields);
          setFromApiData([result.response_data]);
          toast.success(`'${conn.connection_name}' API 호출 완료 — 필드 ${fields.length}개 추출`);
        } else {
          toast.success(`'${conn.connection_name}' API 호출 완료 — 응답을 받았어요`);
        }
      } else {
        setRawResponseError(result.message || result.error_details || "API 호출 실패");
        toast.error(`'${conn.connection_name}' API 호출 실패: ${result.message || ""}`);
      }
    } catch (e: any) {
      setRawResponseError(e?.message || "API 호출 중 오류");
      toast.error(`API 호출 중 오류: ${e?.message ?? ""}`);
    } finally {
      setRawResponseLoading(false);
    }
  };

  // 인증 서비스명 목록 로드
  const loadAuthServiceNames = async () => {
    try {
      const serviceNames = await BatchManagementAPI.getAuthServiceNames();
      setAuthServiceNames(serviceNames);
    } catch (error) {
      console.error("인증 서비스 목록 로드 실패:", error);
    }
  };

  // 배치 타입 변경 시 상태 초기화
  //  - 편집 모드에서는 사용자가 다른 타입으로 직접 바꾸지 않는 한 폼 데이터를 보존해야 하므로
  //    초기 진입 시 (editLoading 동안) 의 batchType 변화는 무시한다.
  const [hasInitialized, setHasInitialized] = useState(false);
  useEffect(() => {
    if (!hasInitialized) {
      setHasInitialized(true);
      return;
    }
    if (isEditMode) return; // 편집 모드는 데이터를 보존
    // 공통 초기화
    setMappingList([]);

    // REST API → DB 관련 초기화
    setToConnection(null);
    setToTables([]);
    setToTable("");
    setToColumns([]);
    setFromApiUrl("");
    setFromApiKey("");
    setFromEndpoint("");
    setFromApiData([]);
    setFromApiFields([]);
    setSelectedRestApiId("manual");

    // DB → REST API 관련 초기화
    setFromConnection(null);
    setFromTables([]);
    setFromTable("");
    setFromColumns([]);
    setSelectedColumns([]);
    setDbToApiFieldMapping({});
    setToApiUrl("");
    setToApiKey("");
    setToEndpoint("");
    setToApiBody("");
    setToApiFields([]);
  }, [batchType]);

  // 연결 목록 로드
  const loadConnections = async () => {
    try {
      const result = await BatchManagementAPI.getAvailableConnections();
      setConnections(result || []);
    } catch (error) {
      console.error("연결 목록 로드 오류:", error);
      toast.error("연결 목록을 불러오는데 실패했습니다.");
    }
  };

  // ──────────────────────────────────────────────────────────────────
  // 편집 모드 (?edit=N) — 기존 배치 설정 불러와 폼 자동 채움
  //   - 연결 목록 + 등록된 REST API 목록 + DB 커넥션이 모두 로드된 뒤 실행
  // ──────────────────────────────────────────────────────────────────
  useEffect(() => {
    if (!isEditMode || !editId) return;
    if (connections.length === 0) return;
    if (registeredRestApis.length === 0) return;

    let cancelled = false;
    (async () => {
      setEditLoading(true);
      try {
        const cfg = await BatchAPI.getBatchConfig(editId);
        if (cancelled || !cfg) return;

        // 기본 정보
        setBatchName(cfg.batch_name || "");
        setCronSchedule(cfg.cron_schedule || "0 12 * * *");
        setDescription(cfg.description || "");
        if ((cfg as any).save_mode) setSaveMode((cfg as any).save_mode);
        if ((cfg as any).conflict_key) setConflictKey((cfg as any).conflict_key);
        if ((cfg as any).data_array_path) setDataArrayPath((cfg as any).data_array_path);
        // 행 단위 제외 필터 로드
        const rfRaw = (cfg as any).row_filter_config;
        if (rfRaw) {
          try {
            const parsed = typeof rfRaw === "string" ? JSON.parse(rfRaw) : rfRaw;
            const rules = Array.isArray(parsed?.exclude) ? parsed.exclude : [];
            setRowFilterRules(rules.map((r: any) => ({
              column: String(r.column || ""),
              op: (r.op === "neq" ? "neq" : "eq") as "eq" | "neq",
              value: String(r.value ?? ""),
            })));
          } catch {/* ignore */}
        }
        if ((cfg as any).auth_service_name) {
          setAuthTokenMode("db");
          setAuthServiceName((cfg as any).auth_service_name);
        }

        const mappings = cfg.batch_mappings || [];
        if (mappings.length === 0) {
          toast.warning("이 배치에는 매핑 정보가 없어요.");
          return;
        }
        const first: any = mappings[0];

        // FROM/TO 타입 결정
        const fromType = first.from_connection_type;
        const toType = first.to_connection_type;
        if (fromType === "restapi") {
          setBatchType("restapi-to-db");

          // 등록된 REST API 연결인 경우 → 자동 적용 (URL/엔드포인트/Body/인증)
          if (first.from_connection_id) {
            const conn = registeredRestApis.find((c) => c.id === first.from_connection_id);
            if (conn) {
              setSelectedRestApiId(String(conn.id));
              setFromApiUrl(conn.base_url || first.from_api_url || "");
              setFromEndpoint(conn.endpoint_path || first.from_table_name || "");
              setFromApiMethod((conn.default_method as any) || (first.from_api_method as any) || "POST");
              setFromApiBody(conn.default_body || first.from_api_body || "");
              setAuthTokenMode("direct");
              if (conn.auth_type !== "wehago") {
                if (conn.auth_type === "bearer" && conn.auth_config?.token) {
                  setFromApiKey(`Bearer ${conn.auth_config.token}`);
                } else if (conn.auth_type === "api-key" && conn.auth_config?.keyValue) {
                  setFromApiKey(conn.auth_config.keyValue);
                }
              }
            } else {
              // 등록 연결 ID 가 있는데 목록에 없는 경우 → 직접 입력 모드 fallback
              setSelectedRestApiId("manual");
              setFromApiUrl(first.from_api_url || "");
              setFromEndpoint(first.from_table_name || "");
              setFromApiMethod((first.from_api_method as any) || "GET");
              setFromApiBody(first.from_api_body || "");
              setFromApiKey(first.from_api_key || "");
            }
          } else {
            // 직접 입력으로 저장된 경우
            setSelectedRestApiId("manual");
            setFromApiUrl(first.from_api_url || "");
            setFromEndpoint(first.from_table_name || "");
            setFromApiMethod((first.from_api_method as any) || "GET");
            setFromApiBody(first.from_api_body || "");
            setFromApiKey(first.from_api_key || "");
          }

          // TO: DB 테이블 자동 선택
          let toConn: BatchConnectionInfo | null = null;
          if (toType === "internal") {
            toConn = connections.find((c) => c.type === "internal") || null;
          } else if (first.to_connection_id) {
            toConn = connections.find((c) => c.id === first.to_connection_id) || null;
          }

          if (toConn && first.to_table_name) {
            // handleToConnectionChange + handleToTableChange 의 흐름을 직접 트리거
            setToConnection(toConn);
            try {
              const connectionType = toConn.type === "internal" ? "internal" : "external";
              const tablesResult = await BatchManagementAPI.getTablesFromConnection(connectionType, toConn.id);
              const tableNames = Array.isArray(tablesResult)
                ? tablesResult.map((t: any) => (typeof t === "string" ? t : t.table_name || String(t)))
                : [];
              if (!cancelled) setToTables(tableNames);
              setToTable(first.to_table_name);
              const cols = await BatchManagementAPI.getTableColumns(connectionType, first.to_table_name, toConn.id);
              if (!cancelled && cols && cols.length > 0) setToColumns(cols);
            } catch (e) {
              console.error("[edit] TO 테이블/컬럼 로드 실패", e);
            }
          }

          // 매핑 리스트 복원 — direct/fixed/conditional 모두 처리
          const restoredMappings: MappingItem[] = mappings.map((m: any, idx: number) => {
            const mt: string = m.mapping_type || "direct";
            const sourceType: MappingItem["sourceType"] =
              mt === "fixed" ? "fixed" : mt === "conditional" ? "conditional" : "api";
            let conditionalConfig: ConditionalConfig | undefined;
            if (sourceType === "conditional" && m.mapping_config) {
              try {
                const parsed = JSON.parse(m.mapping_config);
                conditionalConfig = {
                  rules: Array.isArray(parsed.rules) ? parsed.rules : [],
                  default: parsed.default ?? "",
                };
              } catch {
                conditionalConfig = { rules: [], default: "" };
              }
            }
            return {
              id: `edit-${idx}-${Date.now()}`,
              dbColumn: m.to_column_name || "",
              sourceType,
              apiField:
                sourceType === "fixed" ? "" : m.from_column_name || "",
              fixedValue: sourceType === "fixed" ? m.from_column_name || "" : "",
              conditionalConfig,
            };
          });
          if (!cancelled) setMappingList(restoredMappings);

          // 등록된 연결이 있으면 자동으로 API 호출 → fromApiFields 채워서 매핑 드롭다운에 필드 노출
          if (first.from_connection_id) {
            try {
              const result = await ExternalRestApiConnectionAPI.testConnectionById(
                first.from_connection_id,
                first.from_table_name || undefined,
              );
              if (!cancelled && result?.success) {
                setRawResponse(result.response_data);
                const findArr = (o: any, depth = 0): any[] | null => {
                  if (Array.isArray(o)) return o;
                  if (depth >= 4 || typeof o !== "object" || o === null) return null;
                  for (const v of Object.values(o)) {
                    const a = findArr(v, depth + 1);
                    if (a) return a;
                  }
                  return null;
                };
                const arr = findArr(result.response_data);
                if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
                  setFromApiFields(Object.keys(arr[0]));
                  setFromApiData(arr);
                } else {
                  // 응답이 배열이 아니어도 — 저장된 매핑의 from_column_name 들로 fromApiFields 를 채워서 드롭다운에 보이게 함
                  const savedFields = Array.from(
                    new Set(
                      mappings
                        .filter((m: any) => m.mapping_type !== "fixed" && m.from_column_name)
                        .map((m: any) => m.from_column_name as string),
                    ),
                  );
                  if (savedFields.length > 0) setFromApiFields(savedFields);
                }
              } else {
                // 호출 실패해도 저장된 필드들로 fromApiFields 를 채워 매핑 드롭다운에 표시
                const savedFields = Array.from(
                  new Set(
                    mappings
                      .filter((m: any) => m.mapping_type !== "fixed" && m.from_column_name)
                      .map((m: any) => m.from_column_name as string),
                  ),
                );
                if (savedFields.length > 0) setFromApiFields(savedFields);
              }
            } catch (e) {
              console.error("[edit] API 자동 호출 실패", e);
              const savedFields = Array.from(
                new Set(
                  mappings
                    .filter((m: any) => m.mapping_type !== "fixed" && m.from_column_name)
                    .map((m: any) => m.from_column_name as string),
                ),
              );
              if (savedFields.length > 0) setFromApiFields(savedFields);
            }
          } else {
            // 등록된 연결 없는 경우 — 저장된 from_column_name 들을 그대로 노출
            const savedFields = Array.from(
              new Set(
                mappings
                  .filter((m: any) => m.mapping_type !== "fixed" && m.from_column_name)
                  .map((m: any) => m.from_column_name as string),
              ),
            );
            if (savedFields.length > 0) setFromApiFields(savedFields);
          }

          toast.success(`'${cfg.batch_name}' 편집 모드로 불러왔어요. 필요한 부분만 수정하세요.`);
        } else {
          // DB → REST API 등 다른 타입은 기존 편집 페이지로 라우팅
          toast.info("이 배치 타입은 기본 편집 화면에서 열어주세요.");
        }
      } catch (e: any) {
        console.error("[edit] 배치 로드 실패", e);
        toast.error("배치 정보를 불러올 수 없어요.");
      } finally {
        if (!cancelled) setEditLoading(false);
      }
    })();

    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isEditMode, editId, connections.length, registeredRestApis.length]);

  // TO 연결 변경 핸들러
  const handleToConnectionChange = async (connectionValue: string) => {
    let connection: BatchConnectionInfo | null = null;

    if (connectionValue === "internal") {
      // 내부 데이터베이스 선택
      connection = connections.find((conn) => conn.type === "internal") || null;
    } else {
      // 외부 데이터베이스 선택
      const connectionId = parseInt(connectionValue);
      connection = connections.find((conn) => conn.id === connectionId) || null;
    }

    setToConnection(connection);
    setToTable("");
    setToColumns([]);

    if (connection) {
      try {
        const connectionType = connection.type === "internal" ? "internal" : "external";
        const result = await BatchManagementAPI.getTablesFromConnection(connectionType, connection.id);
        const tableNames = Array.isArray(result)
          ? result.map((table: any) => (typeof table === "string" ? table : table.table_name || String(table)))
          : [];
        setToTables(tableNames);
      } catch (error) {
        console.error("테이블 목록 로드 오류:", error);
        toast.error("테이블 목록을 불러오는데 실패했습니다.");
      }
    }
  };

  // TO 테이블 변경 핸들러
  const handleToTableChange = async (tableName: string) => {
    setToTable(tableName);
    setToColumns([]);

    if (toConnection && tableName) {
      try {
        const connectionType = toConnection.type === "internal" ? "internal" : "external";
        const result = await BatchManagementAPI.getTableColumns(connectionType, tableName, toConnection.id);
        if (result && result.length > 0) {
          setToColumns(result);
        } else {
          setToColumns([]);
        }
      } catch (error) {
        console.error("❌ 컬럼 목록 로드 오류:", error);
        toast.error("컬럼 목록을 불러오는데 실패했습니다.");
        setToColumns([]);
      }
    }
  };

  // FROM 연결 변경 핸들러 (DB → REST API용)
  const handleFromConnectionChange = async (connectionValue: string) => {
    let connection: BatchConnectionInfo | null = null;
    if (connectionValue === "internal") {
      connection = connections.find((conn) => conn.type === "internal") || null;
    } else {
      const connectionId = parseInt(connectionValue);
      connection = connections.find((conn) => conn.id === connectionId) || null;
    }
    setFromConnection(connection);
    setFromTable("");
    setFromColumns([]);

    if (connection) {
      try {
        const connectionType = connection.type === "internal" ? "internal" : "external";
        const result = await BatchManagementAPI.getTablesFromConnection(connectionType, connection.id);
        const tableNames = Array.isArray(result)
          ? result.map((table: any) => (typeof table === "string" ? table : table.table_name || String(table)))
          : [];
        setFromTables(tableNames);
      } catch (error) {
        console.error("테이블 목록 로드 오류:", error);
        toast.error("테이블 목록을 불러오는데 실패했습니다.");
      }
    }
  };

  // FROM 테이블 변경 핸들러 (DB → REST API용)
  const handleFromTableChange = async (tableName: string) => {
    setFromTable(tableName);
    setFromColumns([]);
    setSelectedColumns([]); // 선택된 컬럼도 초기화
    setDbToApiFieldMapping({}); // 매핑도 초기화

    if (fromConnection && tableName) {
      try {
        const connectionType = fromConnection.type === "internal" ? "internal" : "external";
        const result = await BatchManagementAPI.getTableColumns(connectionType, tableName, fromConnection.id);
        if (result && result.length > 0) {
          setFromColumns(result);
        } else {
          setFromColumns([]);
        }
      } catch (error) {
        console.error("❌ FROM 컬럼 목록 로드 오류:", error);
        toast.error("컬럼 목록을 불러오는데 실패했습니다.");
        setFromColumns([]);
      }
    }
  };

  // TO API 미리보기 (DB → REST API용)
  const previewToApiData = async () => {
    if (!toApiUrl || !toApiKey || !toEndpoint) {
      toast.error("API URL, API Key, 엔드포인트를 모두 입력해주세요.");
      return;
    }

    try {
      const result = await BatchManagementAPI.previewRestApiData(
        toApiUrl,
        toApiKey,
        toEndpoint,
        "GET", // 미리보기는 항상 GET으로
      );

      if (result.fields && result.fields.length > 0) {
        setToApiFields(result.fields);
        toast.success(`TO API 필드 ${result.fields.length}개를 조회했습니다.`);
      } else {
        setToApiFields([]);
        toast.warning("TO API에서 필드를 찾을 수 없습니다.");
      }
    } catch (error) {
      console.error("❌ TO API 미리보기 오류:", error);
      toast.error("TO API 미리보기에 실패했습니다.");
      setToApiFields([]);
    }
  };

  // REST API 데이터 미리보기
  const previewRestApiData = async () => {
    // 등록된 REST API 연결이 선택된 경우엔 인증/URL/엔드포인트 검증을 백엔드가 처리하므로 스킵
    const useRegisteredConn = selectedRestApiId !== "manual";

    if (!useRegisteredConn) {
      // 직접 입력 모드 — URL/엔드포인트/토큰 검증
      if (!fromApiUrl || !fromEndpoint) {
        toast.error("API URL과 엔드포인트를 모두 입력해주세요.");
        return;
      }
      if (authTokenMode === "direct" && !fromApiKey) {
        toast.error("인증 토큰을 입력해주세요.");
        return;
      }
      if (authTokenMode === "db" && !authServiceName) {
        toast.error("인증 토큰 서비스를 선택해주세요.");
        return;
      }
    }

    try {
      const result = await BatchManagementAPI.previewRestApiData(
        fromApiUrl,
        authTokenMode === "direct" ? fromApiKey : "", // 직접 입력일 때만 API 키 전달
        fromEndpoint,
        fromApiMethod,
        // 파라미터 정보 추가
        apiParamType !== "none"
          ? {
              paramType: apiParamType,
              paramName: apiParamName,
              paramValue: apiParamValue,
              paramSource: apiParamSource,
            }
          : undefined,
        // Request Body 추가 (POST/PUT/DELETE)
        fromApiMethod === "POST" || fromApiMethod === "PUT" || fromApiMethod === "DELETE" ? fromApiBody : undefined,
        // DB 선택 모드일 때 서비스명 전달
        authTokenMode === "db" ? authServiceName : undefined,
        // 데이터 배열 경로 전달
        dataArrayPath || undefined,
        // 등록된 연결 ID (Wehago/Amaranth 인증 자동 처리)
        selectedRestApiId !== "manual" ? Number(selectedRestApiId) : undefined,
      );

      if (result.fields && result.fields.length > 0) {
        setFromApiFields(result.fields);
        setFromApiData(result.samples);
        toast.success(`API 데이터 미리보기 완료! ${result.fields.length}개 필드, ${result.totalCount}개 레코드`);
      } else if (result.samples && result.samples.length > 0) {
        // 백엔드에서 fields를 제대로 보내지 않은 경우, 프론트엔드에서 직접 추출
        const extractedFields = Object.keys(result.samples[0]);
        setFromApiFields(extractedFields);
        setFromApiData(result.samples);

        toast.success(`API 데이터 미리보기 완료! ${extractedFields.length}개 필드, ${result.samples.length}개 레코드`);
      } else {
        setFromApiFields([]);
        setFromApiData([]);
        toast.warning("API에서 데이터를 가져올 수 없습니다.");
      }
    } catch (error) {
      console.error("REST API 미리보기 오류:", error);
      toast.error("API 데이터 미리보기에 실패했습니다.");
      setFromApiFields([]);
      setFromApiData([]);
    }
  };

  // 배치 설정 저장
  const handleSave = async () => {
    if (!batchName.trim()) {
      toast.error("배치명을 입력해주세요.");
      return;
    }

    // 배치 타입별 검증 및 저장
    if (batchType === "restapi-to-db") {
      // 유효한 매핑만 필터링
      //   api: dbColumn + apiField 둘 다 필요
      //   fixed: dbColumn + fixedValue 둘 다 필요
      //   conditional: dbColumn + apiField (평가할 API 필드) + 최소 1개 룰 또는 default 필요
      const isValid = (m: MappingItem) => {
        if (!m.dbColumn) return false;
        if (m.sourceType === "api") return !!m.apiField;
        if (m.sourceType === "fixed") return m.fixedValue !== undefined && m.fixedValue !== null;
        if (m.sourceType === "conditional") {
          if (!m.apiField) return false;
          const c = m.conditionalConfig;
          return !!c && ((c.rules && c.rules.length > 0) || c.default !== undefined);
        }
        return false;
      };
      const validMappings = mappingList.filter(isValid);
      const droppedMappings = mappingList.filter((m) => m.dbColumn && !isValid(m));

      // ⚠ 누락된 매핑이 있으면 사용자가 알아차릴 수 있게 명시적으로 차단 + 어떤 컬럼/이유인지 표시
      //   (이전엔 silent drop 되어 저장된 줄 알았는데 일부 매핑이 누락되는 문제 발생)
      if (droppedMappings.length > 0) {
        const reasons = droppedMappings.map((m) => {
          if (m.sourceType === "conditional" && !m.apiField) return `${m.dbColumn}: 평가 필드 미선택`;
          if (m.sourceType === "conditional") return `${m.dbColumn}: 규칙 또는 기본값 필요`;
          if (m.sourceType === "api") return `${m.dbColumn}: API 필드 미선택`;
          return `${m.dbColumn}: 값 누락`;
        }).join(", ");
        toast.error(`매핑 입력 누락 — ${reasons}`);
        return;
      }

      if (validMappings.length === 0) {
        toast.error("최소 하나의 매핑을 설정해주세요.");
        return;
      }

      // UPSERT 모드일 때 conflict key 검증
      if (saveMode === "UPSERT" && !conflictKey) {
        toast.error("UPSERT 모드에서는 충돌 기준 컬럼을 선택해주세요.");
        return;
      }

      // 통합 매핑 리스트를 배치 매핑 형태로 변환
      // 고정값 매핑도 동일한 from_connection_type을 사용해야 같은 그룹으로 처리됨
      const apiMappings = validMappings.map((mapping) => {
        // from_column_name: api/conditional 은 평가할 API 필드, fixed 는 고정값
        let fromColumnName: string;
        if (mapping.sourceType === "fixed") fromColumnName = mapping.fixedValue;
        else fromColumnName = mapping.apiField; // api & conditional 모두 apiField 사용
        const mappingType =
          mapping.sourceType === "fixed"
            ? ("fixed" as const)
            : mapping.sourceType === "conditional"
              ? ("conditional" as const)
              : ("direct" as const);
        return {
          from_connection_type: "restapi" as const,
          // 등록된 외부 REST API 연결을 선택했으면 그 id 를 함께 저장 → 로드 시 셀렉트 복원
          from_connection_id:
            selectedRestApiId !== "manual" ? Number(selectedRestApiId) : undefined,
          from_table_name: fromEndpoint,
          from_column_name: fromColumnName,
          from_api_url: fromApiUrl,
          from_api_key: authTokenMode === "direct" ? fromApiKey : "",
          from_api_method: fromApiMethod,
          from_api_body:
            fromApiMethod === "POST" || fromApiMethod === "PUT" || fromApiMethod === "DELETE" ? fromApiBody : undefined,
          from_api_param_type: apiParamType !== "none" ? apiParamType : undefined,
          from_api_param_name: apiParamType !== "none" ? apiParamName : undefined,
          from_api_param_value: apiParamType !== "none" ? apiParamValue : undefined,
          from_api_param_source: apiParamType !== "none" ? apiParamSource : undefined,
          to_connection_type: toConnection?.type === "internal" ? "internal" : "external",
          to_connection_id: toConnection?.type === "internal" ? undefined : toConnection?.id,
          to_table_name: toTable,
          to_column_name: mapping.dbColumn,
          mapping_type: mappingType,
          fixed_value: mapping.sourceType === "fixed" ? mapping.fixedValue : undefined,
          // 조건부 룰을 mapping_config(JSON 문자열)에 저장
          mapping_config:
            mapping.sourceType === "conditional" && mapping.conditionalConfig
              ? JSON.stringify(mapping.conditionalConfig)
              : undefined,
        };
      });

      // 실제 API 호출
      try {
        if (isEditMode && editId) {
          // 편집 모드 — 기존 배치 업데이트
          await BatchAPI.updateBatchConfig(editId, {
            batchName,
            description,
            cronSchedule,
            isActive: "Y",
            saveMode,
            conflictKey: saveMode === "UPSERT" ? conflictKey : undefined,
            authServiceName: authTokenMode === "db" ? authServiceName : undefined,
            dataArrayPath: dataArrayPath || undefined,
            rowFilterConfig: rowFilterRules.length > 0
              ? { exclude: rowFilterRules.filter(r => r.column && r.value !== "") }
              : null,
            mappings: apiMappings,
          } as any);
          toast.success("배치가 수정되었습니다.");
          setTimeout(() => {
            openTab({ type: "admin", title: "배치 관리", adminUrl: "/admin/automaticMng/batchmngList" });
          }, 800);
        } else {
          const result = await BatchManagementAPI.saveRestApiBatch({
            batchName,
            batchType,
            cronSchedule,
            description,
            apiMappings,
            authServiceName: authTokenMode === "db" ? authServiceName : undefined,
            dataArrayPath: dataArrayPath || undefined,
            saveMode,
            conflictKey: saveMode === "UPSERT" ? conflictKey : undefined,
            rowFilterConfig: rowFilterRules.length > 0
              ? { exclude: rowFilterRules.filter(r => r.column && r.value !== "") }
              : null,
          } as any);

          if (result.success) {
            toast.success(result.message || "REST API 배치 설정이 저장되었습니다.");
            setTimeout(() => {
              openTab({ type: "admin", title: "배치 관리", adminUrl: "/admin/automaticMng/batchmngList" });
            }, 1000);
          } else {
            toast.error(result.message || "배치 저장에 실패했습니다.");
          }
        }
      } catch (error) {
        console.error("배치 저장 오류:", error);
        showErrorToast("배치 설정 저장에 실패했습니다", error, {
          guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
        });
      }
      return;
    } else if (batchType === "db-to-restapi") {
      // DB → REST API 배치 검증
      if (!fromConnection || !fromTable || selectedColumns.length === 0) {
        toast.error("소스 데이터베이스, 테이블, 컬럼을 선택해주세요.");
        return;
      }

      if (!toApiUrl || !toApiKey || !toEndpoint) {
        toast.error("대상 API URL, API Key, 엔드포인트를 입력해주세요.");
        return;
      }

      if ((toApiMethod === "POST" || toApiMethod === "PUT") && !toApiBody) {
        toast.error("POST/PUT 메서드의 경우 Request Body 템플릿을 입력해주세요.");
        return;
      }

      // DELETE의 경우 빈 Request Body라도 템플릿 로직을 위해 "{}" 설정
      let finalToApiBody = toApiBody;
      if (toApiMethod === "DELETE" && !finalToApiBody.trim()) {
        finalToApiBody = "{}";
      }

      // DB → REST API 매핑 생성 (선택된 컬럼만)
      const selectedColumnObjects = fromColumns.filter((column) => selectedColumns.includes(column.column_name));
      const dbMappings = selectedColumnObjects.map((column, index) => ({
        from_connection_type: fromConnection.type === "internal" ? "internal" : "external",
        from_connection_id: fromConnection.type === "internal" ? undefined : fromConnection.id,
        from_table_name: fromTable,
        from_column_name: column.column_name,
        from_column_type: column.data_type,
        to_connection_type: "restapi" as const,
        to_table_name: toEndpoint, // API 엔드포인트
        to_column_name: dbToApiFieldMapping[column.column_name] || column.column_name, // 매핑된 API 필드명
        to_api_url: toApiUrl,
        to_api_key: toApiKey,
        to_api_method: toApiMethod,
        to_api_body: finalToApiBody, // Request Body 템플릿
        mapping_type: "template" as const,
        mapping_order: index + 1,
      }));

      // URL 경로 파라미터 매핑 추가 (PUT/DELETE용)
      if ((toApiMethod === "PUT" || toApiMethod === "DELETE") && urlPathColumn) {
        const urlPathColumnObject = fromColumns.find((col) => col.column_name === urlPathColumn);
        if (urlPathColumnObject) {
          dbMappings.push({
            from_connection_type: fromConnection.type === "internal" ? "internal" : "external",
            from_connection_id: fromConnection.type === "internal" ? undefined : fromConnection.id,
            from_table_name: fromTable,
            from_column_name: urlPathColumn,
            from_column_type: urlPathColumnObject.data_type,
            to_connection_type: "restapi" as const,
            to_table_name: toEndpoint,
            to_column_name: "URL_PATH_PARAM", // 특별한 식별자
            to_api_url: toApiUrl,
            to_api_key: toApiKey,
            to_api_method: toApiMethod,
            to_api_body: finalToApiBody,
            mapping_type: "url_path" as const,
            mapping_order: 999, // 마지막 순서
          });
        }
      }

      // 실제 API 호출 (기존 saveRestApiBatch 재사용)
      try {
        const result = await BatchManagementAPI.saveRestApiBatch({
          batchName,
          batchType,
          cronSchedule,
          description,
          apiMappings: dbMappings,
          authServiceName: authServiceName || undefined,
        });

        if (result.success) {
          toast.success(result.message || "DB → REST API 배치 설정이 저장되었습니다.");
          setTimeout(() => {
            openTab({ type: "admin", title: "배치 관리", adminUrl: "/admin/automaticMng/batchmngList" });
          }, 1000);
        } else {
          toast.error(result.message || "배치 저장에 실패했습니다.");
        }
      } catch (error) {
        console.error("배치 저장 오류:", error);
        showErrorToast("배치 설정 저장에 실패했습니다", error, {
          guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
        });
      }
      return;
    }

    toast.error("지원하지 않는 배치 타입입니다.");
  };

  const goBack = () => openTab({ type: "admin", title: "배치 관리", adminUrl: "/admin/automaticMng/batchmngList" });

  return (
    <div className="bg-background h-full overflow-y-auto">
      <div className="mx-auto w-full max-w-[1920px] space-y-3 px-3 pb-20 pt-2 sm:px-4 2xl:px-5">
      {/* 헤더 — 더 컴팩트 */}
      <div className="sticky top-0 z-10 -mx-3 -mt-2 flex items-center justify-between border-b bg-background/95 px-3 py-1.5 backdrop-blur sm:-mx-4 sm:px-4 2xl:-mx-5 2xl:px-5">
        <div className="flex items-center gap-1.5">
          <button onClick={goBack} className="flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
            <ArrowLeft className="h-3.5 w-3.5" />
          </button>
          <div className="flex items-baseline gap-2">
            <h1 className="text-sm font-semibold">
              {isEditMode ? `배치 편집 #${editId}` : "고급 배치 생성"}
            </h1>
            <p className="text-[11px] text-muted-foreground">REST API / DB 간 데이터 동기화</p>
            {editLoading && (
              <span className="text-[11px] text-muted-foreground">(불러오는 중...)</span>
            )}
          </div>
        </div>
        <div className="flex items-center gap-1.5">
          <Button onClick={goBack} variant="outline" size="sm" className="h-7 gap-1 px-2 text-xs">취소</Button>
          <Button onClick={handleSave} size="sm" className="h-7 gap-1 px-2 text-xs">
            <Save className="h-3 w-3" />
            {isEditMode ? "수정" : "저장"}
          </Button>
        </div>
      </div>

      {/* 배치 타입 + 기본 정보를 한 줄로 (FullHD에서 가로 활용) */}
      <div className="grid grid-cols-1 gap-2 2xl:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]">
        {/* 배치 타입 선택 — 컴팩트 가로 칩 */}
        <div className="flex gap-2">
          {batchTypeOptions.map((option) => (
            <button
              key={option.value}
              onClick={() => setBatchType(option.value)}
              className={`flex flex-1 items-center gap-2 rounded-md border px-2.5 py-2 text-left transition-all ${
                batchType === option.value
                  ? "border-primary bg-primary/5 ring-1 ring-primary/30"
                  : "border-border hover:border-muted-foreground/30 hover:bg-muted/50"
              }`}
            >
              <div className={`flex h-7 w-7 shrink-0 items-center justify-center rounded ${batchType === option.value ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"}`}>
                {option.value === "restapi-to-db" ? <Globe className="h-3.5 w-3.5" /> : <Database className="h-3.5 w-3.5" />}
              </div>
              <div className="min-w-0">
                <div className="text-xs font-medium leading-tight">{option.label}</div>
                <div className="text-[10px] text-muted-foreground leading-tight">{option.description}</div>
              </div>
            </button>
          ))}
        </div>

        {/* 기본 정보 */}
        <div className="rounded-md border p-2.5">
          <div className="grid grid-cols-1 gap-2 sm:grid-cols-[1fr_180px_1fr]">
            <div className="space-y-1">
              <Label htmlFor="batchName" className="text-[11px]">배치명 <span className="text-destructive">*</span></Label>
              <Input id="batchName" value={batchName} onChange={e => setBatchName(e.target.value)} placeholder="배치명" className="h-7 text-xs" />
            </div>
            <div className="space-y-1">
              <Label htmlFor="cronSchedule" className="text-[11px]">실행 스케줄 <span className="text-destructive">*</span></Label>
              <Input id="cronSchedule" value={cronSchedule} onChange={e => setCronSchedule(e.target.value)} placeholder="0 12 * * *" className="h-7 font-mono text-xs" />
            </div>
            <div className="space-y-1">
              <Label htmlFor="description" className="text-[11px]">설명</Label>
              <Input id="description" value={description} onChange={e => setDescription(e.target.value)} placeholder="설명 (선택)" className="h-7 text-xs" />
            </div>
          </div>
        </div>
      </div>

      {/* FROM/TO 설정 - 가로 배치 (gap 축소) */}
      <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
        {/* FROM 설정 */}
        <Card>
          <CardHeader className="px-3 pb-2 pt-3">
            <CardTitle className="flex items-center text-sm">
              {batchType === "restapi-to-db" ? (
                <>
                  <Globe className="mr-1.5 h-4 w-4" />
                  FROM: REST API (소스)
                </>
              ) : (
                <>
                  <Database className="mr-1.5 h-4 w-4" />
                  FROM: 데이터베이스 (소스)
                </>
              )}
            </CardTitle>
          </CardHeader>
          <CardContent className="space-y-2 px-3 pb-3 pt-0">
            {/* REST API 설정 (REST API → DB) — 컴팩트 */}
            {batchType === "restapi-to-db" && (
              <div className="space-y-2">
                {/* 등록된 REST API 연결에서 선택 — 헤더+Select 한 줄 */}
                <div className="rounded-md border bg-muted/30 p-2">
                  <div className="grid grid-cols-[auto_1fr] items-center gap-2">
                    <Label className="flex items-center gap-1 text-[11px] font-medium whitespace-nowrap">
                      <LinkIcon className="h-3 w-3" />
                      등록된 연결
                    </Label>
                    <Select value={selectedRestApiId} onValueChange={applyRegisteredRestApi}>
                      <SelectTrigger className="h-7 text-xs">
                        <SelectValue placeholder="등록된 연결 선택 (또는 직접 입력)" />
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="manual">직접 입력 (등록된 연결 사용 안 함)</SelectItem>
                        {registeredRestApis.map((conn) => (
                          <SelectItem key={conn.id} value={String(conn.id)}>
                            {conn.connection_name}
                            {conn.auth_type === "wehago" && (
                              <span className="ml-2 text-[10px] text-muted-foreground">[Wehago]</span>
                            )}
                            {conn.auth_type === "bearer" && (
                              <span className="ml-2 text-[10px] text-muted-foreground">[Bearer]</span>
                            )}
                            {conn.auth_type === "api-key" && (
                              <span className="ml-2 text-[10px] text-muted-foreground">[Key]</span>
                            )}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                </div>

                {/* 등록된 연결 선택 시 한 줄 요약 — Method · URL · Auth */}
                {selectedRestApiId !== "manual" && (() => {
                  const conn = registeredRestApis.find((c) => String(c.id) === selectedRestApiId);
                  if (!conn) return null;
                  const authLabel =
                    conn.auth_type === "wehago" ? "HMAC-SHA256 자동 서명"
                    : conn.auth_type === "bearer" ? "Bearer (저장)"
                    : conn.auth_type === "api-key" ? "API Key (저장)"
                    : conn.auth_type === "none" ? "없음" : conn.auth_type;
                  return (
                    <div className="rounded-md border bg-muted/30 px-2 py-1.5 text-[11px]">
                      <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                        <span className="rounded bg-primary/10 px-1 py-0.5 text-[10px] font-semibold text-primary">
                          {conn.auth_type === "wehago" ? "Wehago" : conn.auth_type.toUpperCase()}
                        </span>
                        <span className="font-mono text-[10px] text-muted-foreground">{conn.default_method || "POST"}</span>
                        <span className="font-mono break-all">{conn.base_url}{conn.endpoint_path || ""}</span>
                        <span className="ml-auto text-[10px] text-muted-foreground">인증: {authLabel}</span>
                      </div>
                      {conn.default_body && (
                        <details className="mt-1">
                          <summary className="cursor-pointer text-[10px] text-primary hover:underline">Request Body</summary>
                          <pre className="mt-1 max-h-[120px] overflow-auto rounded bg-background p-2 text-[10px]">{conn.default_body}</pre>
                        </details>
                      )}
                    </div>
                  );
                })()}

                {/* 직접 입력 모드일 때만 노출되는 수동 입력 영역 — 컴팩트 그리드 */}
                {selectedRestApiId === "manual" && (
                <div className="space-y-2">
                  {/* URL + 메서드 한 줄 */}
                  <div className="grid grid-cols-[1fr_120px] gap-2">
                    <div className="space-y-1">
                      <Label htmlFor="fromApiUrl" className="text-[11px]">API 서버 URL *</Label>
                      <Input id="fromApiUrl" value={fromApiUrl} onChange={(e) => setFromApiUrl(e.target.value)} placeholder="https://api.example.com" className="h-7 text-xs" />
                    </div>
                    <div className="space-y-1">
                      <Label className="text-[11px]">메서드</Label>
                      <Select value={fromApiMethod} onValueChange={(value: any) => setFromApiMethod(value)}>
                        <SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          <SelectItem value="GET">GET</SelectItem>
                          <SelectItem value="POST">POST</SelectItem>
                          <SelectItem value="PUT">PUT</SelectItem>
                          <SelectItem value="DELETE">DELETE</SelectItem>
                        </SelectContent>
                      </Select>
                    </div>
                  </div>

                  {/* 엔드포인트 + 데이터 배열 경로 한 줄 */}
                  <div className="grid grid-cols-2 gap-2">
                    <div className="space-y-1">
                      <Label htmlFor="fromEndpoint" className="text-[11px]">엔드포인트 *</Label>
                      <Input id="fromEndpoint" value={fromEndpoint} onChange={(e) => setFromEndpoint(e.target.value)} placeholder="/api/users" className="h-7 text-xs" />
                    </div>
                    <div className="space-y-1">
                      <Label htmlFor="dataArrayPath" className="text-[11px]">데이터 배열 경로</Label>
                      <Input id="dataArrayPath" value={dataArrayPath} onChange={(e) => setDataArrayPath(e.target.value)} placeholder="resultData (비우면 자동)" className="h-7 text-xs" />
                    </div>
                  </div>

                  {/* 인증 토큰 — 라디오 + 입력을 한 줄로 */}
                  <div className="space-y-1">
                    <Label className="text-[11px]">인증 토큰</Label>
                    <div className="grid grid-cols-[auto_1fr] items-center gap-2">
                      <div className="flex gap-1 rounded-md border p-0.5">
                        <button type="button" onClick={() => { setAuthTokenMode("direct"); setAuthServiceName(""); }} className={`h-6 rounded px-2 text-[11px] ${authTokenMode === "direct" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`}>직접 입력</button>
                        <button type="button" onClick={() => setAuthTokenMode("db")} className={`h-6 rounded px-2 text-[11px] ${authTokenMode === "db" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`}>DB에서 선택</button>
                      </div>
                      {authTokenMode === "direct" ? (
                        <Input id="fromApiKey" value={fromApiKey} onChange={(e) => setFromApiKey(e.target.value)} placeholder="Bearer eyJhbGciOiJIUzI1NiIs..." className="h-7 text-xs" />
                      ) : (
                        <Select value={authServiceName || "none"} onValueChange={(v) => setAuthServiceName(v === "none" ? "" : v)}>
                          <SelectTrigger className="h-7 text-xs"><SelectValue placeholder="서비스명" /></SelectTrigger>
                          <SelectContent>
                            <SelectItem value="none">선택 안 함</SelectItem>
                            {authServiceNames.map((name) => (<SelectItem key={name} value={name}>{name}</SelectItem>))}
                          </SelectContent>
                        </Select>
                      )}
                    </div>
                  </div>

                  {/* Request Body (POST/PUT/DELETE용) — 줄어든 높이 */}
                  {(fromApiMethod === "POST" || fromApiMethod === "PUT" || fromApiMethod === "DELETE") && (
                    <div className="space-y-1">
                      <Label htmlFor="fromApiBody" className="text-[11px]">Request Body (JSON)</Label>
                      <Textarea id="fromApiBody" value={fromApiBody} onChange={(e) => setFromApiBody(e.target.value)} placeholder='{"username": "myuser"}' className="min-h-[60px] text-xs" rows={3} />
                    </div>
                  )}
                </div>
                )}

                {/* 등록된 연결 모드에서도 dataArrayPath 는 노출 — 응답 배열 위치 다를 수 있음 */}
                {selectedRestApiId !== "manual" && (
                  <div className="grid grid-cols-[auto_1fr] items-center gap-2">
                    <Label htmlFor="dataArrayPath2" className="text-[11px] whitespace-nowrap">데이터 배열 경로</Label>
                    <Input
                      id="dataArrayPath2"
                      value={dataArrayPath}
                      onChange={(e) => setDataArrayPath(e.target.value)}
                      placeholder="resultData (비우면 자동 탐색)"
                      className="h-7 text-xs"
                    />
                  </div>
                )}

                {/* 빠른 응답 테스트 — 컴팩트 */}
                <div className="rounded-md border bg-muted/20 p-2">
                  <div className="flex items-center justify-between gap-2">
                    <Label className="text-[11px] font-medium">응답 미리보기 (Quick Test)</Label>
                    <Button
                      type="button"
                      variant="outline"
                      size="sm"
                      onClick={runQuickResponseTest}
                      disabled={rawResponseLoading || !fromApiUrl || !fromEndpoint}
                      className="h-6 gap-1 px-2 text-[11px]"
                    >
                      <Eye className="h-3 w-3" />
                      {rawResponseLoading ? "호출 중..." : "응답 확인"}
                    </Button>
                  </div>

                  {rawResponseError && (
                    <div className="mt-1.5 rounded border border-destructive/30 bg-destructive/10 p-1.5 text-[11px] text-destructive">
                      {rawResponseError}
                    </div>
                  )}

                  {rawResponse !== null && rawResponse !== undefined && (() => {
                    // 응답에서 배열을 자동 탐색 (최대 4단계)
                    const findArray = (o: any, depth = 0): any[] | null => {
                      if (Array.isArray(o)) return o;
                      if (depth >= 4 || typeof o !== "object" || o === null) return null;
                      for (const v of Object.values(o)) {
                        const arr = findArray(v, depth + 1);
                        if (arr) return arr;
                      }
                      return null;
                    };
                    const arr = findArray(rawResponse);
                    const fields =
                      arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null
                        ? Object.keys(arr[0])
                        : rawResponse && typeof rawResponse === "object" && !Array.isArray(rawResponse)
                          ? Object.keys(rawResponse)
                          : [];

                    return (
                      <div className="mt-2 space-y-1.5">
                        <div className="flex items-center gap-2 text-[11px] text-muted-foreground">
                          <span>
                            {arr ? (
                              <>
                                배열 <span className="font-semibold text-foreground">{arr.length}</span>건 ·
                              </>
                            ) : null}
                            필드 <span className="font-semibold text-foreground">{fields.length}</span>개
                          </span>
                        </div>

                        {fields.length > 0 && (
                          <details className="rounded border bg-background">
                            <summary className="cursor-pointer px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted/50">
                              매핑 가능 필드 보기 ({fields.length})
                            </summary>
                            <div className="max-h-[120px] overflow-auto p-2">
                              <div className="flex flex-wrap gap-1">
                                {fields.map((f) => (
                                  <span key={f} className="rounded border bg-muted/30 px-1.5 py-0.5 font-mono text-[10px]">{f}</span>
                                ))}
                              </div>
                            </div>
                          </details>
                        )}

                        <details className="rounded border bg-background">
                          <summary className="cursor-pointer px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted/50">
                            응답 본문 (JSON) 보기
                          </summary>
                          <pre className="max-h-[180px] overflow-auto p-2 text-[10px] leading-relaxed font-mono">
                            {(() => {
                              try {
                                return JSON.stringify(rawResponse, null, 2);
                              } catch {
                                return String(rawResponse);
                              }
                            })()}
                          </pre>
                        </details>
                      </div>
                    );
                  })()}
                </div>

                {/* API 파라미터 설정 — 사용 시에만 펼침 */}
                <details className="rounded-md border bg-muted/20 p-2" open={apiParamType !== "none"}>
                  <summary className="cursor-pointer text-[11px] font-medium hover:text-foreground">
                    API 파라미터 설정 {apiParamType !== "none" && <span className="ml-1 rounded bg-primary/10 px-1 text-[10px] text-primary">활성</span>}
                  </summary>
                  <div className="mt-2 space-y-2">
                    <div className="grid grid-cols-2 gap-2">
                      <div className="space-y-1">
                        <Label className="text-[11px]">파라미터 타입</Label>
                        <Select value={apiParamType} onValueChange={(value: any) => setApiParamType(value)}>
                          <SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
                          <SelectContent>
                            <SelectItem value="none">파라미터 없음</SelectItem>
                            <SelectItem value="url">URL 파라미터</SelectItem>
                            <SelectItem value="query">쿼리 파라미터</SelectItem>
                          </SelectContent>
                        </Select>
                      </div>
                      {apiParamType !== "none" && (
                        <div className="space-y-1">
                          <Label htmlFor="apiParamName" className="text-[11px]">파라미터명 *</Label>
                          <Input id="apiParamName" value={apiParamName} onChange={(e) => setApiParamName(e.target.value)} placeholder="userId, id" className="h-7 text-xs" />
                        </div>
                      )}
                    </div>
                    {apiParamType !== "none" && (
                      <div className="grid grid-cols-[140px_1fr] gap-2">
                        <div className="space-y-1">
                          <Label className="text-[11px]">소스</Label>
                          <Select value={apiParamSource} onValueChange={(value: any) => setApiParamSource(value)}>
                            <SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
                            <SelectContent>
                              <SelectItem value="static">고정값</SelectItem>
                              <SelectItem value="dynamic">동적값</SelectItem>
                            </SelectContent>
                          </Select>
                        </div>
                        <div className="space-y-1">
                          <Label htmlFor="apiParamValue" className="text-[11px]">{apiParamSource === "static" ? "값" : "템플릿"} *</Label>
                          <Input id="apiParamValue" value={apiParamValue} onChange={(e) => setApiParamValue(e.target.value)} placeholder={apiParamSource === "static" ? "123" : "{{user_id}}"} className="h-7 text-xs" />
                        </div>
                      </div>
                    )}
                  </div>
                </details>

                {/* API 호출 미리보기 정보 — 한 줄로 */}
                {fromApiUrl && fromEndpoint && (
                  <div className="rounded-md bg-muted px-2 py-1.5 text-[11px]">
                    <span className="font-medium">호출:</span>{" "}
                    <span className="font-mono text-muted-foreground">
                      {fromApiMethod} {fromApiUrl}
                      {apiParamType === "url" && apiParamName && apiParamValue
                        ? fromEndpoint.replace(`{${apiParamName}}`, apiParamValue) || fromEndpoint + `/${apiParamValue}`
                        : fromEndpoint}
                      {apiParamType === "query" && apiParamName && apiParamValue ? `?${apiParamName}=${apiParamValue}` : ""}
                    </span>
                  </div>
                )}
              </div>
            )}

            {/* DB 설정 (DB → REST API) */}
            {batchType === "db-to-restapi" && (
              <div className="space-y-4">
                <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
                  <div>
                    <Label>소스 연결 *</Label>
                    <Select
                      value={fromConnection?.id?.toString() || fromConnection?.type || ""}
                      onValueChange={handleFromConnectionChange}
                    >
                      <SelectTrigger>
                        <SelectValue placeholder="연결을 선택하세요" />
                      </SelectTrigger>
                      <SelectContent>
                        {connections.map((connection) => (
                          <SelectItem
                            key={connection.id || "internal"}
                            value={connection.id ? connection.id.toString() : "internal"}
                          >
                            {connection.name} ({connection.type === "internal" ? "내부 DB" : connection.db_type})
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  <div>
                    <Label>소스 테이블 *</Label>
                    <Select value={fromTable} onValueChange={handleFromTableChange}>
                      <SelectTrigger>
                        <SelectValue placeholder="테이블을 선택하세요" />
                      </SelectTrigger>
                      <SelectContent>
                        {Array.isArray(fromTables) &&
                          fromTables.map((table: string) => (
                            <SelectItem key={table} value={table}>
                              {table.toUpperCase()}
                            </SelectItem>
                          ))}
                      </SelectContent>
                    </Select>
                  </div>
                </div>

                {/* 컬럼 선택 UI */}
                {fromColumns.length > 0 && (
                  <div>
                    <Label>컬럼 선택 (선택된 컬럼이 API로 전송됩니다)</Label>
                    <div className="mt-2 grid max-h-60 grid-cols-2 gap-2 overflow-y-auto rounded-lg border p-3 md:grid-cols-3 lg:grid-cols-4">
                      {fromColumns.map((column) => (
                        <div key={column.column_name} className="flex items-center space-x-2">
                          <input
                            type="checkbox"
                            id={`col-${column.column_name}`}
                            checked={selectedColumns.includes(column.column_name)}
                            onChange={(e) => {
                              if (e.target.checked) {
                                setSelectedColumns([...selectedColumns, column.column_name]);
                              } else {
                                setSelectedColumns(selectedColumns.filter((col) => col !== column.column_name));
                              }
                            }}
                            className="rounded border-input"
                          />
                          <label
                            htmlFor={`col-${column.column_name}`}
                            className="flex-1 cursor-pointer text-sm"
                            title={`타입: ${column.data_type} | NULL: ${column.is_nullable ? "Y" : "N"}`}
                          >
                            {column.column_name}
                          </label>
                        </div>
                      ))}
                    </div>

                    {/* 선택된 컬럼 개수 표시 */}
                    <div className="mt-2 text-xs text-muted-foreground">
                      선택된 컬럼: {selectedColumns.length}개 / 전체: {fromColumns.length}개
                    </div>

                    {/* 빠른 매핑 버튼들 */}
                    {selectedColumns.length > 0 && toApiFields.length > 0 && (
                      <div className="mt-3 rounded-lg border border-emerald-200 bg-emerald-50 p-3">
                        <div className="mb-2 text-sm font-medium text-emerald-800">빠른 매핑</div>
                        <div className="flex flex-wrap gap-2">
                          <button
                            type="button"
                            onClick={() => {
                              const mapping: Record<string, string> = {};
                              selectedColumns.forEach((col) => {
                                // 스마트 매핑 로직
                                const matchingApiField = toApiFields.find((apiField) => {
                                  const colLower = col.toLowerCase();
                                  const apiLower = apiField.toLowerCase();

                                  // 정확한 매치
                                  if (colLower === apiLower) return true;

                                  // 언더스코어 무시 매치
                                  if (colLower.replace(/_/g, "") === apiLower.replace(/_/g, "")) return true;
                                  // 의미적 매핑
                                  if (
                                    (colLower.includes("created") || colLower.includes("reg")) &&
                                    (apiLower.includes("date") || apiLower.includes("time"))
                                  )
                                    return true;
                                  if (
                                    (colLower.includes("updated") || colLower.includes("mod")) &&
                                    (apiLower.includes("date") || apiLower.includes("time"))
                                  )
                                    return true;
                                  if (colLower.includes("id") && apiLower.includes("id")) return true;
                                  if (colLower.includes("name") && apiLower.includes("name")) return true;
                                  if (colLower.includes("code") && apiLower.includes("code")) return true;

                                  return false;
                                });

                                if (matchingApiField) {
                                  mapping[col] = matchingApiField;
                                }
                              });
                              setDbToApiFieldMapping(mapping);
                              toast.success(`${Object.keys(mapping).length}개 컬럼이 자동 매핑되었습니다.`);
                            }}
                            className="rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
                          >
                            스마트 자동 매핑
                          </button>
                          <button
                            type="button"
                            onClick={() => {
                              setDbToApiFieldMapping({});
                              toast.success("매핑이 초기화되었습니다.");
                            }}
                            className="rounded bg-foreground/80 px-3 py-1 text-xs text-white hover:bg-foreground/90"
                          >
                            매핑 초기화
                          </button>
                        </div>
                      </div>
                    )}

                    {/* 자동 생성된 JSON 미리보기 */}
                    {selectedColumns.length > 0 && (
                      <div className="mt-3 rounded-lg border border-primary/20 bg-primary/10 p-3">
                        <div className="mb-2 text-sm font-medium text-primary">자동 생성된 JSON 구조</div>
                        <pre className="overflow-x-auto font-mono text-xs text-primary">
                          {JSON.stringify(
                            selectedColumns.reduce(
                              (obj, col) => {
                                const apiField = dbToApiFieldMapping[col] || col; // 매핑된 API 필드명 또는 원본 컬럼명
                                obj[apiField] = `{{${col}}}`;
                                return obj;
                              },
                              {} as Record<string, string>,
                            ),
                            null,
                            2,
                          )}
                        </pre>
                        <button
                          type="button"
                          onClick={() => {
                            const autoJson = JSON.stringify(
                              selectedColumns.reduce(
                                (obj, col) => {
                                  const apiField = dbToApiFieldMapping[col] || col; // 매핑된 API 필드명 또는 원본 컬럼명
                                  obj[apiField] = `{{${col}}}`;
                                  return obj;
                                },
                                {} as Record<string, string>,
                              ),
                              null,
                              2,
                            );
                            setToApiBody(autoJson);
                            toast.success("Request Body에 자동 생성된 JSON이 적용되었습니다.");
                          }}
                          className="mt-2 rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
                        >
                          Request Body에 적용
                        </button>
                      </div>
                    )}
                  </div>
                )}
              </div>
            )}
          </CardContent>
        </Card>

        {/* TO 설정 */}
        <Card>
          <CardHeader className="px-3 pb-2 pt-3">
            <CardTitle className="flex items-center text-sm">
              {batchType === "restapi-to-db" ? (
                <>
                  <Database className="mr-1.5 h-4 w-4" />
                  TO: 데이터베이스 (대상)
                </>
              ) : (
                <>
                  <Globe className="mr-1.5 h-4 w-4" />
                  TO: REST API (대상)
                </>
              )}
            </CardTitle>
          </CardHeader>
          <CardContent className="space-y-2 px-3 pb-3 pt-0">
            {/* DB 설정 (REST API → DB) - 컴팩트 (FullHD 풀 활용) */}
            {batchType === "restapi-to-db" && (
              <div className="space-y-2">
                {/* 1. 커넥션 + 테이블 — 한 줄 */}
                <div className="grid grid-cols-2 gap-2">
                  <div className="space-y-1">
                    <Label className="text-[11px]">DB 커넥션 *</Label>
                    <Select
                      value={
                        toConnection
                          ? toConnection.type === "internal"
                            ? "internal"
                            : toConnection.id?.toString() || ""
                          : ""
                      }
                      onValueChange={handleToConnectionChange}
                    >
                      <SelectTrigger className="h-7 text-xs">
                        <SelectValue placeholder="커넥션 선택" />
                      </SelectTrigger>
                      <SelectContent>
                        {connections.map((connection, index) => (
                          <SelectItem
                            key={connection.id || `internal-${index}`}
                            value={connection.id ? connection.id.toString() : "internal"}
                          >
                            {connection.name}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  <div className={`space-y-1 ${toTables.length === 0 ? "pointer-events-none opacity-50" : ""}`}>
                    <Label className="text-[11px]">테이블 *</Label>
                    <Select value={toTable || ""} onValueChange={handleToTableChange} disabled={toTables.length === 0}>
                      <SelectTrigger className="h-7 text-xs">
                        <SelectValue placeholder={toTables.length === 0 ? "먼저 커넥션 선택" : "테이블 선택"} />
                      </SelectTrigger>
                      <SelectContent>
                        {toTables.map((table: string) => (
                          <SelectItem key={table} value={table}>
                            {table.toUpperCase()}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                </div>

                {/* 2. 저장 모드 + 충돌 기준 — 한 줄 */}
                <div className={`grid grid-cols-[auto_1fr] items-end gap-2 ${!toTable ? "pointer-events-none opacity-50" : ""}`}>
                  <div className="space-y-1">
                    <Label className="text-[11px]">저장 모드</Label>
                    <div className="flex gap-1 rounded-md border p-0.5">
                      <button
                        type="button"
                        onClick={() => { setSaveMode("INSERT"); setConflictKey(""); }}
                        disabled={!toTable}
                        className={`h-6 rounded px-2 text-[11px] transition ${saveMode === "INSERT" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`}
                      >INSERT</button>
                      <button
                        type="button"
                        onClick={() => setSaveMode("UPSERT")}
                        disabled={!toTable}
                        className={`h-6 rounded px-2 text-[11px] transition ${saveMode === "UPSERT" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`}
                      >UPSERT</button>
                    </div>
                  </div>
                  <div className={`space-y-1 ${saveMode !== "UPSERT" ? "pointer-events-none opacity-50" : ""}`}>
                    <Label htmlFor="conflictKey" className="text-[11px]">충돌 기준 컬럼 (Conflict Key) {saveMode === "UPSERT" && <span className="text-destructive">*</span>}</Label>
                    <Select value={conflictKey} onValueChange={setConflictKey} disabled={saveMode !== "UPSERT"}>
                      <SelectTrigger className="h-7 text-xs">
                        <SelectValue placeholder={saveMode !== "UPSERT" ? "UPSERT 모드 선택" : "기준 컬럼 선택"} />
                      </SelectTrigger>
                      <SelectContent>
                        {toColumns.map((col) => (
                          <SelectItem key={col.column_name} value={col.column_name}>
                            {col.column_name}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                </div>

                {/* 3. 행 단위 제외 필터 — 특정 API 필드 값을 가진 행 동기화 제외 */}
                <div className={`rounded-md border bg-muted/30 p-2 ${!toTable ? "pointer-events-none opacity-50" : ""}`}>
                  <div className="flex items-center justify-between">
                    <div>
                      <Label className="text-[11px] font-medium">행 제외 필터</Label>
                      <p className="text-[10px] leading-tight text-muted-foreground">
                        지정한 API 필드 값에 해당하는 행을 동기화에서 제외합니다. (예: loginId = wace)
                      </p>
                    </div>
                    <Button
                      type="button"
                      size="sm"
                      variant="outline"
                      className="h-6 px-2 text-[11px]"
                      onClick={() => setRowFilterRules((prev) => [...prev, { column: "", op: "eq", value: "" }])}
                      disabled={!toTable}
                    >+ 규칙 추가</Button>
                  </div>
                  {rowFilterRules.length > 0 && (
                    <div className="mt-1.5 space-y-1">
                      {rowFilterRules.map((rule, idx) => (
                        <div key={idx} className="grid grid-cols-[1fr_70px_1fr_24px] items-center gap-1">
                          <Input
                            value={rule.column}
                            onChange={(e) => setRowFilterRules((prev) => prev.map((r, i) => i === idx ? { ...r, column: e.target.value } : r))}
                            placeholder="API 필드명 (예: loginId)"
                            className="h-7 text-xs"
                          />
                          <Select
                            value={rule.op}
                            onValueChange={(v) => setRowFilterRules((prev) => prev.map((r, i) => i === idx ? { ...r, op: v as "eq" | "neq" } : r))}
                          >
                            <SelectTrigger className="h-7 text-xs">
                              <SelectValue />
                            </SelectTrigger>
                            <SelectContent>
                              <SelectItem value="eq">같으면</SelectItem>
                              <SelectItem value="neq">다르면</SelectItem>
                            </SelectContent>
                          </Select>
                          <Input
                            value={rule.value}
                            onChange={(e) => setRowFilterRules((prev) => prev.map((r, i) => i === idx ? { ...r, value: e.target.value } : r))}
                            placeholder="제외 대상 값 (예: wace)"
                            className="h-7 text-xs"
                          />
                          <button
                            type="button"
                            onClick={() => setRowFilterRules((prev) => prev.filter((_, i) => i !== idx))}
                            className="flex h-7 w-6 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
                            aria-label="규칙 삭제"
                          >×</button>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* REST API 설정 (DB → REST API) */}
            {batchType === "db-to-restapi" && (
              <div className="space-y-4">
                {/* API 서버 URL */}
                <div>
                  <Label htmlFor="toApiUrl">API 서버 URL *</Label>
                  <Input
                    id="toApiUrl"
                    value={toApiUrl}
                    onChange={(e) => setToApiUrl(e.target.value)}
                    placeholder="https://api.example.com"
                  />
                </div>

                {/* API 키 */}
                <div>
                  <Label htmlFor="toApiKey">API 키 *</Label>
                  <Input
                    id="toApiKey"
                    value={toApiKey}
                    onChange={(e) => setToApiKey(e.target.value)}
                    placeholder="ak_your_api_key_here"
                  />
                </div>

                {/* 엔드포인트 */}
                <div>
                  <Label htmlFor="toEndpoint">엔드포인트 *</Label>
                  <Input
                    id="toEndpoint"
                    value={toEndpoint}
                    onChange={(e) => setToEndpoint(e.target.value)}
                    placeholder="/api/users"
                  />
                  {(toApiMethod === "PUT" || toApiMethod === "DELETE") && (
                    <p className="mt-1 text-xs text-muted-foreground">
                      실제 URL: {toEndpoint}/{urlPathColumn ? `{${urlPathColumn}}` : "{ID}"}
                    </p>
                  )}
                </div>

                {/* HTTP 메서드 */}
                <div>
                  <Label>HTTP 메서드</Label>
                  <Select value={toApiMethod} onValueChange={(value: any) => setToApiMethod(value)}>
                    <SelectTrigger>
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="POST">POST (데이터 생성)</SelectItem>
                      <SelectItem value="PUT">PUT (데이터 업데이트)</SelectItem>
                      <SelectItem value="DELETE">DELETE (데이터 삭제)</SelectItem>
                    </SelectContent>
                  </Select>
                </div>

                {/* URL 경로 파라미터 설정 (PUT/DELETE용) */}
                {(toApiMethod === "PUT" || toApiMethod === "DELETE") && (
                  <div>
                    <Label>URL 경로 파라미터 컬럼 *</Label>
                    <Select value={urlPathColumn} onValueChange={setUrlPathColumn}>
                      <SelectTrigger>
                        <SelectValue placeholder="URL 경로에 사용할 컬럼을 선택하세요" />
                      </SelectTrigger>
                      <SelectContent>
                        {selectedColumns.map((column) => (
                          <SelectItem key={column} value={column}>
                            {column} (예: /api/users/{`{${column}}`})
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                    <p className="mt-1 text-xs text-muted-foreground">
                      PUT/DELETE 요청 시 URL 경로에 포함될 컬럼을 선택하세요. (예: USER_ID → /api/users/user123)
                    </p>
                  </div>
                )}

                {/* TO API 미리보기 버튼 */}
                <div className="flex justify-center">
                  <button
                    type="button"
                    onClick={previewToApiData}
                    className="flex items-center space-x-2 rounded-md bg-emerald-600 px-4 py-2 text-white hover:bg-green-700"
                  >
                    <Eye className="h-4 w-4" />
                    <span>API 필드 미리보기</span>
                  </button>
                </div>

                {/* TO API 필드 표시 */}
                {toApiFields.length > 0 && (
                  <div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3">
                    <div className="mb-2 text-sm font-medium text-emerald-800">
                      API 필드 목록 ({toApiFields.length}개)
                    </div>
                    <div className="flex flex-wrap gap-2">
                      {toApiFields.map((field) => (
                        <span key={field} className="rounded bg-emerald-100 px-2 py-1 text-xs text-emerald-700">
                          {field}
                        </span>
                      ))}
                    </div>
                  </div>
                )}

                {/* Request Body 템플릿 */}
                {(toApiMethod === "POST" || toApiMethod === "PUT") && (
                  <div>
                    <Label htmlFor="toApiBody">Request Body 템플릿 (JSON)</Label>
                    <textarea
                      id="toApiBody"
                      value={toApiBody}
                      onChange={(e) => setToApiBody(e.target.value)}
                      placeholder='{"id": "{{id}}", "name": "{{name}}", "email": "{{email}}"}'
                      className="h-24 w-full rounded-md border p-2 font-mono text-sm"
                    />
                    <div className="mt-1 text-xs text-muted-foreground">
                      DB 컬럼 값을 {"{{컬럼명}}"} 형태로 매핑하세요. 예: {"{{user_id}}, {{user_name}}"}
                    </div>
                  </div>
                )}

                {/* API 호출 정보 */}
                {toApiUrl && toApiKey && toEndpoint && (
                  <div className="rounded-lg bg-muted p-3">
                    <div className="text-sm font-medium text-foreground">API 호출 정보</div>
                    <div className="mt-1 text-sm text-muted-foreground">
                      {toApiMethod} {toApiUrl}
                      {toEndpoint}
                    </div>
                    <div className="mt-1 text-xs text-muted-foreground">Headers: X-API-Key: {toApiKey.substring(0, 10)}...</div>
                    {toApiBody && (
                      <div className="mt-1 text-xs text-primary">Body: {toApiBody.substring(0, 50)}...</div>
                    )}
                  </div>
                )}
              </div>
            )}
          </CardContent>
        </Card>
      </div>

      {/* API 데이터 미리보기 버튼 - FROM/TO 섹션 아래 */}
      {batchType === "restapi-to-db" && (() => {
        const useRegistered = selectedRestApiId !== "manual";
        // 등록된 연결이면 toTable 만 필요, 직접 입력이면 URL/엔드포인트도 필요
        const fromOk = useRegistered || (fromApiUrl && fromEndpoint);
        const disabled = !fromOk || !toTable;
        return (
          <div className="flex justify-center">
            <Button
              onClick={previewRestApiData}
              variant="outline"
              disabled={disabled}
              className="gap-2"
            >
              <RefreshCw className="h-4 w-4" />
              데이터 불러오고 매핑하기
            </Button>
            {disabled && (
              <p className="ml-4 flex items-center text-xs text-muted-foreground">
                {useRegistered
                  ? "TO: 데이터베이스의 커넥션·테이블을 선택하세요."
                  : "FROM 섹션과 TO 섹션의 필수 값을 모두 입력해야 합니다."}
              </p>
            )}
          </div>
        );
      })()}

      {/* 매핑 UI - 배치 타입별 동적 렌더링 */}
      {/* REST API → DB 매핑 */}
      {batchType === "restapi-to-db" && fromApiFields.length > 0 && toColumns.length > 0 && (
        <RestApiToDbMappingCard
          fromApiFields={fromApiFields}
          toColumns={toColumns}
          fromApiData={fromApiData}
          mappingList={mappingList}
          setMappingList={setMappingList}
        />
      )}

      {/* DB → REST API 매핑 */}
      {batchType === "db-to-restapi" && selectedColumns.length > 0 && toApiFields.length > 0 && (
        <DbToRestApiMappingCard
          fromColumns={fromColumns}
          selectedColumns={selectedColumns}
          toApiFields={toApiFields}
          dbToApiFieldMapping={dbToApiFieldMapping}
          setDbToApiFieldMapping={setDbToApiFieldMapping}
          setToApiBody={setToApiBody}
        />
      )}

      {/* 하단 액션 버튼 — 헤더 우측에도 있지만, 스크롤 끝에서 다시 한 번 노출 */}
      <div className="flex items-center justify-end gap-2 border-t pt-4">
        <Button onClick={goBack} variant="outline" size="sm" className="h-8 gap-1 text-xs">취소</Button>
        <Button onClick={loadConnections} variant="outline" size="sm" className="h-8 gap-1 text-xs">
          <RefreshCw className="h-3.5 w-3.5" />
          새로고침
        </Button>
        <Button onClick={handleSave} size="sm" className="h-8 gap-1 text-xs">
          <Save className="h-3.5 w-3.5" />
          {isEditMode ? "수정" : "저장"}
        </Button>
      </div>
      </div>
    </div>
  );
}

/**
 * 조건 변환 편집기 — API 필드값에 따라 다른 값으로 매핑
 *  예: enrlFg 가 "J01" → "active",  "J05" → "inactive",  나머지 → ""
 */
function ConditionalEditor({
  mapping,
  fromApiFields,
  onApiFieldChange,
  onConfigChange,
}: {
  mapping: MappingItem;
  fromApiFields: string[];
  onApiFieldChange: (v: string) => void;
  onConfigChange: (cfg: ConditionalConfig) => void;
}) {
  const cfg = mapping.conditionalConfig || { rules: [{ when: "", then: "" }], default: "" };
  const isApiFieldMissing = !mapping.apiField;
  const updateRule = (idx: number, patch: Partial<ConditionalRule>) => {
    const rules = cfg.rules.map((r, i) => (i === idx ? { ...r, ...patch } : r));
    onConfigChange({ ...cfg, rules });
  };
  const addRule = () => onConfigChange({ ...cfg, rules: [...cfg.rules, { when: "", then: "" }] });
  const removeRule = (idx: number) =>
    onConfigChange({ ...cfg, rules: cfg.rules.filter((_, i) => i !== idx) });
  return (
    <div className="space-y-1.5 rounded border bg-muted/30 p-2">
      <div className="space-y-0.5">
        <div className="flex items-center gap-1.5">
          <span className="shrink-0 text-[10px] font-medium text-muted-foreground">평가 필드 <span className="text-destructive">*</span></span>
          <Select value={mapping.apiField || "none"} onValueChange={(v) => onApiFieldChange(v === "none" ? "" : v)}>
            <SelectTrigger className={cn(
              "h-7 text-xs",
              isApiFieldMissing && "border-destructive ring-1 ring-destructive/40",
            )}>
              <SelectValue placeholder="조건을 평가할 API 필드 선택 (필수)" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="none">선택 안함</SelectItem>
              {/* 저장된 apiField 가 fromApiFields 옵션에 없을 때 동적 추가 (응답 미리보기 안 한 편집 모드 대응) */}
              {mapping.apiField && !fromApiFields.includes(mapping.apiField) && (
                <SelectItem value={mapping.apiField}>{mapping.apiField} (저장값)</SelectItem>
              )}
              {fromApiFields.map((f) => (
                <SelectItem key={f} value={f}>{f}</SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
        <p className="pl-[60px] text-[10px] text-muted-foreground">
          이 필드의 값에 따라 아래 규칙으로 변환됩니다 (예: status 컬럼에 <span className="font-mono">enrlFg</span> 의 값을 보고 J01→active 변환 시 평가 필드 = <span className="font-mono">enrlFg</span>)
        </p>
      </div>
      <div className="space-y-1">
        {cfg.rules.map((rule, idx) => (
          <div key={idx} className="flex items-center gap-1">
            <span className="shrink-0 text-[10px] text-muted-foreground">값이</span>
            <Input
              value={rule.when}
              onChange={(e) => updateRule(idx, { when: e.target.value })}
              placeholder="예: J01"
              className="h-7 flex-1 text-xs"
            />
            <span className="shrink-0 text-[10px] text-muted-foreground">→</span>
            <Input
              value={rule.then}
              onChange={(e) => updateRule(idx, { then: e.target.value })}
              placeholder="저장값"
              className="h-7 flex-1 text-xs"
            />
            <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => removeRule(idx)}>
              <Trash2 className="h-3 w-3" />
            </Button>
          </div>
        ))}
        <Button variant="outline" size="sm" onClick={addRule} className="h-6 gap-1 px-2 text-[10px]">
          <Plus className="h-3 w-3" />
          규칙 추가
        </Button>
      </div>
      <div className="flex items-center gap-1 border-t pt-1">
        <span className="shrink-0 text-[10px] text-muted-foreground">매칭 없음(기본)</span>
        <Input
          value={cfg.default}
          onChange={(e) => onConfigChange({ ...cfg, default: e.target.value })}
          placeholder="예: 0 또는 빈값"
          className="h-7 flex-1 text-xs"
        />
      </div>
    </div>
  );
}

const RestApiToDbMappingCard = memo(function RestApiToDbMappingCard({
  fromApiFields,
  toColumns,
  fromApiData,
  mappingList,
  setMappingList,
}: RestApiToDbMappingCardProps) {
  // 샘플 JSON 문자열
  const sampleJsonList = useMemo(
    () => fromApiData.slice(0, 3).map((item) => JSON.stringify(item, null, 2)),
    [fromApiData],
  );

  const firstSample = fromApiData[0] || null;

  // 이미 매핑된 DB 컬럼들
  const mappedDbColumns = useMemo(() => mappingList.map((m) => m.dbColumn).filter(Boolean), [mappingList]);

  // 매핑 추가
  const addMapping = () => {
    const newId = `mapping-${Date.now()}`;
    setMappingList((prev) => [
      ...prev,
      {
        id: newId,
        dbColumn: "",
        sourceType: "api",
        apiField: "",
        fixedValue: "",
      },
    ]);
  };

  // 매핑 삭제
  const removeMapping = (id: string) => {
    setMappingList((prev) => prev.filter((m) => m.id !== id));
  };

  // 매핑 업데이트
  const updateMapping = (id: string, updates: Partial<MappingItem>) => {
    setMappingList((prev) => prev.map((m) => (m.id === id ? { ...m, ...updates } : m)));
  };

  return (
    <Card>
      <CardHeader className="px-3 pb-2 pt-3">
        <CardTitle className="text-sm">컬럼 매핑 설정</CardTitle>
        <CardDescription className="text-[11px]">DB 컬럼에 API 필드 또는 고정값을 매핑합니다.</CardDescription>
      </CardHeader>
      <CardContent className="px-3 pb-3 pt-0">
        <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
          {/* 왼쪽: 샘플 데이터 */}
          <div className="flex flex-col">
            <div className="mb-2 flex h-7 items-center">
              <h4 className="text-sm font-semibold">샘플 데이터 (최대 3개)</h4>
            </div>
            {sampleJsonList.length > 0 ? (
              <div className="bg-muted/30 h-[360px] overflow-y-auto rounded-lg border p-3">
                <div className="space-y-2">
                  {sampleJsonList.map((json, index) => (
                    <div key={index} className="bg-background rounded border p-2">
                      <pre className="font-mono text-xs whitespace-pre-wrap">{json}</pre>
                    </div>
                  ))}
                </div>
              </div>
            ) : (
              <div className="flex h-[360px] items-center justify-center rounded-lg border border-dashed">
                <p className="text-muted-foreground text-sm">
                  API 데이터 미리보기를 실행하면 샘플 데이터가 표시됩니다.
                </p>
              </div>
            )}
          </div>

          {/* 오른쪽: 매핑 영역 (스크롤) */}
          <div className="flex flex-col">
            <div className="mb-3 flex h-8 items-center justify-between">
              <h4 className="text-sm font-semibold">매핑 설정</h4>
              <Button variant="outline" size="sm" onClick={addMapping} className="h-8 gap-1">
                <Plus className="h-4 w-4" />
                매핑 추가
              </Button>
            </div>

            {mappingList.length === 0 ? (
              <div className="flex h-[360px] flex-col items-center justify-center rounded-lg border border-dashed text-center">
                <p className="text-muted-foreground text-sm">매핑이 없습니다.</p>
                <Button variant="link" onClick={addMapping} className="mt-2">
                  매핑 추가하기
                </Button>
              </div>
            ) : (
              <div className="bg-muted/30 h-[360px] space-y-3 overflow-y-auto rounded-lg border p-3">
                {mappingList.map((mapping, index) => (
                  <div key={mapping.id} className="bg-background flex items-center gap-2 rounded-lg border p-3">
                    {/* 순서 표시 */}
                    <div className="bg-primary/10 text-primary flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-medium">
                      {index + 1}
                    </div>

                    {/* DB 컬럼 선택 (좌측 - TO) */}
                    <div className="w-36 shrink-0">
                      <Select
                        value={mapping.dbColumn || "none"}
                        onValueChange={(value) =>
                          updateMapping(mapping.id, { dbColumn: value === "none" ? "" : value })
                        }
                      >
                        <SelectTrigger className="h-9">
                          <SelectValue placeholder="DB 컬럼" />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="none">선택 안함</SelectItem>
                          {toColumns.map((col) => {
                            const isUsed =
                              mappedDbColumns.includes(col.column_name) && mapping.dbColumn !== col.column_name;
                            return (
                              <SelectItem key={col.column_name} value={col.column_name} disabled={isUsed}>
                                <div className="flex items-center gap-2">
                                  <span className={isUsed ? "text-muted-foreground" : ""}>{col.column_name}</span>
                                  <span className="text-muted-foreground text-xs">({col.data_type})</span>
                                </div>
                              </SelectItem>
                            );
                          })}
                        </SelectContent>
                      </Select>
                    </div>

                    {/* 화살표 */}
                    <ArrowLeft className="text-muted-foreground h-4 w-4 shrink-0" />

                    {/* 소스 타입 선택 */}
                    <div className="w-28 shrink-0">
                      <Select
                        value={mapping.sourceType}
                        onValueChange={(value: "api" | "fixed" | "conditional") =>
                          updateMapping(mapping.id, {
                            sourceType: value,
                            // 모드 전환 시 입력값 정리
                            apiField:
                              value === "api" || value === "conditional"
                                ? mapping.apiField
                                : "",
                            fixedValue: value === "fixed" ? mapping.fixedValue : "",
                            conditionalConfig:
                              value === "conditional"
                                ? mapping.conditionalConfig || { rules: [{ when: "", then: "" }], default: "" }
                                : mapping.conditionalConfig,
                          })
                        }
                      >
                        <SelectTrigger className="h-9">
                          <SelectValue />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="api">API 필드</SelectItem>
                          <SelectItem value="fixed">고정값</SelectItem>
                          <SelectItem value="conditional">조건 변환</SelectItem>
                        </SelectContent>
                      </Select>
                    </div>

                    {/* API 필드 선택 / 고정값 입력 / 조건 변환 (우측 - FROM) */}
                    <div className="min-w-0 flex-1">
                      {mapping.sourceType === "api" && (
                        <Select
                          value={mapping.apiField || "none"}
                          onValueChange={(value) =>
                            updateMapping(mapping.id, { apiField: value === "none" ? "" : value })
                          }
                        >
                          <SelectTrigger className="h-9">
                            <SelectValue placeholder="API 필드" />
                          </SelectTrigger>
                          <SelectContent>
                            <SelectItem value="none">선택 안함</SelectItem>
                            {fromApiFields.map((field) => (
                              <SelectItem key={field} value={field}>
                                <div className="flex items-center gap-2">
                                  <span>{field}</span>
                                  {firstSample && firstSample[field] !== undefined && (
                                    <span className="text-muted-foreground text-xs">
                                      (예: {String(firstSample[field]).substring(0, 15)}
                                      {String(firstSample[field]).length > 15 ? "..." : ""})
                                    </span>
                                  )}
                                </div>
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </Select>
                      )}
                      {mapping.sourceType === "fixed" && (
                        <Input
                          value={mapping.fixedValue}
                          onChange={(e) => updateMapping(mapping.id, { fixedValue: e.target.value })}
                          placeholder="고정값 입력"
                          className="h-9"
                        />
                      )}
                      {mapping.sourceType === "conditional" && (
                        <ConditionalEditor
                          mapping={mapping}
                          fromApiFields={fromApiFields}
                          onApiFieldChange={(v) => updateMapping(mapping.id, { apiField: v })}
                          onConfigChange={(cfg) =>
                            updateMapping(mapping.id, { conditionalConfig: cfg })
                          }
                        />
                      )}
                    </div>

                    {/* 삭제 버튼 */}
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => removeMapping(mapping.id)}
                      className="text-muted-foreground hover:text-destructive h-8 w-8 shrink-0"
                    >
                      <Trash2 className="h-4 w-4" />
                    </Button>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </CardContent>
    </Card>
  );
});

const DbToRestApiMappingCard = memo(function DbToRestApiMappingCard({
  fromColumns,
  selectedColumns,
  toApiFields,
  dbToApiFieldMapping,
  setDbToApiFieldMapping,
  setToApiBody,
}: DbToRestApiMappingCardProps) {
  const selectedColumnObjects = useMemo(
    () => fromColumns.filter((column) => selectedColumns.includes(column.column_name)),
    [fromColumns, selectedColumns],
  );

  const autoJsonPreview = useMemo(() => {
    if (selectedColumns.length === 0) {
      return "";
    }
    const obj = selectedColumns.reduce(
      (acc, col) => {
        const apiField = dbToApiFieldMapping[col] || col;
        acc[apiField] = `{{${col}}}`;
        return acc;
      },
      {} as Record<string, string>,
    );
    return JSON.stringify(obj, null, 2);
  }, [selectedColumns, dbToApiFieldMapping]);

  return (
    <Card>
      <CardHeader className="px-3 pb-2 pt-3">
        <CardTitle className="text-sm">DB 컬럼 → API 필드 매핑</CardTitle>
        <CardDescription className="text-[11px]">
          DB 컬럼 값을 REST API Request Body에 매핑하세요. Request Body 템플릿에서 {"{{컬럼명}}"} 형태로 사용됩니다.
        </CardDescription>
      </CardHeader>
      <CardContent className="px-3 pb-3 pt-0">
        <div className="max-h-96 space-y-2 overflow-y-auto rounded-lg border p-2">
          {selectedColumnObjects.map((column) => (
            <div key={column.column_name} className="flex items-center space-x-4 rounded-lg bg-muted p-3">
              {/* DB 컬럼 정보 */}
              <div className="flex-1">
                <div className="text-sm font-medium">{column.column_name}</div>
                <div className="text-xs text-muted-foreground">
                  타입: {column.data_type} | NULL: {column.is_nullable ? "Y" : "N"}
                </div>
              </div>

              {/* 화살표 */}
              <div className="text-muted-foreground/70">→</div>

              {/* API 필드 선택 드롭다운 */}
              <div className="flex-1">
                <Select
                  value={dbToApiFieldMapping[column.column_name] || ""}
                  onValueChange={(value) => {
                    setDbToApiFieldMapping((prev) => ({
                      ...prev,
                      [column.column_name]: value === "none" ? "" : value,
                    }));
                  }}
                >
                  <SelectTrigger className="w-full">
                    <SelectValue placeholder="API 필드 선택" />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="none">선택 안함</SelectItem>
                    {toApiFields.map((apiField) => (
                      <SelectItem key={apiField} value={apiField}>
                        {apiField}
                      </SelectItem>
                    ))}
                    <SelectItem value="custom">직접 입력...</SelectItem>
                  </SelectContent>
                </Select>

                {/* 직접 입력 모드 */}
                {dbToApiFieldMapping[column.column_name] === "custom" && (
                  <input
                    type="text"
                    placeholder="API 필드명을 직접 입력하세요"
                    className="mt-2 w-full rounded-md border border-input px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:outline-none"
                    onChange={(e) => {
                      setDbToApiFieldMapping((prev) => ({
                        ...prev,
                        [column.column_name]: e.target.value,
                      }));
                    }}
                  />
                )}

                <div className="mt-1 text-xs text-muted-foreground">
                  {dbToApiFieldMapping[column.column_name]
                    ? `매핑: ${column.column_name} → ${dbToApiFieldMapping[column.column_name]}`
                    : `기본값: ${column.column_name} (DB 컬럼명 사용)`}
                </div>
              </div>

              {/* 템플릿 미리보기 */}
              <div className="flex-1">
                <div className="rounded border bg-white p-2 font-mono text-sm">{`{{${column.column_name}}}`}</div>
                <div className="mt-1 text-xs text-muted-foreground">실제 DB 값으로 치환됩니다</div>
              </div>
            </div>
          ))}
        </div>

        {selectedColumns.length > 0 && (
          <div className="mt-4 rounded-lg border border-primary/20 bg-primary/10 p-3">
            <div className="text-sm font-medium text-primary">자동 생성된 JSON 구조</div>
            <pre className="mt-1 overflow-x-auto font-mono text-xs text-primary">{autoJsonPreview}</pre>
            <button
              type="button"
              onClick={() => {
                setToApiBody(autoJsonPreview);
                toast.success("Request Body에 자동 생성된 JSON이 적용되었습니다.");
              }}
              className="mt-2 rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
            >
              Request Body에 적용
            </button>
          </div>
        )}

        <div className="mt-4 rounded-lg border border-primary/20 bg-primary/10 p-3">
          <div className="text-sm font-medium text-primary">매핑 사용 예시</div>
          <div className="mt-1 font-mono text-xs text-primary">
            {'{"id": "{{id}}", "name": "{{user_name}}", "email": "{{email}}"}'}
          </div>
        </div>
      </CardContent>
    </Card>
  );
});
