"use client";

/**
 * InvLegacyButtonConfigPanel — 버튼 (v2-button-primary) cp 톤 설정 패널
 *
 * 흐름:
 *   ① 액션 유형 (CPVisualGrid 14종 시각 카드)
 *   ② 표시 모드 (CPVisualGrid 3종 — text/icon/icon-text)
 *   ③ 텍스트 + 변형 (CPSegment)
 *   ④ 액션별 세부 설정 (FormatBody 디스패처 — 10+ case)
 *   ▾ 아이콘 설정 (조건부 — icon/icon-text 일 때)
 *   ▾ 고급 설정 (행 선택 / 데이터 매핑 / 제어 흐름 / 흐름 가시성)
 *
 * V2ButtonConfigPanel(2212줄) 폐기 → cp 톤 신규.
 *
 * Reference: notes/gbpark/2026-04-28-cp-panel-standard.md
 */

import React, { useState, useEffect, useMemo, useCallback } from "react";
import {
  Save,
  Trash2,
  Pencil,
  ArrowRight,
  Maximize2,
  SendHorizontal,
  Download,
  Upload,
  Check,
  Plus,
  X,
  Type,
  Image as ImageIcon,
  Columns,
  ScanLine,
  Truck,
  Send,
  Copy,
  FileSpreadsheet,
  Settings,
  Workflow,
  Info,
} from "lucide-react";
import { icons as allLucideIcons } from "lucide-react";
import {
  CPSection,
  CPRow,
  CPGroup,
  CPText,
  CPSelect,
  CPSwitch,
  CPSegment,
  CPNumber,
  CPIconBtn,
  CPVisualGrid,
  FeatureChipGrid,
  Hint,
  SectionLabel,
  InlineLoader,
} from "./_shared/cp";
import { apiClient } from "@/lib/api/client";
import {
  actionIconMap,
  noIconActions,
  NO_ICON_MESSAGE,
  iconSizePresets,
  getLucideIcon,
  getDefaultIconForAction,
  sanitizeSvg,
} from "@/lib/button-icon-map";
import { ImprovedButtonControlConfigPanel } from "@/components/screen/config-panels/ImprovedButtonControlConfigPanel";
import { FlowVisibilityConfigPanel } from "@/components/screen/config-panels/FlowVisibilityConfigPanel";
import type { ComponentData } from "@/types/screen";
import { isTableLikeComponentType } from "@/lib/utils/componentTypeUtils";

// ───────────────────────────────────────────────────────
// 상수: 액션 / 표시 / 변형
// ───────────────────────────────────────────────────────
const ACTION_TYPE_CARDS = [
  { value: "save", icon: Save, title: "저장", desc: "데이터 저장" },
  { value: "delete", icon: Trash2, title: "삭제", desc: "데이터 삭제" },
  { value: "edit", icon: Pencil, title: "편집", desc: "데이터 수정" },
  { value: "modal", icon: Maximize2, title: "모달", desc: "팝업 열기" },
  { value: "navigate", icon: ArrowRight, title: "이동", desc: "다른 화면" },
  { value: "transferData", icon: SendHorizontal, title: "전달", desc: "다른 테이블로" },
  { value: "excel_download", icon: Download, title: "엑셀 ⇣", desc: "엑셀 다운" },
  { value: "excel_upload", icon: Upload, title: "엑셀 ⇡", desc: "엑셀 업로드" },
  { value: "approval", icon: Check, title: "결재", desc: "결재 요청" },
  { value: "control", icon: Settings, title: "제어", desc: "흐름 제어" },
  { value: "copy", icon: Copy, title: "복사", desc: "데이터 복사" },
  { value: "barcode_scan", icon: ScanLine, title: "바코드", desc: "스캔" },
  { value: "operation_control", icon: Truck, title: "운행", desc: "운행 관리" },
  { value: "multi_table_excel_upload", icon: FileSpreadsheet, title: "다중 ⇡", desc: "여러 테이블" },
] as const;

const DISPLAY_MODE_CARDS = [
  { value: "text", icon: Type, title: "텍스트" },
  { value: "icon", icon: ImageIcon, title: "아이콘" },
  { value: "icon-text", icon: Columns, title: "아이콘+텍스트" },
] as const;

const VARIANT_OPTIONS = [
  { value: "primary", label: "기본" },
  { value: "secondary", label: "보조" },
  { value: "danger", label: "위험" },
] as const;

const MODAL_SIZE_OPTIONS = [
  { value: "sm", label: "작게" },
  { value: "md", label: "보통" },
  { value: "lg", label: "크게" },
  { value: "xl", label: "더 크게" },
  { value: "full", label: "전체" },
] as const;

// canonical table / legacy table-list / hidden v2-table-list / data-table / datatable
// 은 table-like helper 로 통일. 추가로 repeater-field-group / form-group 도 데이터 전송
// 호환 대상으로 함께 인식.
const DATA_TRANSFER_EXTRA_PATTERNS = ["repeater-field-group", "form-group"] as const;
const isDataTransferComponentType = (typeValue: unknown): boolean => {
  if (isTableLikeComponentType(typeValue)) return true;
  if (typeof typeValue !== "string") return false;
  return DATA_TRANSFER_EXTRA_PATTERNS.some((t) => typeValue.includes(t));
};

const TRANSFER_MODE_OPTIONS = [
  { value: "append", label: "추가" },
  { value: "replace", label: "교체" },
  { value: "merge", label: "병합" },
] as const;

const ICON_TEXT_POSITION_OPTIONS = [
  { value: "left", label: "왼쪽" },
  { value: "right", label: "오른쪽" },
  { value: "top", label: "위" },
  { value: "bottom", label: "아래" },
] as const;

interface ScreenOption {
  id: number;
  name: string;
  description?: string;
}

interface InvLegacyButtonConfigPanelProps {
  config: Record<string, any>;
  onChange: (config: Record<string, any>) => void;
  component?: ComponentData;
  currentComponent?: ComponentData;
  onUpdateProperty?: (path: string, value: any) => void;
  allComponents?: ComponentData[];
  currentTableName?: string;
  screenTableName?: string;
  currentScreenCompanyCode?: string;
  [key: string]: any;
}

// ───────────────────────────────────────────────────────
// Main Component
// ───────────────────────────────────────────────────────
export const InvLegacyButtonConfigPanel: React.FC<InvLegacyButtonConfigPanelProps> = ({
  config,
  onChange,
  component,
  currentComponent,
  allComponents = [],
  currentTableName,
  screenTableName,
}) => {
  const effectiveComponent = component || currentComponent;
  const effectiveTableName = currentTableName || screenTableName;

  const actionType = String(config.action?.type || "save");
  const displayMode = (config.displayMode as "text" | "icon" | "icon-text") || "text";
  const variant = config.variant || "primary";
  const buttonText = config.text !== undefined ? config.text : "버튼";

  // ── State ────────────────────────────────────────────
  const [selectedIcon, setSelectedIcon] = useState<string>(config.icon?.name || "");
  const [selectedIconType, setSelectedIconType] = useState<"lucide" | "svg">(
    config.icon?.type || "lucide",
  );
  const [iconSize, setIconSize] = useState<string>(config.icon?.size || "보통");

  const [screens, setScreens] = useState<ScreenOption[]>([]);
  const [screensLoading, setScreensLoading] = useState(false);

  const [availableTables, setAvailableTables] = useState<Array<{ name: string; label: string }>>(
    [],
  );
  const [mappingSourceColumnsMap, setMappingSourceColumnsMap] = useState<
    Record<string, Array<{ name: string; label: string }>>
  >({});
  const [mappingTargetColumns, setMappingTargetColumns] = useState<
    Array<{ name: string; label: string }>
  >([]);
  const [activeMappingGroupIndex, setActiveMappingGroupIndex] = useState(0);

  const [lucideSearchTerm, setLucideSearchTerm] = useState("");
  const [svgInput, setSvgInput] = useState("");
  const [svgName, setSvgName] = useState("");
  const [svgError, setSvgError] = useState("");

  const showIconSettings = displayMode === "icon" || displayMode === "icon-text";
  const currentActionIcons = actionIconMap[actionType] || [];
  const isNoIconAction = noIconActions.has(actionType);
  const customIcons: string[] = config.customIcons || [];
  const customSvgIcons: Array<{ name: string; svg: string }> = config.customSvgIcons || [];

  const hasFlowWidget = useMemo(() => {
    return allComponents.some((comp: any) => {
      const compType = comp.componentType || comp.widgetType || "";
      return compType === "flow-widget" || compType?.toLowerCase().includes("flow");
    });
  }, [allComponents]);

  // ── Helpers ──────────────────────────────────────────
  const updateConfig = useCallback(
    (field: string, value: any) => {
      onChange({ ...config, [field]: value });
    },
    [config, onChange],
  );

  const updateActionConfig = useCallback(
    (field: string, value: any) => {
      const currentAction = config.action || {};
      onChange({ ...config, action: { ...currentAction, [field]: value } });
    },
    [config, onChange],
  );

  const updateAction = useCallback(
    (key: string, value: boolean) => updateActionConfig(key, value),
    [updateActionConfig],
  );

  const handleUpdateProperty = useCallback(
    (path: string, value: any) => {
      const normalized = path.replace(/^componentConfig\./, "").replace(/^webTypeConfig\./, "");
      const parts = normalized.split(".");
      const newConfig = { ...config };
      let current: any = newConfig;
      for (let i = 0; i < parts.length - 1; i++) {
        if (!current[parts[i]]) current[parts[i]] = {};
        current[parts[i]] = { ...current[parts[i]] };
        current = current[parts[i]];
      }
      current[parts[parts.length - 1]] = value;
      onChange(newConfig);
    },
    [config, onChange],
  );

  useEffect(() => {
    setSelectedIcon(config.icon?.name || "");
    setSelectedIconType(config.icon?.type || "lucide");
    setIconSize(config.icon?.size || "보통");
  }, [config.icon?.name, config.icon?.type, config.icon?.size]);

  // 화면 목록 로드 (modal/edit/navigate)
  useEffect(() => {
    if (actionType !== "modal" && actionType !== "navigate" && actionType !== "edit") return;
    if (screens.length > 0) return;
    const load = async () => {
      setScreensLoading(true);
      try {
        const r = await apiClient.get("/screen-management/screens?size=1000");
        if (r.data.success && r.data.data) {
          setScreens(
            r.data.data.map((s: any) => ({
              id: s.id || s.screen_id,
              name: s.name || s.screen_name,
              description: s.description || "",
            })),
          );
        }
      } catch {
        setScreens([]);
      } finally {
        setScreensLoading(false);
      }
    };
    load();
  }, [actionType, screens.length]);

  // 테이블 목록 로드 (transferData)
  useEffect(() => {
    if (actionType !== "transferData") return;
    if (availableTables.length > 0) return;
    const load = async () => {
      try {
        const r = await apiClient.get("/table-management/tables");
        if (r.data.success && r.data.data) {
          setAvailableTables(
            r.data.data.map((t: any) => ({
              name: t.table_name || t.name,
              label: t.display_name || t.table_label || t.label || t.table_name || t.name,
            })),
          );
        }
      } catch {
        setAvailableTables([]);
      }
    };
    load();
  }, [actionType, availableTables.length]);

  const loadTableColumns = useCallback(
    async (tableName: string): Promise<Array<{ name: string; label: string }>> => {
      try {
        const r = await apiClient.get(`/table-management/tables/${tableName}/columns`);
        if (r.data.success) {
          let data = r.data.data;
          if (!Array.isArray(data) && data?.columns) data = data.columns;
          if (!Array.isArray(data) && data?.data) data = data.data;
          if (Array.isArray(data)) {
            return data.map((c: any) => ({
              name: c.name || c.column_name,
              label: c.display_name || c.label || c.column_label || c.name || c.column_name,
            }));
          }
        }
      } catch {
        /* ignore */
      }
      return [];
    },
    [],
  );

  // 멀티 테이블 매핑: 소스/타겟 컬럼 로드
  useEffect(() => {
    if (actionType !== "transferData") return;
    const multiTableMappings: Array<{ sourceTable: string }> =
      config.action?.dataTransfer?.multiTableMappings || [];
    const targetTable = config.action?.dataTransfer?.targetTable;

    const loadAll = async () => {
      const sourceTableNames = multiTableMappings.map((m) => m.sourceTable).filter(Boolean);
      const newMap: Record<string, Array<{ name: string; label: string }>> = {};
      for (const tbl of sourceTableNames) {
        if (!mappingSourceColumnsMap[tbl]) {
          newMap[tbl] = await loadTableColumns(tbl);
        }
      }
      if (Object.keys(newMap).length > 0) {
        setMappingSourceColumnsMap((prev) => ({ ...prev, ...newMap }));
      }
      if (targetTable) {
        const cols = await loadTableColumns(targetTable);
        setMappingTargetColumns(cols);
      } else {
        setMappingTargetColumns([]);
      }
    };
    loadAll();
  }, [
    actionType,
    config.action?.dataTransfer?.multiTableMappings,
    config.action?.dataTransfer?.targetTable,
    loadTableColumns,
  ]);

  const handleSelectIcon = (iconName: string, iconType: "lucide" | "svg" = "lucide") => {
    setSelectedIcon(iconName);
    setSelectedIconType(iconType);
    updateConfig("icon", { name: iconName, type: iconType, size: iconSize });
  };

  const revertToDefaultIcon = () => {
    const def = getDefaultIconForAction(actionType);
    handleSelectIcon(def.name, def.type);
  };

  const handleActionTypeChange = (newType: string) => {
    const currentAction = config.action || {};
    onChange({ ...config, action: { ...currentAction, type: newType } });
    const newActionIcons = actionIconMap[newType] || [];
    if (
      selectedIcon &&
      selectedIconType === "lucide" &&
      !newActionIcons.includes(selectedIcon) &&
      !customIcons.includes(selectedIcon)
    ) {
      setSelectedIcon("");
      updateConfig("icon", undefined);
    }
  };

  const componentData = useMemo(() => {
    if (effectiveComponent) {
      return {
        ...effectiveComponent,
        componentConfig: config,
        webTypeConfig: config,
      } as unknown as ComponentData;
    }
    return {
      id: "virtual",
      type: "widget" as const,
      position: { x: 0, y: 0 },
      size: { width: 120, height: 40 },
      componentConfig: config,
      webTypeConfig: config,
      componentType: "v2-button-primary",
    } as unknown as ComponentData;
  }, [effectiveComponent, config]);

  // ── Render ───────────────────────────────────────────
  return (
    <div style={{ fontFamily: "var(--v5-font-sans)", color: "var(--cp-text)", padding: "0 12px" }}>
      {/* ── ① 액션 유형 ─────────────────────────── */}
      <CPSection title="① 액션 유형" desc="이 버튼이 무엇을 하는가">
        <CPVisualGrid
          cols={3}
          cardHeight={66}
          value={actionType}
          onChange={(v) => handleActionTypeChange(v)}
          options={ACTION_TYPE_CARDS.map((c) => {
            const Icon = c.icon;
            return {
              value: c.value,
              label: c.title,
              desc: c.desc,
              preview: <Icon size={16} />,
            };
          })}
        />
      </CPSection>

      {/* ── ② 표시 모드 ─────────────────────────── */}
      <CPSection title="② 표시 모드" desc="버튼 형태">
        <CPVisualGrid
          cols={3}
          cardHeight={54}
          value={displayMode}
          onChange={(v) => {
            updateConfig("displayMode", v);
            if ((v === "icon" || v === "icon-text") && !selectedIcon) {
              revertToDefaultIcon();
            }
          }}
          options={DISPLAY_MODE_CARDS.map((c) => {
            const Icon = c.icon;
            return { value: c.value, label: c.title, preview: <Icon size={14} /> };
          })}
        />
      </CPSection>

      {/* ── ③ 텍스트 + 변형 ─────────────────────────── */}
      <CPSection title="③ 텍스트 / 스타일">
        {(displayMode === "text" || displayMode === "icon-text") && (
          <CPRow label="버튼 텍스트">
            <CPText
              value={buttonText}
              onChange={(v) => updateConfig("text", v)}
              placeholder="버튼"
            />
          </CPRow>
        )}
        <CPRow label="스타일">
          <CPSegment
            value={variant}
            onChange={(v) => updateConfig("variant", v)}
            options={VARIANT_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
          />
        </CPRow>
      </CPSection>

      {/* ── ④ 액션별 세부 설정 ─────────────────────────── */}
      <CPSection title="④ 액션별 설정" desc={describeAction(actionType)}>
        <ActionDetailBody
          actionType={actionType}
          config={config}
          updateActionConfig={updateActionConfig}
          screens={screens}
          screensLoading={screensLoading}
          allComponents={allComponents}
        />
      </CPSection>

      {/* ── ▾ 아이콘 설정 ─────────────────────────── */}
      {showIconSettings && (
        <CPGroup title="아이콘 설정" defaultOpen={false}>
          <IconOptionsBody
            actionType={actionType}
            isNoIconAction={isNoIconAction}
            currentActionIcons={currentActionIcons}
            selectedIcon={selectedIcon}
            selectedIconType={selectedIconType}
            iconSize={iconSize}
            displayMode={displayMode}
            iconTextPosition={config.iconTextPosition || "right"}
            iconGap={config.iconGap ?? 6}
            customIcons={customIcons}
            customSvgIcons={customSvgIcons}
            lucideSearchTerm={lucideSearchTerm}
            setLucideSearchTerm={setLucideSearchTerm}
            svgInput={svgInput}
            setSvgInput={setSvgInput}
            svgName={svgName}
            setSvgName={setSvgName}
            svgError={svgError}
            setSvgError={setSvgError}
            onSelectIcon={handleSelectIcon}
            onRevertToDefault={revertToDefaultIcon}
            onIconSizeChange={(preset) => {
              setIconSize(preset);
              if (selectedIcon) updateConfig("icon", { ...config.icon, size: preset });
            }}
            onIconTextPositionChange={(pos) => updateConfig("iconTextPosition", pos)}
            onIconGapChange={(gap) => updateConfig("iconGap", gap)}
            onCustomIconsChange={(icons) => updateConfig("customIcons", icons)}
            onCustomSvgIconsChange={(icons) => updateConfig("customSvgIcons", icons)}
          />
        </CPGroup>
      )}

      {/* ── ▾ 고급 설정 ─────────────────────────── */}
      <CPGroup title="고급 설정" defaultOpen={false}>
        {/* 행 선택 활성화 */}
        <CPRow label="행 선택 활성화" help="행을 선택해야만 버튼이 활성화">
          <CPSwitch
            value={config.action?.requireRowSelection || false}
            onChange={(v) => updateAction("requireRowSelection", v)}
          />
        </CPRow>
        {config.action?.requireRowSelection && (
          <div style={{ marginLeft: 8, paddingLeft: 10, borderLeft: "2px solid rgba(var(--v5-primary-rgb), 0.3)" }}>
            <CPRow label="데이터 소스">
              <CPSelect
                value={config.action?.rowSelectionSource || "auto"}
                onChange={(v) => updateActionConfig("rowSelectionSource", v)}
                searchable={false}
              >
                <option value="auto">자동 감지 (권장)</option>
                <option value="tableList">테이블 리스트</option>
                <option value="splitPanelLeft">분할 패널 좌측</option>
                <option value="flowWidget">플로우 위젯</option>
              </CPSelect>
            </CPRow>
            <CPRow label="다중 선택 허용" help="여러 행 선택 시에도 활성화">
              <CPSwitch
                value={config.action?.allowMultiRowSelection ?? true}
                onChange={(v) => updateAction("allowMultiRowSelection", v)}
              />
            </CPRow>
          </div>
        )}

        {/* 데이터 전달 매핑 (transferData 일 때) */}
        {actionType === "transferData" && (
          <div style={{ marginTop: 10 }}>
            <TransferDataMappingBody
              config={config}
              onChange={onChange}
              availableTables={availableTables}
              mappingSourceColumnsMap={mappingSourceColumnsMap}
              setMappingSourceColumnsMap={setMappingSourceColumnsMap}
              mappingTargetColumns={mappingTargetColumns}
              activeMappingGroupIndex={activeMappingGroupIndex}
              setActiveMappingGroupIndex={setActiveMappingGroupIndex}
              loadTableColumns={loadTableColumns}
            />
          </div>
        )}

        {/* 제어 흐름 (excel_upload / multi_table_excel_upload 제외) */}
        {actionType !== "excel_upload" && actionType !== "multi_table_excel_upload" && (
          <div style={{ marginTop: 10, paddingTop: 8, borderTop: "1px solid var(--cp-border-subtle)" }}>
            <SectionLabel icon={<Settings size={11} />} text="제어 흐름" />
            <ImprovedButtonControlConfigPanel
              component={componentData}
              onUpdateProperty={handleUpdateProperty}
            />
          </div>
        )}

        {/* 흐름 가시성 (flow widget 있을 때) */}
        {hasFlowWidget && (
          <div style={{ marginTop: 10, paddingTop: 8, borderTop: "1px solid var(--cp-border-subtle)" }}>
            <SectionLabel icon={<Workflow size={11} />} text="플로우 단계별 표시 제어" primary />
            <FlowVisibilityConfigPanel
              component={componentData}
              allComponents={allComponents}
              onUpdateProperty={handleUpdateProperty}
            />
          </div>
        )}
      </CPGroup>
    </div>
  );
};

InvLegacyButtonConfigPanel.displayName = "InvLegacyButtonConfigPanel";

// ───────────────────────────────────────────────────────
// describeAction — ④ 섹션 desc 동적 텍스트
// ───────────────────────────────────────────────────────
function describeAction(t: string): string {
  const card = ACTION_TYPE_CARDS.find((c) => c.value === t);
  return card ? `${card.title} — ${card.desc}` : "선택한 액션의 옵션";
}

// ───────────────────────────────────────────────────────
// ActionDetailBody — 액션별 세부 설정 (10+ case)
// ───────────────────────────────────────────────────────
interface ActionDetailBodyProps {
  actionType: string;
  config: Record<string, any>;
  updateActionConfig: (field: string, value: any) => void;
  screens: ScreenOption[];
  screensLoading: boolean;
  allComponents: ComponentData[];
}

function ActionDetailBody(p: ActionDetailBodyProps) {
  const action = p.config.action || {};

  const messageRows = (
    <>
      <CPRow label="성공 메시지">
        <CPText
          value={action.successMessage || ""}
          onChange={(v) => p.updateActionConfig("successMessage", v)}
          placeholder="처리되었습니다."
        />
      </CPRow>
      <CPRow label="에러 메시지">
        <CPText
          value={action.errorMessage || ""}
          onChange={(v) => p.updateActionConfig("errorMessage", v)}
          placeholder="처리 중 오류가 발생했습니다."
        />
      </CPRow>
    </>
  );

  // ── save / delete / quickInsert
  if (p.actionType === "save" || p.actionType === "delete" || p.actionType === "quickInsert") {
    return (
      <>
        {messageRows}
        {p.actionType === "delete" && (
          <CPRow label="삭제 확인 팝업" help="삭제 전 확인 다이얼로그">
            <CPSwitch
              value={action.confirmBeforeDelete !== false}
              onChange={(v) => p.updateActionConfig("confirmBeforeDelete", v)}
            />
          </CPRow>
        )}
      </>
    );
  }

  // ── edit
  if (p.actionType === "edit") {
    const editMode = action.editMode || "modal";
    return (
      <>
        <CPRow label="수정 폼 화면" help="편집 시 열릴 화면 선택">
          {p.screensLoading ? (
            <InlineLoader text="화면 로딩..." />
          ) : (
            <CPSelect
              value={action.targetScreenId ? String(action.targetScreenId) : ""}
              onChange={(v) => p.updateActionConfig("targetScreenId", v ? Number(v) : undefined)}
            >
              <option value="">화면 선택</option>
              {p.screens.map((s) => (
                <option key={s.id} value={String(s.id)}>
                  {s.name}
                </option>
              ))}
            </CPSelect>
          )}
        </CPRow>
        <CPRow label="편집 모드">
          <CPSegment
            value={editMode}
            onChange={(v) => p.updateActionConfig("editMode", v)}
            options={[
              { value: "modal", label: "모달" },
              { value: "navigate", label: "페이지 이동" },
            ]}
          />
        </CPRow>
        {editMode === "modal" && (
          <>
            <CPRow label="모달 제목">
              <CPText
                value={action.editModalTitle || ""}
                onChange={(v) => p.updateActionConfig("editModalTitle", v)}
                placeholder="데이터 수정"
              />
            </CPRow>
            <CPRow label="모달 설명">
              <CPText
                value={action.editModalDescription || ""}
                onChange={(v) => p.updateActionConfig("editModalDescription", v)}
                placeholder="모달 설명"
              />
            </CPRow>
            <CPRow label="모달 크기">
              <CPSegment
                value={action.modalSize || "lg"}
                onChange={(v) => p.updateActionConfig("modalSize", v)}
                options={MODAL_SIZE_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
              />
            </CPRow>
          </>
        )}
        {messageRows}
      </>
    );
  }

  // ── modal
  if (p.actionType === "modal") {
    return (
      <>
        <CPRow label="대상 화면">
          {p.screensLoading ? (
            <InlineLoader text="화면 로딩..." />
          ) : (
            <CPSelect
              value={action.targetScreenId ? String(action.targetScreenId) : ""}
              onChange={(v) => p.updateActionConfig("targetScreenId", v ? Number(v) : undefined)}
            >
              <option value="">화면 선택</option>
              {p.screens.map((s) => (
                <option key={s.id} value={String(s.id)}>
                  {s.name}
                </option>
              ))}
            </CPSelect>
          )}
        </CPRow>
        <CPRow label="모달 제목">
          <CPText
            value={action.modalTitle || ""}
            onChange={(v) => p.updateActionConfig("modalTitle", v)}
            placeholder="모달 제목"
          />
        </CPRow>
        <CPRow label="모달 설명">
          <CPText
            value={action.modalDescription || ""}
            onChange={(v) => p.updateActionConfig("modalDescription", v)}
            placeholder="모달 설명"
          />
        </CPRow>
        <CPRow label="데이터 자동 전달" help="선택된 행을 모달에 전달">
          <CPSwitch
            value={action.autoDetectDataSource || false}
            onChange={(v) => p.updateActionConfig("autoDetectDataSource", v)}
          />
        </CPRow>
        {messageRows}
      </>
    );
  }

  // ── navigate
  if (p.actionType === "navigate") {
    return (
      <>
        <CPRow label="이동 대상 URL">
          <CPText
            value={action.targetUrl || ""}
            onChange={(v) => p.updateActionConfig("targetUrl", v)}
            placeholder="/admin/example"
          />
        </CPRow>
        {messageRows}
      </>
    );
  }

  // ── excel_download
  if (p.actionType === "excel_download") {
    return (
      <>
        <CPRow label="현재 필터 적용" help="검색 조건이 적용된 데이터만 다운">
          <CPSwitch
            value={action.applyCurrentFilters !== false}
            onChange={(v) => p.updateActionConfig("applyCurrentFilters", v)}
          />
        </CPRow>
        <CPRow label="선택된 행만" help="테이블에서 선택한 행만">
          <CPSwitch
            value={action.selectedRowsOnly || false}
            onChange={(v) => p.updateActionConfig("selectedRowsOnly", v)}
          />
        </CPRow>
        {messageRows}
      </>
    );
  }

  // ── excel_upload / multi_table_excel_upload
  if (p.actionType === "excel_upload" || p.actionType === "multi_table_excel_upload") {
    return <>{messageRows}</>;
  }

  // ── transferData
  if (p.actionType === "transferData") {
    const dt = action.dataTransfer || {};
    const updateDt = (k: string, v: any) =>
      p.updateActionConfig("dataTransfer", { ...dt, [k]: v });
    return (
      <>
        <CPRow label="소스 컴포넌트" help="데이터를 가져올 컴포넌트">
          <CPSelect
            value={dt.sourceComponentId || ""}
            onChange={(v) => updateDt("sourceComponentId", v)}
          >
            <option value="">선택...</option>
            <option value="__auto__">자동 탐색 (현재 활성 테이블)</option>
            {p.allComponents
              .filter((c: any) => {
                const t = c.componentType || c.type || "";
                return isDataTransferComponentType(t);
              })
              .map((c: any) => (
                <option key={c.id} value={c.id}>
                  {c.label || c.componentConfig?.title || c.id}
                </option>
              ))}
          </CPSelect>
        </CPRow>
        <CPRow label="타겟 타입">
          <CPSegment
            value={dt.targetType || "component"}
            onChange={(v) => updateDt("targetType", v)}
            options={[
              { value: "component", label: "컴포넌트" },
              { value: "splitPanel", label: "분할 반대편" },
            ]}
          />
        </CPRow>
        {dt.targetType === "component" && (
          <CPRow label="타겟 컴포넌트">
            <CPSelect
              value={dt.targetComponentId || ""}
              onChange={(v) => updateDt("targetComponentId", v)}
            >
              <option value="">선택...</option>
              {p.allComponents
                .filter((c: any) => {
                  const t = c.componentType || c.type || "";
                  const ok = isDataTransferComponentType(t);
                  return ok && c.id !== dt.sourceComponentId;
                })
                .map((c: any) => (
                  <option key={c.id} value={c.id}>
                    {c.label || c.componentConfig?.title || c.id}
                  </option>
                ))}
            </CPSelect>
          </CPRow>
        )}
        <CPRow label="전달 모드">
          <CPSegment
            value={dt.mode || "append"}
            onChange={(v) => updateDt("mode", v)}
            options={TRANSFER_MODE_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
          />
        </CPRow>
        <CPRow label="전달 후 소스 초기화" help="전달 후 선택 해제">
          <CPSwitch
            value={dt.clearAfterTransfer || false}
            onChange={(v) => updateDt("clearAfterTransfer", v)}
          />
        </CPRow>
        <CPRow label="전달 전 확인" help="확인 다이얼로그 표시">
          <CPSwitch
            value={dt.confirmBeforeTransfer || false}
            onChange={(v) => updateDt("confirmBeforeTransfer", v)}
          />
        </CPRow>
        {dt.confirmBeforeTransfer && (
          <CPRow label="확인 메시지">
            <CPText
              value={dt.confirmMessage || ""}
              onChange={(v) => updateDt("confirmMessage", v)}
              placeholder="선택한 항목을 전달하시겠습니까?"
            />
          </CPRow>
        )}
        <Hint>매핑 규칙은 ▾ 고급 설정의 "데이터 전달 매핑" 섹션에서 편집</Hint>
        {messageRows}
      </>
    );
  }

  // ── event
  if (p.actionType === "event") {
    return (
      <>
        <CPRow label="이벤트명">
          <CPText
            value={action.eventName || ""}
            onChange={(v) => p.updateActionConfig("eventName", v)}
            placeholder="이벤트 이름"
          />
        </CPRow>
        {messageRows}
      </>
    );
  }

  // ── default (approval / control / copy / barcode_scan / operation_control)
  return (
    <>
      <Hint>이 액션은 기본 메시지 외 추가 옵션이 없습니다 (제어 흐름은 ▾ 고급 설정).</Hint>
      {messageRows}
    </>
  );
}

// ───────────────────────────────────────────────────────
// IconOptionsBody — 아이콘 설정 (추천 / 커스텀 / 검색 / SVG / 크기 / 위치 / 간격)
// ───────────────────────────────────────────────────────
interface IconOptionsBodyProps {
  actionType: string;
  isNoIconAction: boolean;
  currentActionIcons: string[];
  selectedIcon: string;
  selectedIconType: "lucide" | "svg";
  iconSize: string;
  displayMode: string;
  iconTextPosition: string;
  iconGap: number;
  customIcons: string[];
  customSvgIcons: Array<{ name: string; svg: string }>;
  lucideSearchTerm: string;
  setLucideSearchTerm: (v: string) => void;
  svgInput: string;
  setSvgInput: (v: string) => void;
  svgName: string;
  setSvgName: (v: string) => void;
  svgError: string;
  setSvgError: (v: string) => void;
  onSelectIcon: (name: string, type?: "lucide" | "svg") => void;
  onRevertToDefault: () => void;
  onIconSizeChange: (preset: string) => void;
  onIconTextPositionChange: (pos: string) => void;
  onIconGapChange: (gap: number) => void;
  onCustomIconsChange: (icons: string[]) => void;
  onCustomSvgIconsChange: (icons: Array<{ name: string; svg: string }>) => void;
}

function IconOptionsBody(p: IconOptionsBodyProps) {
  const [showLucideSearch, setShowLucideSearch] = useState(false);
  const [showSvgPaste, setShowSvgPaste] = useState(false);

  return (
    <>
      {/* 추천 아이콘 */}
      {p.isNoIconAction ? (
        <Hint>{NO_ICON_MESSAGE}</Hint>
      ) : (
        <>
          <SectionLabel text="추천 아이콘" />
          <IconGrid
            icons={p.currentActionIcons}
            selectedIcon={p.selectedIcon}
            selectedType={p.selectedIconType}
            type="lucide"
            onSelect={p.onSelectIcon}
          />
        </>
      )}

      {/* 커스텀 아이콘 */}
      {(p.customIcons.length > 0 || p.customSvgIcons.length > 0) && (
        <div style={{ marginTop: 10 }}>
          <SectionLabel text="커스텀 아이콘" primary />
          <div
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(4, 1fr)",
              gap: 4,
            }}
          >
            {p.customIcons.map((iconName) => (
              <CustomIconCard
                key={`c-${iconName}`}
                iconName={iconName}
                type="lucide"
                isSelected={p.selectedIcon === iconName && p.selectedIconType === "lucide"}
                onSelect={() => p.onSelectIcon(iconName, "lucide")}
                onRemove={() => {
                  p.onCustomIconsChange(p.customIcons.filter((n) => n !== iconName));
                  if (p.selectedIcon === iconName) p.onRevertToDefault();
                }}
              />
            ))}
            {p.customSvgIcons.map((s) => (
              <CustomIconCard
                key={`s-${s.name}`}
                iconName={s.name}
                type="svg"
                svg={s.svg}
                isSelected={p.selectedIcon === s.name && p.selectedIconType === "svg"}
                onSelect={() => p.onSelectIcon(s.name, "svg")}
                onRemove={() => {
                  p.onCustomSvgIconsChange(p.customSvgIcons.filter((x) => x.name !== s.name));
                  if (p.selectedIcon === s.name) p.onRevertToDefault();
                }}
              />
            ))}
          </div>
        </div>
      )}

      {/* 추가 버튼 */}
      <div style={{ marginTop: 8, display: "flex", gap: 6 }}>
        <button
          type="button"
          onClick={() => setShowLucideSearch((x) => !x)}
          style={addBtnStyle(showLucideSearch)}
        >
          <Plus size={11} /> Lucide 검색
        </button>
        <button
          type="button"
          onClick={() => setShowSvgPaste((x) => !x)}
          style={addBtnStyle(showSvgPaste)}
        >
          <Plus size={11} /> SVG 붙여넣기
        </button>
      </div>

      {/* Lucide 검색 (인라인) */}
      {showLucideSearch && (
        <div style={inlinePanelStyle}>
          <input
            type="text"
            value={p.lucideSearchTerm}
            onChange={(e) => p.setLucideSearchTerm(e.target.value)}
            placeholder="아이콘 이름 검색..."
            style={searchInputStyle}
            autoFocus
          />
          <div
            style={{
              maxHeight: 180,
              overflowY: "auto",
              marginTop: 5,
            }}
          >
            <IconGrid
              icons={Object.keys(allLucideIcons)
                .filter((n) => n.toLowerCase().includes(p.lucideSearchTerm.toLowerCase()))
                .slice(0, 32)}
              selectedIcon={p.selectedIcon}
              selectedType={p.selectedIconType}
              type="lucide"
              onSelect={(name) => {
                if (!p.customIcons.includes(name)) {
                  p.onCustomIconsChange([...p.customIcons, name]);
                }
                p.onSelectIcon(name, "lucide");
              }}
            />
          </div>
        </div>
      )}

      {/* SVG 붙여넣기 (인라인) */}
      {showSvgPaste && (
        <div style={inlinePanelStyle}>
          <CPRow label="이름">
            <CPText value={p.svgName} onChange={p.setSvgName} placeholder="아이콘 이름" />
          </CPRow>
          <textarea
            value={p.svgInput}
            onChange={(e) => {
              p.setSvgInput(e.target.value);
              if (p.svgError) p.setSvgError("");
            }}
            onKeyDown={(e) => e.stopPropagation()}
            placeholder='<svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>'
            style={{
              width: "100%",
              minHeight: 70,
              padding: 6,
              fontSize: 10.5,
              fontFamily: "var(--v5-font-mono)",
              background: "var(--cp-surface)",
              border: "1px solid var(--cp-border)",
              borderRadius: 4,
              color: "var(--cp-text)",
              marginTop: 5,
              resize: "vertical",
            }}
          />
          {p.svgInput && (
            <div
              style={{
                marginTop: 5,
                padding: 6,
                background: "var(--cp-bg-subtle)",
                borderRadius: 4,
                display: "flex",
                justifyContent: "center",
              }}
            >
              <span
                style={{
                  width: 28,
                  height: 28,
                  display: "inline-flex",
                  alignItems: "center",
                  justifyContent: "center",
                  color: "var(--cp-text)",
                }}
                dangerouslySetInnerHTML={{ __html: sanitizeSvg(p.svgInput) }}
              />
            </div>
          )}
          {p.svgError && <Hint tone="warn">{p.svgError}</Hint>}
          <button
            type="button"
            onClick={() => {
              if (!p.svgName.trim()) {
                p.setSvgError("아이콘 이름을 입력하세요.");
                return;
              }
              if (!p.svgInput.trim().includes("<svg")) {
                p.setSvgError("유효한 SVG 코드가 아닙니다.");
                return;
              }
              const sanitized = sanitizeSvg(p.svgInput);
              let finalName = p.svgName.trim();
              const existing = new Set(p.customSvgIcons.map((s) => s.name));
              if (existing.has(finalName)) {
                let n = 2;
                while (existing.has(`${p.svgName.trim()}(${n})`)) n++;
                finalName = `${p.svgName.trim()}(${n})`;
              }
              p.onCustomSvgIconsChange([
                ...p.customSvgIcons,
                { name: finalName, svg: sanitized },
              ]);
              p.setSvgInput("");
              p.setSvgName("");
              p.setSvgError("");
              setShowSvgPaste(false);
            }}
            style={{
              marginTop: 6,
              width: "100%",
              padding: "6px 8px",
              fontSize: 11,
              fontWeight: 600,
              background: "var(--v5-primary, #6c5ce7)",
              color: "#fff",
              border: "none",
              borderRadius: 4,
              cursor: "pointer",
              fontFamily: "var(--v5-font-sans)",
            }}
          >
            추가
          </button>
        </div>
      )}

      {/* 아이콘 크기 */}
      <div style={{ marginTop: 10 }}>
        <CPRow label="아이콘 크기">
          <CPSegment
            value={p.iconSize}
            onChange={p.onIconSizeChange}
            options={Object.keys(iconSizePresets).map((v) => ({ value: v, label: v }))}
          />
        </CPRow>
      </div>

      {/* 텍스트 위치 (icon-text 일 때) */}
      {p.displayMode === "icon-text" && (
        <>
          <CPRow label="텍스트 위치">
            <CPSegment
              value={p.iconTextPosition}
              onChange={p.onIconTextPositionChange}
              options={ICON_TEXT_POSITION_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
            />
          </CPRow>
          <CPRow label="아이콘-텍스트 간격">
            <CPNumber
              value={p.iconGap}
              onChange={(v) => p.onIconGapChange(v ?? 6)}
              min={0}
              max={32}
              suffix="px"
            />
          </CPRow>
        </>
      )}
    </>
  );
}

// ───────────────────────────────────────────────────────
// IconGrid — 추천 / 검색 / 커스텀 공통
// ───────────────────────────────────────────────────────
function IconGrid({
  icons,
  selectedIcon,
  selectedType,
  type,
  onSelect,
}: {
  icons: string[];
  selectedIcon: string;
  selectedType: "lucide" | "svg";
  type: "lucide" | "svg";
  onSelect: (name: string, type?: "lucide" | "svg") => void;
}) {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(4, 1fr)",
        gap: 4,
      }}
    >
      {icons.map((iconName) => {
        const Icon = getLucideIcon(iconName);
        if (!Icon) return null;
        const active = selectedIcon === iconName && selectedType === type;
        return (
          <button
            key={iconName}
            type="button"
            onClick={() => onSelect(iconName, type)}
            title={iconName}
            style={{
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              gap: 3,
              padding: "6px 4px",
              background: active
                ? "rgba(var(--v5-primary-rgb), 0.10)"
                : "var(--cp-bg-subtle)",
              border: `1px solid ${
                active ? "rgba(var(--v5-primary-rgb), 0.5)" : "var(--cp-border-subtle)"
              }`,
              borderRadius: 4,
              cursor: "pointer",
              fontFamily: "var(--v5-font-sans)",
              color: active ? "var(--v5-primary, #6c5ce7)" : "var(--cp-text-sec)",
              minHeight: 42,
            }}
          >
            <Icon size={16} />
            <span
              style={{
                fontSize: 8.5,
                color: "var(--cp-text-muted)",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                maxWidth: "100%",
              }}
            >
              {iconName}
            </span>
          </button>
        );
      })}
    </div>
  );
}

function CustomIconCard({
  iconName,
  type,
  svg,
  isSelected,
  onSelect,
  onRemove,
}: {
  iconName: string;
  type: "lucide" | "svg";
  svg?: string;
  isSelected: boolean;
  onSelect: () => void;
  onRemove: () => void;
}) {
  const Icon = type === "lucide" ? getLucideIcon(iconName) : null;
  return (
    <div style={{ position: "relative" }}>
      <button
        type="button"
        onClick={onSelect}
        title={iconName}
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          gap: 3,
          padding: "6px 4px",
          background: isSelected
            ? "rgba(var(--v5-primary-rgb), 0.10)"
            : "var(--cp-bg-subtle)",
          border: `1px solid ${
            isSelected ? "rgba(var(--v5-primary-rgb), 0.5)" : "var(--cp-border-subtle)"
          }`,
          borderRadius: 4,
          cursor: "pointer",
          width: "100%",
          color: isSelected ? "var(--v5-primary, #6c5ce7)" : "var(--cp-text-sec)",
          minHeight: 42,
        }}
      >
        {type === "lucide" && Icon ? (
          <Icon size={16} />
        ) : svg ? (
          <span
            style={{ width: 16, height: 16, display: "inline-flex" }}
            dangerouslySetInnerHTML={{ __html: sanitizeSvg(svg) }}
          />
        ) : null}
        <span
          style={{
            fontSize: 8.5,
            color: "var(--cp-text-muted)",
            whiteSpace: "nowrap",
            overflow: "hidden",
            textOverflow: "ellipsis",
            maxWidth: "100%",
          }}
        >
          {iconName}
        </span>
      </button>
      <button
        type="button"
        onClick={onRemove}
        style={{
          position: "absolute",
          top: -4,
          right: -4,
          width: 14,
          height: 14,
          borderRadius: 999,
          background: "var(--v5-red, #ef4444)",
          color: "#fff",
          border: "none",
          cursor: "pointer",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          fontSize: 9,
        }}
      >
        <X size={9} />
      </button>
    </div>
  );
}

// 인라인 추가 버튼 / 패널 스타일
function addBtnStyle(active: boolean): React.CSSProperties {
  return {
    flex: 1,
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    gap: 4,
    padding: "5px 8px",
    fontSize: 10.5,
    background: active ? "rgba(var(--v5-primary-rgb), 0.10)" : "var(--cp-bg-subtle)",
    border: `1px solid ${
      active ? "rgba(var(--v5-primary-rgb), 0.45)" : "var(--cp-border-subtle)"
    }`,
    borderRadius: 4,
    cursor: "pointer",
    color: active ? "var(--v5-primary, #6c5ce7)" : "var(--cp-text-sec)",
    fontFamily: "var(--v5-font-sans)",
  };
}

const inlinePanelStyle: React.CSSProperties = {
  marginTop: 6,
  padding: 8,
  background: "var(--cp-bg-subtle)",
  border: "1px solid var(--cp-border-subtle)",
  borderRadius: 5,
};

const searchInputStyle: React.CSSProperties = {
  width: "100%",
  height: 26,
  padding: "0 8px",
  fontSize: 11.5,
  background: "var(--cp-surface)",
  border: "1px solid var(--cp-border)",
  borderRadius: 4,
  color: "var(--cp-text)",
  outline: "none",
  fontFamily: "var(--v5-font-sans)",
};

// ───────────────────────────────────────────────────────
// TransferDataMappingBody — 데이터 전달 매핑 (multi table)
// ───────────────────────────────────────────────────────
interface TransferDataMappingBodyProps {
  config: Record<string, any>;
  onChange: (config: Record<string, any>) => void;
  availableTables: Array<{ name: string; label: string }>;
  mappingSourceColumnsMap: Record<string, Array<{ name: string; label: string }>>;
  setMappingSourceColumnsMap: React.Dispatch<
    React.SetStateAction<Record<string, Array<{ name: string; label: string }>>>
  >;
  mappingTargetColumns: Array<{ name: string; label: string }>;
  activeMappingGroupIndex: number;
  setActiveMappingGroupIndex: (i: number) => void;
  loadTableColumns: (tableName: string) => Promise<Array<{ name: string; label: string }>>;
}

function TransferDataMappingBody(p: TransferDataMappingBodyProps) {
  const dt = p.config.action?.dataTransfer || {};
  const multi: Array<{
    sourceTable: string;
    mappingRules: Array<{ sourceField: string; targetField: string }>;
  }> = dt.multiTableMappings || [];

  const updateDt = (field: string, value: any) => {
    const a = p.config.action || {};
    const cur = a.dataTransfer || {};
    p.onChange({ ...p.config, action: { ...a, dataTransfer: { ...cur, [field]: value } } });
  };

  const activeGroup = multi[p.activeMappingGroupIndex];
  const activeSourceTable = activeGroup?.sourceTable || "";
  const activeSourceColumns = p.mappingSourceColumnsMap[activeSourceTable] || [];
  const activeRules = activeGroup?.mappingRules || [];

  const updateGroupField = (field: string, value: any) => {
    const arr = [...multi];
    arr[p.activeMappingGroupIndex] = { ...arr[p.activeMappingGroupIndex], [field]: value };
    updateDt("multiTableMappings", arr);
  };

  return (
    <>
      <SectionLabel icon={<SendHorizontal size={11} />} text="데이터 전달 매핑" primary />
      <Hint>레이어별 소스 테이블이 다를 때 각각 매핑 규칙</Hint>

      <CPRow label="타겟 테이블">
        <CPSelect
          value={dt.targetTable || ""}
          onChange={(v) => updateDt("targetTable", v)}
        >
          <option value="">선택...</option>
          {p.availableTables.map((t) => (
            <option key={t.name} value={t.name}>
              {t.label}
            </option>
          ))}
        </CPSelect>
      </CPRow>

      {!dt.targetTable ? (
        <Hint tone="warn">타겟 테이블을 먼저 선택하세요</Hint>
      ) : (
        <>
          {/* 소스 테이블 그룹 탭 */}
          <div style={{ marginTop: 8, display: "flex", flexWrap: "wrap", gap: 4 }}>
            {multi.map((g, i) => {
              const active = i === p.activeMappingGroupIndex;
              return (
                <div key={i} style={{ display: "flex", alignItems: "center" }}>
                  <button
                    type="button"
                    onClick={() => p.setActiveMappingGroupIndex(i)}
                    style={{
                      padding: "4px 8px",
                      fontSize: 10.5,
                      background: active
                        ? "var(--v5-primary, #6c5ce7)"
                        : "var(--cp-bg-subtle)",
                      color: active ? "#fff" : "var(--cp-text-sec)",
                      border: `1px solid ${
                        active ? "var(--v5-primary, #6c5ce7)" : "var(--cp-border-subtle)"
                      }`,
                      borderRight: "none",
                      borderRadius: "4px 0 0 4px",
                      cursor: "pointer",
                      fontFamily: "var(--v5-font-sans)",
                    }}
                  >
                    {g.sourceTable
                      ? p.availableTables.find((t) => t.name === g.sourceTable)?.label ||
                        g.sourceTable
                      : `그룹 ${i + 1}`}
                    {g.mappingRules?.length > 0 && (
                      <span
                        style={{
                          marginLeft: 4,
                          padding: "0 5px",
                          background: active
                            ? "rgba(255,255,255,0.25)"
                            : "rgba(var(--v5-primary-rgb), 0.15)",
                          borderRadius: 999,
                          fontSize: 9,
                          fontWeight: 700,
                        }}
                      >
                        {g.mappingRules.length}
                      </span>
                    )}
                  </button>
                  <button
                    type="button"
                    onClick={() => {
                      const arr = [...multi];
                      arr.splice(i, 1);
                      updateDt("multiTableMappings", arr);
                      if (p.activeMappingGroupIndex >= arr.length) {
                        p.setActiveMappingGroupIndex(Math.max(0, arr.length - 1));
                      }
                    }}
                    style={{
                      padding: "4px 5px",
                      background: active
                        ? "var(--v5-primary, #6c5ce7)"
                        : "var(--cp-bg-subtle)",
                      color: active ? "#fff" : "var(--v5-red, #ef4444)",
                      border: `1px solid ${
                        active ? "var(--v5-primary, #6c5ce7)" : "var(--cp-border-subtle)"
                      }`,
                      borderRadius: "0 4px 4px 0",
                      cursor: "pointer",
                      display: "flex",
                      alignItems: "center",
                    }}
                  >
                    <X size={10} />
                  </button>
                </div>
              );
            })}
            <button
              type="button"
              onClick={() => {
                updateDt("multiTableMappings", [
                  ...multi,
                  { sourceTable: "", mappingRules: [] },
                ]);
                p.setActiveMappingGroupIndex(multi.length);
              }}
              style={{
                padding: "4px 8px",
                fontSize: 10.5,
                background: "var(--cp-bg-subtle)",
                color: "var(--cp-text-sec)",
                border: "1px dashed var(--cp-border)",
                borderRadius: 4,
                cursor: "pointer",
                display: "inline-flex",
                alignItems: "center",
                gap: 3,
                fontFamily: "var(--v5-font-sans)",
              }}
            >
              <Plus size={10} /> 그룹
            </button>
          </div>

          {/* 활성 그룹 편집 */}
          {activeGroup && (
            <div
              style={{
                marginTop: 8,
                padding: 8,
                background: "var(--cp-bg-subtle)",
                border: "1px solid var(--cp-border-subtle)",
                borderRadius: 4,
              }}
            >
              <CPRow label="소스 테이블">
                <CPSelect
                  value={activeSourceTable}
                  onChange={async (v) => {
                    updateGroupField("sourceTable", v);
                    if (v && !p.mappingSourceColumnsMap[v]) {
                      const cols = await p.loadTableColumns(v);
                      p.setMappingSourceColumnsMap((prev) => ({ ...prev, [v]: cols }));
                    }
                  }}
                >
                  <option value="">선택...</option>
                  {p.availableTables.map((t) => (
                    <option key={t.name} value={t.name}>
                      {t.label}
                    </option>
                  ))}
                </CPSelect>
              </CPRow>

              <div style={{ marginTop: 6 }}>
                <div
                  style={{
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "space-between",
                    marginBottom: 4,
                  }}
                >
                  <SectionLabel text={`매핑 규칙 ${activeRules.length}개`} />
                  <button
                    type="button"
                    onClick={() =>
                      updateGroupField("mappingRules", [
                        ...activeRules,
                        { sourceField: "", targetField: "" },
                      ])
                    }
                    disabled={!activeSourceTable}
                    style={{
                      padding: "3px 7px",
                      fontSize: 10,
                      background: "var(--cp-bg-subtle)",
                      border: "1px solid var(--cp-border)",
                      borderRadius: 4,
                      cursor: activeSourceTable ? "pointer" : "not-allowed",
                      color: "var(--cp-text)",
                      opacity: activeSourceTable ? 1 : 0.5,
                      display: "inline-flex",
                      alignItems: "center",
                      gap: 3,
                      fontFamily: "var(--v5-font-sans)",
                    }}
                  >
                    <Plus size={10} /> 추가
                  </button>
                </div>

                {!activeSourceTable ? (
                  <Hint tone="warn">소스 테이블을 먼저 선택하세요</Hint>
                ) : activeRules.length === 0 ? (
                  <Hint>매핑 없음 (동일 필드명 자동 매핑)</Hint>
                ) : (
                  <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                    {activeRules.map((rule, ri) => (
                      <div
                        key={ri}
                        style={{
                          display: "grid",
                          gridTemplateColumns: "1fr 14px 1fr 24px",
                          gap: 4,
                          alignItems: "center",
                        }}
                      >
                        <CPSelect
                          value={rule.sourceField}
                          onChange={(v) => {
                            const arr = [...activeRules];
                            arr[ri] = { ...arr[ri], sourceField: v };
                            updateGroupField("mappingRules", arr);
                          }}
                        >
                          <option value="">소스</option>
                          {activeSourceColumns.map((c) => (
                            <option key={c.name} value={c.name}>
                              {c.label}
                            </option>
                          ))}
                        </CPSelect>
                        <ArrowRight
                          size={10}
                          style={{ color: "var(--cp-text-muted)", margin: "0 auto" }}
                        />
                        <CPSelect
                          value={rule.targetField}
                          onChange={(v) => {
                            const arr = [...activeRules];
                            arr[ri] = { ...arr[ri], targetField: v };
                            updateGroupField("mappingRules", arr);
                          }}
                        >
                          <option value="">타겟</option>
                          {p.mappingTargetColumns.map((c) => (
                            <option key={c.name} value={c.name}>
                              {c.label}
                            </option>
                          ))}
                        </CPSelect>
                        <CPIconBtn
                          tone="danger"
                          size={20}
                          onClick={() => {
                            const arr = [...activeRules];
                            arr.splice(ri, 1);
                            updateGroupField("mappingRules", arr);
                          }}
                        >
                          <X size={10} />
                        </CPIconBtn>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          )}
        </>
      )}
    </>
  );
}

export default InvLegacyButtonConfigPanel;
