"use client";

import React, { useCallback, useState, useEffect, useMemo } from "react";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import { PopComponentRegistry } from "@/lib/registry/PopComponentRegistry";
import { usePopAction } from "@/hooks/pop/usePopAction";
import { executeTaskList, type CollectedPayload } from "@/hooks/pop/executePopAction";
import { usePopDesignerContext } from "@/components/pop/designer/PopDesignerContext";
import { usePopEvent } from "@/hooks/pop/usePopEvent";
import {
  Save,
  Trash2,
  LogOut,
  Menu,
  ExternalLink,
  Plus,
  Check,
  X,
  Edit,
  Search,
  RefreshCw,
  Download,
  Upload,
  Send,
  Copy,
  Settings,
  ChevronDown,
  ShoppingCart,
  ShoppingBag,
  PackageCheck,
  ChevronRight,
  GripVertical,
  ChevronsUpDown,
  type LucideIcon,
} from "lucide-react";
import { toast } from "sonner";
import type { CollectedDataResponse, StatusChangeRule, ConditionalValue } from "./types";
import { apiClient } from "@/lib/api/client";
import { TableCombobox } from "./pop-shared/TableCombobox";
import { ColumnCombobox } from "./pop-shared/ColumnCombobox";
import { fetchTableList, fetchTableColumns, type TableInfo, type ColumnInfo } from "./pop-dashboard/utils/dataFetcher";

// ========================================
// STEP 1: 타입 정의
// ========================================

/** 메인 액션 타입 (5종) */
export type ButtonActionType = "save" | "delete" | "api" | "modal" | "event";

/** 후속 액션 타입 (4종) */
export type FollowUpActionType = "event" | "refresh" | "navigate" | "close-modal";

/** 버튼 variant (shadcn 기반 4종) */
export type ButtonVariant = "default" | "secondary" | "outline" | "destructive";

/** 모달 열기 방식 */
export type ModalMode = "dropdown" | "fullscreen" | "screen-ref";

/** 확인 다이얼로그 설정 */
export interface ConfirmConfig {
  enabled: boolean;
  message?: string; // 빈값이면 기본 메시지
}

/** 후속 액션 1건 */
export interface FollowUpAction {
  type: FollowUpActionType;
  // event
  eventName?: string;
  eventPayload?: Record<string, unknown>;
  // navigate
  targetScreenId?: string;
  params?: Record<string, string>;
}

/** 드롭다운 모달 메뉴 항목 */
export interface ModalMenuItem {
  label: string;
  screenId?: string;
  action?: string; // 커스텀 이벤트명
}

/** 메인 액션 설정 */
export interface ButtonMainAction {
  type: ButtonActionType;
  // save/delete 공통
  targetTable?: string;
  // api
  apiEndpoint?: string;
  apiMethod?: "GET" | "POST" | "PUT" | "DELETE";
  // modal
  modalMode?: ModalMode;
  modalScreenId?: string;
  modalTitle?: string;
  modalItems?: ModalMenuItem[];
  // event
  eventName?: string;
  eventPayload?: Record<string, unknown>;
}

/** 프리셋 이름 */
export type ButtonPreset = "save" | "delete" | "logout" | "menu" | "modal-open" | "cart" | "inbound-confirm" | "custom";

/** row_data 저장 모드 */
export type RowDataMode = "all" | "selected";

/** 장바구니 버튼 전용 설정 */
export interface CartButtonConfig {
  cartScreenId?: string;
  rowDataMode?: RowDataMode;
  selectedColumns?: string[];
}

/** pop-button 전체 설정 */
export interface PopButtonConfig {
  label: string;
  variant: ButtonVariant;
  icon?: string;
  iconOnly?: boolean;
  preset: ButtonPreset;
  confirm?: ConfirmConfig;
  action: ButtonMainAction;
  followUpActions?: FollowUpAction[];
  cart?: CartButtonConfig;
  statusChangeRules?: StatusChangeRule[];
  /** @deprecated inboundConfirm.statusChangeRules -> statusChangeRules로 이동 */
  inboundConfirm?: { statusChangeRules?: StatusChangeRule[] };
}

// ========================================
// STEP 1-B: 통합 작업 목록 타입 (v2)
// ========================================

/** 작업 타입 (10종) */
export type ButtonTaskType =
  | "data-save"
  | "data-update"
  | "data-delete"
  | "cart-save"
  | "modal-open"
  | "navigate"
  | "close-modal"
  | "refresh"
  | "api-call"
  | "custom-event";

/** 데이터 수정 연산 */
export type UpdateOperationType =
  | "assign"
  | "add"
  | "subtract"
  | "multiply"
  | "divide"
  | "conditional"
  | "db-conditional";

/** 데이터 수정 값 출처 */
export type UpdateValueSource = "fixed" | "linked" | "reference";

/** 작업 1건 설정 */
export interface ButtonTask {
  id: string;
  type: ButtonTaskType;
  label?: string;

  // data-update / data-delete
  targetTable?: string;
  targetColumn?: string;
  operationType?: UpdateOperationType;
  valueSource?: UpdateValueSource;
  fixedValue?: string;
  sourceField?: string;
  referenceTable?: string;
  referenceColumn?: string;
  referenceJoinKey?: string;
  conditionalValue?: ConditionalValue;
  // db-conditional (DB 컬럼 간 비교 후 값 판정)
  compareColumn?: string;
  compareOperator?: "=" | "!=" | ">" | "<" | ">=" | "<=";
  compareWith?: string;
  dbThenValue?: string;
  dbElseValue?: string;
  lookupMode?: "auto" | "manual";
  manualItemField?: string;
  manualPkColumn?: string;

  // cart-save
  cartScreenId?: string;

  // modal-open
  modalMode?: ModalMode;
  modalScreenId?: string;
  modalTitle?: string;
  modalItems?: ModalMenuItem[];

  // navigate
  targetScreenId?: string;
  params?: Record<string, string>;

  // api-call
  apiEndpoint?: string;
  apiMethod?: "GET" | "POST" | "PUT" | "DELETE";

  // custom-event (제어 실행)
  eventName?: string;
  eventPayload?: Record<string, unknown>;
  flowId?: number;
  flowName?: string;
}

/** 빠른 시작 템플릿 */
export type QuickStartTemplate = "save" | "delete" | "confirm" | "cart" | "modal" | "custom";

/** pop-button 설정 v2 (작업 목록 기반) */
export interface PopButtonConfigV2 {
  label: string;
  variant: ButtonVariant;
  icon?: string;
  iconOnly?: boolean;
  confirm?: ConfirmConfig;
  tasks: ButtonTask[];
}

/** 기존 config(v1) → v2 변환. tasks 필드가 이미 있으면 그대로 반환. */
export function migrateButtonConfig(old: PopButtonConfig): PopButtonConfigV2 {
  if ("tasks" in old && Array.isArray((old as unknown as PopButtonConfigV2).tasks)) {
    return old as unknown as PopButtonConfigV2;
  }

  const tasks: ButtonTask[] = [];
  let tid = 1;
  const nextId = () => `t${tid++}`;

  // 메인 액션 → task 변환
  if (old.preset === "cart") {
    tasks.push({ id: nextId(), type: "cart-save", cartScreenId: old.cart?.cartScreenId });
  } else if (old.action?.type === "modal") {
    tasks.push({
      id: nextId(),
      type: "modal-open",
      modalMode: old.action.modalMode,
      modalScreenId: old.action.modalScreenId,
      modalTitle: old.action.modalTitle,
      modalItems: old.action.modalItems,
    });
  } else if (old.action?.type === "delete") {
    tasks.push({ id: nextId(), type: "data-delete", targetTable: old.action.targetTable });
  } else if (old.action?.type === "api") {
    tasks.push({
      id: nextId(),
      type: "api-call",
      apiEndpoint: old.action.apiEndpoint,
      apiMethod: old.action.apiMethod,
    });
  } else if (old.action?.type === "event" && old.preset !== "inbound-confirm") {
    tasks.push({
      id: nextId(),
      type: "custom-event",
      eventName: old.action.eventName,
      eventPayload: old.action.eventPayload,
    });
  } else {
    // save / inbound-confirm / 기본
    tasks.push({ id: nextId(), type: "data-save" });
  }

  // 상태변경 규칙 → data-update task
  const rules = old.statusChangeRules ?? old.inboundConfirm?.statusChangeRules ?? [];
  for (const rule of rules) {
    tasks.push({
      id: nextId(),
      type: "data-update",
      targetTable: rule.targetTable,
      targetColumn: rule.targetColumn,
      operationType: "assign",
      valueSource: rule.valueType === "conditional" ? "fixed" : "fixed",
      fixedValue: rule.fixedValue,
      conditionalValue: rule.conditionalValue,
      lookupMode: rule.lookupMode,
      manualItemField: rule.manualItemField,
      manualPkColumn: rule.manualPkColumn,
    });
  }

  // 후속 액션 → task
  for (const fa of old.followUpActions ?? []) {
    switch (fa.type) {
      case "refresh":
        tasks.push({ id: nextId(), type: "refresh" });
        break;
      case "navigate":
        tasks.push({ id: nextId(), type: "navigate", targetScreenId: fa.targetScreenId, params: fa.params });
        break;
      case "close-modal":
        tasks.push({ id: nextId(), type: "close-modal" });
        break;
      case "event":
        tasks.push({ id: nextId(), type: "custom-event", eventName: fa.eventName, eventPayload: fa.eventPayload });
        break;
    }
  }

  return {
    label: old.label,
    variant: old.variant,
    icon: old.icon,
    iconOnly: old.iconOnly,
    confirm: old.confirm,
    tasks,
  };
}

/** 작업 타입 한글 라벨 */
export const TASK_TYPE_LABELS: Record<ButtonTaskType, string> = {
  "data-save": "데이터 저장",
  "data-update": "데이터 수정",
  "data-delete": "데이터 삭제",
  "cart-save": "장바구니 저장",
  "modal-open": "모달 열기",
  navigate: "페이지 이동",
  "close-modal": "모달 닫기",
  refresh: "새로고침",
  "api-call": "API 호출",
  "custom-event": "제어 실행",
};

/** 빠른 시작 템플릿별 기본 작업 목록 + 외형 */
export const QUICK_START_DEFAULTS: Record<
  QuickStartTemplate,
  { label: string; variant: ButtonVariant; icon: string; confirm?: ConfirmConfig; tasks: ButtonTask[] }
> = {
  save: {
    label: "저장",
    variant: "default",
    icon: "Save",
    confirm: { enabled: false },
    tasks: [{ id: "t1", type: "data-save" }],
  },
  delete: {
    label: "삭제",
    variant: "destructive",
    icon: "Trash2",
    confirm: { enabled: true, message: "삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다." },
    tasks: [{ id: "t1", type: "data-delete" }],
  },
  confirm: {
    label: "확정",
    variant: "default",
    icon: "PackageCheck",
    confirm: { enabled: true, message: "선택한 항목을 확정하시겠습니까?" },
    tasks: [
      { id: "t1", type: "data-save" },
      { id: "t2", type: "data-update" },
      { id: "t3", type: "refresh" },
    ],
  },
  cart: {
    label: "장바구니 저장",
    variant: "default",
    icon: "ShoppingCart",
    confirm: { enabled: true, message: "장바구니에 담은 항목을 저장하시겠습니까?" },
    tasks: [{ id: "t1", type: "cart-save" }],
  },
  modal: {
    label: "열기",
    variant: "outline",
    icon: "ExternalLink",
    confirm: { enabled: false },
    tasks: [{ id: "t1", type: "modal-open" }],
  },
  custom: {
    label: "버튼",
    variant: "default",
    icon: "none",
    confirm: { enabled: false },
    tasks: [],
  },
};

// ========================================
// 상수
// ========================================

/** 메인 액션 타입 라벨 */
const ACTION_TYPE_LABELS: Record<ButtonActionType, string> = {
  save: "저장",
  delete: "삭제",
  api: "API 호출",
  modal: "모달 열기",
  event: "이벤트 발행",
};

/** 후속 액션 타입 라벨 */
const FOLLOWUP_TYPE_LABELS: Record<FollowUpActionType, string> = {
  event: "이벤트 발행",
  refresh: "새로고침",
  navigate: "화면 이동",
  "close-modal": "모달 닫기",
};

/** variant 라벨 */
const VARIANT_LABELS: Record<ButtonVariant, string> = {
  default: "기본 (Primary)",
  secondary: "보조 (Secondary)",
  outline: "외곽선 (Outline)",
  destructive: "위험 (Destructive)",
};

/** 프리셋 라벨 */
const PRESET_LABELS: Record<ButtonPreset, string> = {
  save: "저장",
  delete: "삭제",
  logout: "로그아웃",
  menu: "메뉴 (드롭다운)",
  "modal-open": "모달 열기",
  cart: "장바구니 저장",
  "inbound-confirm": "입고 확정",
  custom: "직접 설정",
};

/** 모달 모드 라벨 */
const MODAL_MODE_LABELS: Record<ModalMode, string> = {
  dropdown: "드롭다운",
  fullscreen: "전체 모달",
  "screen-ref": "화면 선택",
};

/** API 메서드 라벨 */
const API_METHOD_LABELS: Record<string, string> = {
  GET: "GET",
  POST: "POST",
  PUT: "PUT",
  DELETE: "DELETE",
};

/** 주요 Lucide 아이콘 목록 (설정 패널용) */
const ICON_OPTIONS: { value: string; label: string }[] = [
  { value: "none", label: "없음" },
  { value: "Save", label: "저장 (Save)" },
  { value: "Trash2", label: "삭제 (Trash)" },
  { value: "LogOut", label: "로그아웃 (LogOut)" },
  { value: "Menu", label: "메뉴 (Menu)" },
  { value: "ExternalLink", label: "외부링크 (ExternalLink)" },
  { value: "Plus", label: "추가 (Plus)" },
  { value: "Check", label: "확인 (Check)" },
  { value: "X", label: "취소 (X)" },
  { value: "Edit", label: "수정 (Edit)" },
  { value: "Search", label: "검색 (Search)" },
  { value: "RefreshCw", label: "새로고침 (RefreshCw)" },
  { value: "Download", label: "다운로드 (Download)" },
  { value: "Upload", label: "업로드 (Upload)" },
  { value: "Send", label: "전송 (Send)" },
  { value: "Copy", label: "복사 (Copy)" },
  { value: "Settings", label: "설정 (Settings)" },
  { value: "ChevronDown", label: "아래 화살표 (ChevronDown)" },
  { value: "ShoppingCart", label: "장바구니 (ShoppingCart)" },
  { value: "ShoppingBag", label: "장바구니 담김 (ShoppingBag)" },
];

/** 프리셋별 기본 설정 */
const PRESET_DEFAULTS: Record<ButtonPreset, Partial<PopButtonConfig>> = {
  save: {
    label: "저장",
    variant: "default",
    icon: "Save",
    confirm: { enabled: false },
    action: { type: "save" },
  },
  delete: {
    label: "삭제",
    variant: "destructive",
    icon: "Trash2",
    confirm: { enabled: true, message: "" },
    action: { type: "delete" },
  },
  logout: {
    label: "로그아웃",
    variant: "outline",
    icon: "LogOut",
    confirm: { enabled: true, message: "로그아웃 하시겠습니까?" },
    action: {
      type: "api",
      apiEndpoint: "/api/auth/logout",
      apiMethod: "POST",
    },
  },
  menu: {
    label: "메뉴",
    variant: "secondary",
    icon: "Menu",
    confirm: { enabled: false },
    action: { type: "modal", modalMode: "dropdown" },
  },
  "modal-open": {
    label: "열기",
    variant: "outline",
    icon: "ExternalLink",
    confirm: { enabled: false },
    action: { type: "modal", modalMode: "fullscreen" },
  },
  cart: {
    label: "장바구니 저장",
    variant: "default",
    icon: "ShoppingCart",
    confirm: { enabled: true, message: "장바구니에 담은 항목을 저장하시겠습니까?" },
    action: { type: "event" },
  },
  "inbound-confirm": {
    label: "입고 확정",
    variant: "default",
    icon: "PackageCheck",
    confirm: { enabled: true, message: "선택한 품목을 입고 확정하시겠습니까?" },
    action: { type: "event" },
  },
  custom: {
    label: "버튼",
    variant: "default",
    icon: "none",
    confirm: { enabled: false },
    action: { type: "save" },
  },
};

/** 확인 다이얼로그 기본 메시지 (액션별) */
const DEFAULT_CONFIRM_MESSAGES: Record<ButtonActionType, string> = {
  save: "저장하시겠습니까?",
  delete: "삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
  api: "실행하시겠습니까?",
  modal: "열기하시겠습니까?",
  event: "실행하시겠습니까?",
};

// ========================================
// 헬퍼 함수
// ========================================

/** 섹션 구분선 */
function SectionDivider({ label }: { label: string }) {
  return (
    <div className="pt-3 pb-1">
      <div className="flex items-center gap-2">
        <div className="bg-muted/60 h-px flex-1" />
        <span className="text-muted-foreground text-xs font-medium">{label}</span>
        <div className="bg-muted/60 h-px flex-1" />
      </div>
    </div>
  );
}

/** 장바구니 데이터 매핑 행 (읽기 전용) */
function CartMappingRow({
  source,
  target,
  desc,
  auto,
}: {
  source: string;
  target: string;
  desc?: string;
  auto?: boolean;
}) {
  return (
    <div className="flex items-start gap-1 py-0.5">
      <span className={cn("min-w-0 flex-1 text-[10px]", auto ? "text-muted-foreground" : "text-foreground")}>
        {source}
      </span>
      <span className="text-muted-foreground shrink-0 text-[10px]">&rarr;</span>
      <div className="shrink-0 text-right">
        <code className="bg-muted text-foreground rounded px-1 font-mono text-[10px]">{target}</code>
        {desc && <p className="text-muted-foreground mt-0.5 text-[9px] leading-tight">{desc}</p>}
      </div>
    </div>
  );
}

/** 허용된 아이콘 맵 (개별 import로 트리 쉐이킹 적용) */
const LUCIDE_ICON_MAP: Record<string, LucideIcon> = {
  Save,
  Trash2,
  LogOut,
  Menu,
  ExternalLink,
  Plus,
  Check,
  X,
  Edit,
  Search,
  RefreshCw,
  Download,
  Upload,
  Send,
  Copy,
  Settings,
  ChevronDown,
  ShoppingCart,
  ShoppingBag,
  PackageCheck,
  ChevronRight,
  GripVertical,
};

/** Lucide 아이콘 동적 렌더링 */
function DynamicLucideIcon({ name, size = 16, className }: { name: string; size?: number; className?: string }) {
  const IconComponent = LUCIDE_ICON_MAP[name];
  if (!IconComponent) return null;
  return <IconComponent size={size} className={className} />;
}

// ========================================
// STEP 2: 메인 컴포넌트
// ========================================

interface PopButtonComponentProps {
  config?: PopButtonConfig;
  label?: string;
  isDesignMode?: boolean;
  screenId?: string;
  componentId?: string;
}

export function PopButtonComponent({ config, label, isDesignMode, screenId, componentId }: PopButtonComponentProps) {
  const { execute, isLoading, pendingConfirm, confirmExecute, cancelConfirm } = usePopAction(screenId || "");

  const { subscribe, publish } = usePopEvent(screenId || "default");

  // 장바구니 모드 상태 (v1 preset 또는 v2 tasks에 cart-save가 있으면 활성)
  const v2Tasks = (config && "tasks" in config && Array.isArray((config as any).tasks))
    ? (config as any).tasks as PopButtonTask[]
    : null;
  const hasCartSaveTask = !!v2Tasks?.some((t) => t.type === "cart-save");
  const isCartMode = config?.preset === "cart" || hasCartSaveTask;
  const isInboundConfirmMode = config?.preset === "inbound-confirm";
  const [cartCount, setCartCount] = useState(0);
  const [cartIsDirty, setCartIsDirty] = useState(false);
  const [cartSaving, setCartSaving] = useState(false);
  const [showCartConfirm, setShowCartConfirm] = useState(false);
  const [confirmProcessing, setConfirmProcessing] = useState(false);
  const [showInboundConfirm, setShowInboundConfirm] = useState(false);
  const [inboundSelectedCount, setInboundSelectedCount] = useState(0);
  const [generatedCodesResult, setGeneratedCodesResult] = useState<
    Array<{ targetColumn: string; code: string; showResultModal?: boolean }>
  >([]);

  const [deferredV2Tasks, setDeferredV2Tasks] = useState<
    Array<{ type: string; targetScreenId?: string; params?: Record<string, string> }>
  >([]);

  const handleCloseGeneratedCodesModal = useCallback(() => {
    setGeneratedCodesResult([]);
    toast.success("작업 완료");
    publish(`__comp_output__${componentId}__action_completed`, {
      action: "task-list",
      success: true,
    });

    // v2 보류된 작업 실행
    if (deferredV2Tasks.length > 0) {
      for (const task of deferredV2Tasks) {
        if (task.type === "navigate" && task.targetScreenId) {
          publish("__pop_navigate__", { screenId: task.targetScreenId, params: task.params });
        }
      }
      setDeferredV2Tasks([]);
      return;
    }

    // v1 후속 액션
    const followUps = config?.followUpActions ?? [];
    for (const fa of followUps) {
      switch (fa.type) {
        case "navigate":
          if (fa.targetScreenId) {
            publish("__pop_navigate__", { screenId: fa.targetScreenId, params: fa.params });
          }
          break;
        case "refresh":
          publish("__pop_refresh__");
          break;
        case "event":
          if (fa.eventName) publish(fa.eventName, fa.eventPayload);
          break;
      }
    }
  }, [componentId, publish, config?.followUpActions, deferredV2Tasks]);

  // v2 작업 목록 감지 (선택 항목 구독보다 먼저 선언)
  const v2Config = useMemo(() => {
    if (!config) return null;
    if ("tasks" in config && Array.isArray((config as unknown as PopButtonConfigV2).tasks)) {
      return config as unknown as PopButtonConfigV2;
    }
    return null;
  }, [config]);

  // 선택 항목 수 수신 (v1 inbound-confirm + v2 모두 활성)
  const hasDataTasks = useMemo(() => {
    if (isInboundConfirmMode) return true;
    if (!v2Config) return false;
    return v2Config.tasks.some((t) => t.type === "data-save" || t.type === "data-update");
  }, [isInboundConfirmMode, v2Config]);

  useEffect(() => {
    if (!hasDataTasks || !componentId) return;
    const unsub = subscribe(`__comp_input__${componentId}__selected_items`, (payload: unknown) => {
      const data = payload as { value?: unknown[] } | undefined;
      const items = Array.isArray(data?.value) ? data.value : [];
      setInboundSelectedCount(items.length);
    });
    return unsub;
  }, [hasDataTasks, componentId, subscribe]);

  // 장바구니 상태 수신 (카드 목록에서 count/isDirty 전달)
  useEffect(() => {
    if (!isCartMode || !componentId) return;
    const unsub = subscribe(`__comp_input__${componentId}__cart_updated`, (payload: unknown) => {
      const data = payload as { value?: { count?: number; isDirty?: boolean } } | undefined;
      const inner = data?.value;
      if (inner?.count !== undefined) setCartCount(inner.count);
      if (inner?.isDirty !== undefined) setCartIsDirty(inner.isDirty);
    });
    return unsub;
  }, [isCartMode, componentId, subscribe]);

  // 저장 완료 수신 (카드 목록에서 saveToDb 완료 후 전달)
  const resolvedCartScreenId = config?.cart?.cartScreenId
    || v2Tasks?.find((t) => t.type === "cart-save")?.cartScreenId;
  const cartScreenIdRef = React.useRef(resolvedCartScreenId);
  cartScreenIdRef.current = resolvedCartScreenId;

  useEffect(() => {
    if (!isCartMode || !componentId) return;
    const unsub = subscribe(`__comp_input__${componentId}__cart_save_completed`, (payload: unknown) => {
      const data = payload as { value?: { success?: boolean } } | undefined;
      setCartSaving(false);
      if (data?.value?.success) {
        setCartIsDirty(false);
        const targetScreenId = cartScreenIdRef.current;
        if (targetScreenId) {
          const cleanId = targetScreenId.replace(/^.*\/(\d+)$/, "$1").trim();
          window.location.href = `/pop/screens/${cleanId}`;
        } else {
          toast.success("장바구니가 저장되었습니다.");
        }
      } else {
        toast.error("장바구니 저장에 실패했습니다.");
      }
    });
    return unsub;
  }, [isCartMode, componentId, subscribe]);

  const getConfirmMessage = useCallback((): string => {
    if (pendingConfirm?.confirm?.message) return pendingConfirm.confirm.message;
    if (config?.confirm?.message) return config.confirm.message;
    return DEFAULT_CONFIRM_MESSAGES[config?.action?.type || "save"];
  }, [pendingConfirm?.confirm?.message, config?.confirm?.message, config?.action?.type]);

  // 장바구니 저장 트리거 (연결 미설정 시 10초 타임아웃으로 복구)
  const cartSaveTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);

  const handleCartSave = useCallback(() => {
    if (!componentId) return;
    setCartSaving(true);
    const selectedCols = config?.cart?.rowDataMode === "selected" ? config.cart.selectedColumns : undefined;
    publish(`__comp_output__${componentId}__cart_save_trigger`, {
      selectedColumns: selectedCols,
    });

    if (cartSaveTimeoutRef.current) clearTimeout(cartSaveTimeoutRef.current);
    cartSaveTimeoutRef.current = setTimeout(() => {
      setCartSaving((prev) => {
        if (prev) {
          toast.error("장바구니 저장 응답이 없습니다. 연결 설정을 확인하세요.");
        }
        return false;
      });
    }, 10_000);
  }, [componentId, publish, config?.cart?.rowDataMode, config?.cart?.selectedColumns]);

  // 저장 완료 시 타임아웃 정리
  useEffect(() => {
    if (!cartSaving && cartSaveTimeoutRef.current) {
      clearTimeout(cartSaveTimeoutRef.current);
      cartSaveTimeoutRef.current = null;
    }
  }, [cartSaving]);

  // 입고 확정: 데이터 수집 → API 호출
  const handleInboundConfirm = useCallback(async () => {
    if (!componentId) return;
    setConfirmProcessing(true);

    try {
      // 동기적 이벤트 수집 (connectionResolver가 동기 중계)
      const responses: CollectedDataResponse[] = [];
      const unsub = subscribe(`__comp_input__${componentId}__collected_data`, (payload: unknown) => {
        const enriched = payload as { value?: CollectedDataResponse };
        if (enriched?.value) {
          responses.push(enriched.value);
        }
      });

      publish(`__comp_output__${componentId}__collect_data`, {
        requestId: crypto.randomUUID(),
        action: "inbound-confirm",
      });

      unsub();

      if (responses.length === 0) {
        toast.error("연결된 컴포넌트에서 데이터를 수집할 수 없습니다. 연결 설정을 확인하세요.");
        return;
      }

      const cardListData = responses.find((r) => r.componentType === "pop-card-list");
      const fieldData = responses.find((r) => r.componentType === "pop-field");

      const selectedItems = cardListData?.data?.items ?? [];
      if (selectedItems.length === 0) {
        toast.error("확정할 항목을 선택해주세요.");
        return;
      }

      const fieldValues = fieldData?.data?.values ?? {};

      const statusChangeRules = config?.statusChangeRules ?? config?.inboundConfirm?.statusChangeRules ?? [];

      const cardListMapping = cardListData?.mapping ?? null;
      const fieldMapping = fieldData?.mapping ?? null;

      const result = await apiClient.post("/pop/execute-action", {
        action: "inbound-confirm",
        data: {
          items: selectedItems,
          fieldValues,
        },
        mappings: {
          cardList: cardListMapping,
          field: fieldMapping,
        },
        statusChanges: statusChangeRules,
      });

      if (result.data?.success) {
        const codes: Array<{ targetColumn: string; code: string; showResultModal?: boolean }> =
          result.data.data?.generatedCodes ?? [];
        const modalCodes = codes.filter((c) => c.showResultModal);
        if (modalCodes.length > 0) {
          setGeneratedCodesResult(modalCodes);
        } else {
          toast.success(`${selectedItems.length}건 입고 확정 완료`);
          publish(`__comp_output__${componentId}__action_completed`, {
            action: "inbound-confirm",
            success: true,
            count: selectedItems.length,
          });
          const followUps = config?.followUpActions ?? [];
          for (const fa of followUps) {
            switch (fa.type) {
              case "navigate":
                if (fa.targetScreenId) {
                  publish("__pop_navigate__", { screenId: fa.targetScreenId, params: fa.params });
                }
                break;
              case "refresh":
                publish("__pop_refresh__");
                break;
              case "event":
                if (fa.eventName) publish(fa.eventName, fa.eventPayload);
                break;
            }
          }
        }
      } else {
        toast.error(result.data?.message || "입고 확정에 실패했습니다.");
      }
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "입고 확정 중 오류가 발생했습니다.";
      toast.error(message);
    } finally {
      setConfirmProcessing(false);
      setShowInboundConfirm(false);
    }
  }, [
    componentId,
    subscribe,
    publish,
    config?.statusChangeRules,
    config?.inboundConfirm?.statusChangeRules,
    config?.followUpActions,
  ]);

  // v2: 데이터 수집 → executeTaskList 호출
  const handleV2Execute = useCallback(async () => {
    if (!v2Config || !componentId) return;
    setConfirmProcessing(true);

    try {
      const responses: CollectedDataResponse[] = [];
      const unsub = subscribe(`__comp_input__${componentId}__collected_data`, (payload: unknown) => {
        const enriched = payload as { value?: CollectedDataResponse };
        if (enriched?.value) responses.push(enriched.value);
      });

      publish(`__comp_output__${componentId}__collect_data`, {
        requestId: crypto.randomUUID(),
        action: "task-list",
      });
      unsub();

      const cardListData = responses.find((r) => r.componentType === "pop-card-list");
      const fieldData = responses.find((r) => r.componentType === "pop-field");

      const collectedData: CollectedPayload = {
        items: cardListData?.data?.items ?? [],
        fieldValues: fieldData?.data?.values ?? {},
        mappings: {
          cardList: cardListData?.mapping ?? null,
          field: fieldData?.mapping ?? null,
        },
        cartChanges: (cardListData?.data as Record<string, unknown>)?.cartChanges as CollectedPayload["cartChanges"],
      };

      const result = await executeTaskList(v2Config.tasks, {
        publish,
        componentId,
        collectedData,
      });

      if (result.success) {
        const resultData = result.data as Record<string, unknown> | undefined;
        const generatedCodes = resultData?.generatedCodes as
          | Array<{ targetColumn: string; code: string; showResultModal?: boolean }>
          | undefined;
        const deferred = resultData?.deferredTasks as
          | Array<{ type: string; targetScreenId?: string; params?: Record<string, string> }>
          | undefined;

        if (generatedCodes?.some((g) => g.showResultModal)) {
          setGeneratedCodesResult(generatedCodes);
          if (deferred && deferred.length > 0) {
            setDeferredV2Tasks(deferred);
          }
        } else {
          toast.success("작업 완료");
        }
      } else {
        toast.error(result.error || "작업 실행에 실패했습니다.");
      }
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "작업 실행 중 오류가 발생했습니다.";
      toast.error(message);
    } finally {
      setConfirmProcessing(false);
    }
  }, [v2Config, componentId, subscribe, publish]);

  // 클릭 핸들러
  const handleClick = useCallback(async () => {
    if (isDesignMode) {
      const modeLabel = v2Config
        ? v2Config.tasks.map((t) => TASK_TYPE_LABELS[t.type]).join(" → ")
        : isCartMode
          ? "장바구니 저장"
          : isInboundConfirmMode
            ? "입고 확정"
            : ACTION_TYPE_LABELS[config?.action?.type || "save"];
      toast.info(`[디자인 모드] ${modeLabel}`);
      return;
    }

    // 장바구니 모드 (v1 preset: "cart" 또는 v2 tasks에 cart-save 포함)
    if (isCartMode) {
      if (cartCount === 0 && !cartIsDirty) {
        toast.info("장바구니가 비어 있습니다.");
        return;
      }

      if (cartIsDirty) {
        setShowCartConfirm(true);
      } else {
        const targetScreenId = resolvedCartScreenId;
        if (targetScreenId) {
          const cleanId = String(targetScreenId).replace(/^.*\/(\d+)$/, "$1").trim();
          window.location.href = `/pop/screens/${cleanId}`;
        } else {
          toast.info("장바구니 화면이 설정되지 않았습니다.");
        }
      }
      return;
    }

    // v2 경로: tasks 배열이 있으면 새 실행 엔진 사용 (cart-save 제외)
    if (v2Config) {
      if (v2Config.confirm?.enabled) {
        setShowInboundConfirm(true);
      } else {
        await handleV2Execute();
      }
      return;
    }

    // === 이하 v1 레거시 경로 ===

    // 입고 확정 모드: confirm 다이얼로그 후 데이터 수집 → API 호출
    if (isInboundConfirmMode) {
      if (config?.confirm?.enabled !== false) {
        setShowInboundConfirm(true);
      } else {
        await handleInboundConfirm();
      }
      return;
    }

    const action = config?.action;
    if (!action) return;

    await execute(action, {
      confirm: config?.confirm,
      followUpActions: config?.followUpActions,
    });
  }, [
    isDesignMode,
    v2Config,
    isCartMode,
    isInboundConfirmMode,
    config,
    cartCount,
    cartIsDirty,
    execute,
    handleCartSave,
    handleInboundConfirm,
    handleV2Execute,
  ]);

  // 외형
  const buttonLabel = config?.label || label || "버튼";
  const variant = config?.variant || "default";
  const iconName = config?.icon || "";
  const isIconOnly = config?.iconOnly || false;

  // 장바구니 3상태 아이콘: 빈 장바구니 / 저장 완료 / 변경사항 있음
  const cartIconName = useMemo(() => {
    if (!isCartMode) return iconName;
    if (cartCount === 0 && !cartIsDirty) return "ShoppingCart";
    if (cartCount > 0 && !cartIsDirty) return "ShoppingBag";
    return "ShoppingCart";
  }, [isCartMode, cartCount, cartIsDirty, iconName]);

  // 장바구니 3상태 버튼 색상
  const cartButtonClass = useMemo(() => {
    if (!isCartMode) return "";
    if (cartCount > 0 && !cartIsDirty) {
      return "bg-primary hover:bg-primary/90 text-primary-foreground border-primary";
    }
    if (cartIsDirty) {
      return "bg-warning hover:bg-warning/90 text-warning-foreground border-warning animate-pulse";
    }
    return "";
  }, [isCartMode, cartCount, cartIsDirty]);

  // 데이터 작업 버튼 2상태 아이콘: 미선택(기본) / 선택됨(아이콘 유지)
  const inboundIconName = useMemo(() => {
    if (!hasDataTasks && !isInboundConfirmMode) return iconName;
    return config?.icon || iconName || "PackageCheck";
  }, [hasDataTasks, isInboundConfirmMode, config?.icon, iconName]);

  // 데이터 작업 버튼 2상태 색상: 미선택(기본) / 선택됨(초록)
  const inboundButtonClass = useMemo(() => {
    if (isCartMode) return "";
    return inboundSelectedCount > 0 ? "bg-primary hover:bg-primary/90 text-primary-foreground border-primary" : "";
  }, [isCartMode, inboundSelectedCount]);

  return (
    <>
      <div className="flex h-full w-full items-center justify-center overflow-hidden">
        <div className="relative max-h-full">
          <Button
            variant={variant}
            onClick={handleClick}
            disabled={isLoading || cartSaving || confirmProcessing}
            className={cn(
              "max-h-full transition-transform active:scale-95",
              isIconOnly && "px-2",
              cartButtonClass,
              inboundButtonClass,
            )}
          >
            {(isCartMode ? cartIconName : hasDataTasks ? inboundIconName : iconName) && (
              <DynamicLucideIcon
                name={isCartMode ? cartIconName : hasDataTasks ? inboundIconName : iconName}
                size={16}
                className={isIconOnly ? "" : "mr-1.5"}
              />
            )}
            {!isIconOnly && <span>{buttonLabel}</span>}
          </Button>

          {/* 장바구니 배지 */}
          {isCartMode && cartCount > 0 && (
            <div
              className={cn(
                "absolute top-0 right-0 translate-x-1/3 -translate-y-1/3 flex items-center justify-center rounded-full text-[10px] font-bold",
                cartIsDirty ? "bg-warning text-warning-foreground" : "bg-primary text-primary-foreground",
              )}
              style={{ minWidth: 18, height: 18, padding: "0 4px" }}
            >
              {cartCount}
            </div>
          )}

          {/* 선택 개수 배지 (v1 inbound-confirm + v2 data tasks) */}
          {!isCartMode && hasDataTasks && inboundSelectedCount > 0 && (
            <div
              className="absolute top-0 right-0 translate-x-1/3 -translate-y-1/3 flex items-center justify-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground"
              style={{ minWidth: 18, height: 18, padding: "0 4px" }}
            >
              {inboundSelectedCount}
            </div>
          )}
        </div>
      </div>

      {/* 장바구니 확인 다이얼로그 */}
      <AlertDialog open={showCartConfirm} onOpenChange={setShowCartConfirm}>
        <AlertDialogContent className="max-w-[95vw] sm:max-w-[400px]">
          <AlertDialogHeader>
            <AlertDialogTitle className="text-base sm:text-lg">장바구니 저장</AlertDialogTitle>
            <AlertDialogDescription className="text-xs sm:text-sm">
              {config?.confirm?.message || `${cartCount}개 항목을 장바구니에 저장하시겠습니까?`}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter className="gap-2 sm:gap-0">
            <AlertDialogCancel className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">취소</AlertDialogCancel>
            <AlertDialogAction
              onClick={() => {
                setShowCartConfirm(false);
                handleCartSave();
              }}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              저장
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      {/* 확정/실행 확인 다이얼로그 (v2 + v1 입고확정 공용) */}
      <AlertDialog open={showInboundConfirm} onOpenChange={setShowInboundConfirm}>
        <AlertDialogContent className="max-w-[95vw] sm:max-w-[400px]">
          <AlertDialogHeader>
            <AlertDialogTitle className="text-base sm:text-lg">{v2Config ? "실행 확인" : "입고 확정"}</AlertDialogTitle>
            <AlertDialogDescription className="text-xs sm:text-sm">
              {v2Config
                ? v2Config.confirm?.message || "작업을 실행하시겠습니까?"
                : config?.confirm?.message || "선택한 품목을 입고 확정하시겠습니까?"}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter className="gap-2 sm:gap-0">
            <AlertDialogCancel
              disabled={confirmProcessing}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </AlertDialogCancel>
            <AlertDialogAction
              onClick={() => {
                v2Config ? handleV2Execute() : handleInboundConfirm();
              }}
              disabled={confirmProcessing}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              {confirmProcessing ? "처리 중..." : "확인"}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      {/* 채번 결과 다이얼로그 - 사용자가 확인 누를 때까지 유지 */}
      <AlertDialog
        open={generatedCodesResult.length > 0}
        onOpenChange={(open) => {
          if (!open) handleCloseGeneratedCodesModal();
        }}
      >
        <AlertDialogContent className="max-w-[95vw] sm:max-w-[400px]">
          <AlertDialogHeader>
            <AlertDialogTitle className="text-base sm:text-lg">자동 생성 완료</AlertDialogTitle>
            <AlertDialogDescription asChild>
              <div className="space-y-2 text-xs sm:text-sm">
                <p>다음 번호가 자동 생성되었습니다.</p>
                {generatedCodesResult.map((c, i) => (
                  <div key={i} className="flex items-center justify-between rounded-md border p-2">
                    <span className="text-muted-foreground">{c.targetColumn}</span>
                    <span className="font-mono font-semibold">{c.code}</span>
                  </div>
                ))}
              </div>
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogAction
              onClick={handleCloseGeneratedCodesModal}
              className="h-8 w-full text-xs sm:h-10 sm:text-sm"
            >
              확인
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      {/* 일반 확인 다이얼로그 */}
      <AlertDialog
        open={!!pendingConfirm}
        onOpenChange={(open) => {
          if (!open) cancelConfirm();
        }}
      >
        <AlertDialogContent className="max-w-[95vw] sm:max-w-[400px]">
          <AlertDialogHeader>
            <AlertDialogTitle className="text-base sm:text-lg">실행 확인</AlertDialogTitle>
            <AlertDialogDescription className="text-xs sm:text-sm">{getConfirmMessage()}</AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter className="gap-2 sm:gap-0">
            <AlertDialogCancel className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">취소</AlertDialogCancel>
            <AlertDialogAction
              onClick={confirmExecute}
              className={cn(
                "h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm",
                config?.action?.type === "delete" &&
                  "bg-destructive text-destructive-foreground hover:bg-destructive/90",
              )}
            >
              확인
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </>
  );
}

// ========================================
// STEP 3: 설정 패널 (v2 작업 목록 기반)
// ========================================

interface PopButtonConfigPanelProps {
  config: PopButtonConfig;
  onUpdate: (config: PopButtonConfig) => void;
  allComponents?: { id: string; type: string; config?: Record<string, unknown> }[];
  connections?: { sourceComponent: string; targetComponent: string; sourceOutput?: string; targetInput?: string }[];
  componentId?: string;
}

/** 연결된 컴포넌트에서 사용 가능한 필드 목록 추출 (연결 기반) */
function extractConnectedFields(
  componentId?: string,
  connections?: PopButtonConfigPanelProps["connections"],
  allComponents?: PopButtonConfigPanelProps["allComponents"],
): { value: string; label: string; source: string }[] {
  if (!componentId || !connections || !allComponents) return [];

  const targetIds = connections
    .filter((c) => c.sourceComponent === componentId || c.targetComponent === componentId)
    .map((c) => (c.sourceComponent === componentId ? c.targetComponent : c.sourceComponent));
  const uniqueIds = [...new Set(targetIds)];
  if (uniqueIds.length === 0) return [];

  const fields: { value: string; label: string; source: string }[] = [];

  for (const tid of uniqueIds) {
    const comp = allComponents.find((c) => c.id === tid);
    if (!comp?.config) continue;
    const cfg = comp.config as Record<string, unknown>;
    const compLabel = (comp as Record<string, unknown>).label as string || comp.type || tid;

    if (comp.type === "pop-card-list") {
      const tpl = cfg.cardTemplate as
        | { header?: Record<string, unknown>; body?: { fields?: { id?: string; label?: string; valueType?: string; columnName?: string }[] } }
        | undefined;
      if (tpl) {
        if (tpl.header?.codeField) {
          fields.push({ value: String(tpl.header.codeField), label: String(tpl.header.codeField), source: compLabel });
        }
        if (tpl.header?.titleField) {
          fields.push({ value: String(tpl.header.titleField), label: String(tpl.header.titleField), source: compLabel });
        }
        for (const f of tpl.body?.fields ?? []) {
          if (f.valueType === "column" && f.columnName) {
            fields.push({ value: f.columnName, label: f.label || f.columnName, source: compLabel });
          } else if (f.valueType === "formula" && f.label) {
            fields.push({ value: `__formula_${f.id || f.label}`, label: f.label, source: `${compLabel} (수식)` });
          }
        }
      }
      fields.push({ value: "__cart_quantity", label: "사용자 입력 수량", source: `${compLabel} (장바구니)` });
      fields.push({ value: "__cart_row_key", label: "선택한 카드의 원본 키", source: `${compLabel} (장바구니)` });
      fields.push({ value: "__cart_id", label: "장바구니 항목 ID", source: `${compLabel} (장바구니)` });
    }

    if (comp.type === "pop-field") {
      const sections = cfg.sections as Array<{
        fields?: Array<{ id: string; fieldName?: string; labelText?: string }>;
      }> | undefined;
      if (Array.isArray(sections)) {
        for (const section of sections) {
          for (const f of section.fields ?? []) {
            if (f.fieldName) {
              fields.push({ value: f.fieldName, label: f.labelText || f.fieldName, source: compLabel });
            }
          }
        }
      }
    }

    if (comp.type === "pop-search") {
      const filterCols = cfg.filterColumns as string[] | undefined;
      const modalCfg = cfg.modalConfig as { valueField?: string } | undefined;
      if (Array.isArray(filterCols) && filterCols.length > 0) {
        for (const col of filterCols) {
          fields.push({ value: col, label: col, source: compLabel });
        }
      } else if (modalCfg?.valueField) {
        fields.push({ value: modalCfg.valueField, label: modalCfg.valueField, source: compLabel });
      } else if (cfg.fieldName && typeof cfg.fieldName === "string") {
        fields.push({ value: cfg.fieldName, label: (cfg.placeholder as string) || cfg.fieldName, source: compLabel });
      }
    }
  }

  return fields;
}

export function PopButtonConfigPanel({
  config,
  onUpdate,
  allComponents,
  connections,
  componentId,
}: PopButtonConfigPanelProps) {
  const v2 = useMemo(() => migrateButtonConfig(config), [config]);
  const cardFields = useMemo(
    () => extractConnectedFields(componentId, connections, allComponents),
    [componentId, connections, allComponents],
  );

  const updateV2 = useCallback(
    (partial: Partial<PopButtonConfigV2>) => {
      const merged = { ...v2, ...partial };
      onUpdate(merged as unknown as PopButtonConfig);
    },
    [v2, onUpdate],
  );

  const updateTask = useCallback(
    (taskId: string, partial: Partial<ButtonTask>) => {
      const next = v2.tasks.map((t) => (t.id === taskId ? { ...t, ...partial } : t));
      updateV2({ tasks: next });
    },
    [v2.tasks, updateV2],
  );

  const removeTask = useCallback(
    (taskId: string) => {
      updateV2({ tasks: v2.tasks.filter((t) => t.id !== taskId) });
    },
    [v2.tasks, updateV2],
  );

  const addTask = useCallback(
    (type: ButtonTaskType) => {
      const id = `t${Date.now()}`;
      updateV2({ tasks: [...v2.tasks, { id, type }] });
    },
    [v2.tasks, updateV2],
  );

  // 빠른 시작
  const applyQuickStart = useCallback(
    (template: QuickStartTemplate) => {
      const defaults = QUICK_START_DEFAULTS[template];
      updateV2({
        label: defaults.label,
        variant: defaults.variant,
        icon: defaults.icon === "none" ? undefined : defaults.icon,
        confirm: defaults.confirm,
        tasks: defaults.tasks.map((t) => ({ ...t, id: `t${Date.now()}_${Math.random().toString(36).slice(2, 6)}` })),
      });
    },
    [updateV2],
  );

  // 작업 순서 이동
  const moveTask = useCallback(
    (taskId: string, direction: "up" | "down") => {
      const idx = v2.tasks.findIndex((t) => t.id === taskId);
      if (idx < 0) return;
      const swapIdx = direction === "up" ? idx - 1 : idx + 1;
      if (swapIdx < 0 || swapIdx >= v2.tasks.length) return;
      const next = [...v2.tasks];
      [next[idx], next[swapIdx]] = [next[swapIdx], next[idx]];
      updateV2({ tasks: next });
    },
    [v2.tasks, updateV2],
  );

  return (
    <div className="space-y-2 overflow-y-auto pr-1 pb-32">
      {/* 빠른 시작 */}
      <SectionDivider label="빠른 시작" />
      <div className="flex flex-wrap gap-1">
        {(Object.keys(QUICK_START_DEFAULTS) as QuickStartTemplate[]).map((key) => (
          <Button
            key={key}
            variant="outline"
            size="sm"
            className="h-7 text-[10px]"
            onClick={() => applyQuickStart(key)}
          >
            {QUICK_START_DEFAULTS[key].label}
          </Button>
        ))}
      </div>
      <p className="text-muted-foreground text-[10px]">클릭하면 외형과 작업 목록이 자동 설정됩니다</p>

      {/* 외형 설정 */}
      <SectionDivider label="외형" />
      <div className="space-y-2">
        <div>
          <Label className="text-xs">라벨</Label>
          <Input
            value={v2.label || ""}
            onChange={(e) => updateV2({ label: e.target.value })}
            placeholder="버튼 텍스트"
            className="h-8 text-xs"
          />
        </div>

        <div>
          <Label className="text-xs">스타일</Label>
          <Select value={v2.variant || "default"} onValueChange={(v) => updateV2({ variant: v as ButtonVariant })}>
            <SelectTrigger className="h-8 text-xs">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {Object.entries(VARIANT_LABELS).map(([key, lbl]) => (
                <SelectItem key={key} value={key} className="text-xs">
                  {lbl}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>

        <div>
          <Label className="text-xs">아이콘</Label>
          <Select value={v2.icon || "none"} onValueChange={(v) => updateV2({ icon: v === "none" ? undefined : v })}>
            <SelectTrigger className="h-8 text-xs">
              <SelectValue placeholder="없음" />
            </SelectTrigger>
            <SelectContent>
              {ICON_OPTIONS.map((opt) => (
                <SelectItem key={opt.value} value={opt.value} className="text-xs">
                  <div className="flex items-center gap-2">
                    {opt.value && <DynamicLucideIcon name={opt.value} size={14} />}
                    <span>{opt.label}</span>
                  </div>
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>

        <div className="flex items-center gap-2">
          <Checkbox
            id="iconOnlyV2"
            checked={v2.iconOnly || false}
            onCheckedChange={(checked) => updateV2({ iconOnly: checked === true })}
          />
          <Label htmlFor="iconOnlyV2" className="text-xs">
            아이콘만 표시 (라벨 숨김)
          </Label>
        </div>
      </div>

      {/* 확인 메시지 */}
      <SectionDivider label="확인 메시지" />
      <div className="space-y-2">
        <div className="flex items-center gap-2">
          <Checkbox
            id="confirmEnabledV2"
            checked={v2.confirm?.enabled || false}
            onCheckedChange={(checked) => updateV2({ confirm: { ...v2.confirm, enabled: checked === true } })}
          />
          <Label htmlFor="confirmEnabledV2" className="text-xs">
            실행 전 확인 메시지 표시
          </Label>
        </div>
        {v2.confirm?.enabled && (
          <Input
            value={v2.confirm?.message || ""}
            onChange={(e) => updateV2({ confirm: { ...v2.confirm, enabled: true, message: e.target.value } })}
            placeholder="비워두면 기본 메시지 사용"
            className="h-8 text-xs"
          />
        )}
      </div>

      {/* 작업 목록 */}
      <SectionDivider label="작업 목록" />
      <div className="space-y-2.5">
        {v2.tasks.length === 0 && (
          <p className="text-[11px] text-muted-foreground py-2 text-center">
            작업이 없습니다. 빠른 시작 또는 아래 버튼으로 추가하세요.
          </p>
        )}

        {v2.tasks.map((task, idx) => (
          <TaskItemEditor
            key={task.id}
            task={task}
            index={idx}
            totalCount={v2.tasks.length}
            onUpdate={(partial) => updateTask(task.id, partial)}
            onRemove={() => removeTask(task.id)}
            onMove={(dir) => moveTask(task.id, dir)}
            cardFields={cardFields}
          />
        ))}

        {/* 작업 추가 */}
        <Select onValueChange={(v) => addTask(v as ButtonTaskType)}>
          <SelectTrigger className="h-8 text-xs">
            <SelectValue placeholder="+ 작업 추가" />
          </SelectTrigger>
          <SelectContent>
            {(Object.entries(TASK_TYPE_LABELS) as [ButtonTaskType, string][]).map(([key, lbl]) => (
              <SelectItem key={key} value={key} className="text-xs">
                {lbl}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
    </div>
  );
}

// ========================================
// 작업 항목 에디터 (접힘/펼침)
// ========================================

/** 작업 항목의 요약 텍스트 생성 */
function buildTaskSummary(task: ButtonTask): string {
  switch (task.type) {
    case "data-update": {
      if (!task.targetTable) return "";
      const col = task.targetColumn ? `.${task.targetColumn}` : "";
      const opLabels: Record<string, string> = {
        assign: "값 지정",
        add: "더하기",
        subtract: "빼기",
        multiply: "곱하기",
        divide: "나누기",
        conditional: "조건 분기",
        "db-conditional": "조건 비교",
      };
      const op = opLabels[task.operationType || "assign"] || "";
      return `${task.targetTable}${col} ${op}`;
    }
    case "data-delete":
      return task.targetTable || "";
    case "navigate":
      return task.targetScreenId ? `화면 ${task.targetScreenId}` : "";
    case "modal-open":
      return task.modalTitle || task.modalScreenId || "";
    case "cart-save":
      return task.cartScreenId ? `화면 ${task.cartScreenId}` : "";
    case "api-call":
      return task.apiEndpoint || "";
    case "custom-event":
      return task.flowName || task.eventName || "";
    default:
      return "";
  }
}

function TaskItemEditor({
  task,
  index,
  totalCount,
  onUpdate,
  onRemove,
  onMove,
  cardFields,
}: {
  task: ButtonTask;
  index: number;
  totalCount: number;
  onUpdate: (partial: Partial<ButtonTask>) => void;
  onRemove: () => void;
  onMove: (direction: "up" | "down") => void;
  cardFields: { value: string; label: string; source: string }[];
}) {
  const [expanded, setExpanded] = useState(false);
  const designerCtx = usePopDesignerContext();
  const summary = buildTaskSummary(task);

  return (
    <div className="rounded-md border border-border">
      <div
        className="flex cursor-pointer items-center gap-1.5 px-2.5 py-2 hover:bg-muted/30"
        onClick={() => setExpanded(!expanded)}
      >
        <ChevronRight
          className={cn("h-3.5 w-3.5 shrink-0 transition-transform", expanded && "rotate-90")}
        />
        <div className="min-w-0 flex-1 overflow-hidden">
          <div className="flex items-baseline gap-1">
            <span className="shrink-0 whitespace-nowrap text-xs font-medium">
              {index + 1}. {TASK_TYPE_LABELS[task.type]}
            </span>
            {summary && (
              <span
                className="truncate text-[11px] text-muted-foreground"
                title={summary}
              >
                - {summary}
              </span>
            )}
          </div>
        </div>
        <div className="flex items-center gap-0.5 shrink-0">
          {index > 0 && (
            <Button variant="ghost" size="icon" className="h-6 w-6" onClick={(e) => { e.stopPropagation(); onMove("up"); }}>
              <ChevronRight className="h-3 w-3 -rotate-90" />
            </Button>
          )}
          {index < totalCount - 1 && (
            <Button variant="ghost" size="icon" className="h-6 w-6" onClick={(e) => { e.stopPropagation(); onMove("down"); }}>
              <ChevronRight className="h-3 w-3 rotate-90" />
            </Button>
          )}
          <Button
            variant="ghost"
            size="icon"
            className="h-6 w-6 text-destructive"
            onClick={(e) => { e.stopPropagation(); onRemove(); }}
          >
            <Trash2 className="h-3.5 w-3.5" />
          </Button>
        </div>
      </div>

      {expanded && (
        <div className="space-y-3 border-t px-2.5 py-3">
          <TaskDetailForm task={task} onUpdate={onUpdate} designerCtx={designerCtx} cardFields={cardFields} />
        </div>
      )}
    </div>
  );
}

// ========================================
// 작업별 설정 폼 (M-4)
// ========================================

function TaskDetailForm({
  task,
  onUpdate,
  designerCtx,
  cardFields,
}: {
  task: ButtonTask;
  onUpdate: (partial: Partial<ButtonTask>) => void;
  designerCtx: ReturnType<typeof usePopDesignerContext>;
  cardFields: { value: string; label: string; source: string }[];
}) {
  // 테이블/컬럼 조회 (data-update, data-delete용)
  const [tables, setTables] = useState<TableInfo[]>([]);
  const [columns, setColumns] = useState<ColumnInfo[]>([]);
  const needsTable = task.type === "data-update" || task.type === "data-delete";

  useEffect(() => {
    if (needsTable) fetchTableList().then(setTables);
  }, [needsTable]);

  useEffect(() => {
    if (needsTable && task.targetTable) {
      fetchTableColumns(task.targetTable).then(setColumns);
    } else {
      setColumns([]);
    }
  }, [needsTable, task.targetTable]);

  switch (task.type) {
    case "data-save":
      return (
        <p className="text-[11px] text-muted-foreground">
          연결된 입력 컴포넌트의 저장 매핑을 사용합니다. 별도 설정 불필요.
        </p>
      );

    case "data-update":
      return (
        <DataUpdateTaskForm
          task={task}
          onUpdate={onUpdate}
          tables={tables}
          columns={columns}
          cardFields={cardFields}
        />
      );

    case "data-delete":
      return (
        <div className="space-y-2">
          <Label className="text-xs font-medium">어떤 테이블에서 삭제할까요?</Label>
          <TableCombobox
            tables={tables}
            value={task.targetTable || ""}
            onSelect={(v) => onUpdate({ targetTable: v })}
          />
        </div>
      );

    case "cart-save":
      return (
        <div className="space-y-2">
          <Label className="text-xs font-medium">저장 후 이동할 화면 ID</Label>
          <Input
            value={task.cartScreenId || ""}
            onChange={(e) => onUpdate({ cartScreenId: e.target.value })}
            placeholder="비워두면 이동 없이 저장만"
            className="h-8 text-xs"
          />
        </div>
      );

    case "modal-open":
      return (
        <div className="space-y-3">
          <div className="space-y-1.5">
            <Label className="text-xs font-medium">모달 방식</Label>
            <Select
              value={task.modalMode || "fullscreen"}
              onValueChange={(v) => onUpdate({ modalMode: v as ModalMode })}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(MODAL_MODE_LABELS).map(([key, lbl]) => (
                  <SelectItem key={key} value={key} className="text-xs">
                    {lbl}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          {task.modalMode === "screen-ref" && (
            <div className="space-y-1.5">
              <Label className="text-xs font-medium">화면 ID</Label>
              <Input
                value={task.modalScreenId || ""}
                onChange={(e) => onUpdate({ modalScreenId: e.target.value })}
                placeholder="화면 ID"
                className="h-8 text-xs"
              />
            </div>
          )}
          <div className="space-y-1.5">
            <Label className="text-xs font-medium">모달 제목</Label>
            <Input
              value={task.modalTitle || ""}
              onChange={(e) => onUpdate({ modalTitle: e.target.value })}
              placeholder="모달 제목 (선택)"
              className="h-8 text-xs"
            />
          </div>
          {task.modalMode === "fullscreen" && designerCtx && (
            <div>
              {task.modalScreenId ? (
                <Button variant="outline" size="sm" className="h-8 w-full text-xs" onClick={() => designerCtx.navigateToCanvas(task.modalScreenId!)}>
                  모달 캔버스 열기
                </Button>
              ) : (
                <Button
                  variant="outline"
                  size="sm"
                  className="h-8 w-full text-xs"
                  onClick={() => {
                    const selectedId = designerCtx.selectedComponentId;
                    if (!selectedId) return;
                    const modalId = designerCtx.createModalCanvas(selectedId, task.modalTitle || "새 모달");
                    onUpdate({ modalScreenId: modalId });
                  }}
                >
                  모달 캔버스 생성
                </Button>
              )}
            </div>
          )}
        </div>
      );

    case "navigate":
      return (
        <div className="space-y-2">
          <Label className="text-xs font-medium">이동할 화면 ID</Label>
          <Input
            value={task.targetScreenId || ""}
            onChange={(e) => onUpdate({ targetScreenId: e.target.value })}
            placeholder="화면 ID 입력"
            className="h-8 text-xs"
          />
        </div>
      );

    case "api-call":
      return (
        <div className="space-y-3">
          <div className="space-y-1.5">
            <Label className="text-xs font-medium">엔드포인트</Label>
            <Input
              value={task.apiEndpoint || ""}
              onChange={(e) => onUpdate({ apiEndpoint: e.target.value })}
              placeholder="/api/..."
              className="h-8 text-xs"
            />
          </div>
          <div className="space-y-1.5">
            <Label className="text-xs font-medium">HTTP 메서드</Label>
            <Select
              value={task.apiMethod || "POST"}
              onValueChange={(v) => onUpdate({ apiMethod: v as "GET" | "POST" | "PUT" | "DELETE" })}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(API_METHOD_LABELS).map(([key, lbl]) => (
                  <SelectItem key={key} value={key} className="text-xs">
                    {lbl}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
        </div>
      );

    case "custom-event":
      return (
        <ControlFlowTaskForm
          task={task}
          onUpdate={onUpdate}
        />
      );

    case "refresh":
    case "close-modal":
      return (
        <p className="text-[11px] text-muted-foreground">설정 불필요</p>
      );

    default:
      return null;
  }
}

// ========================================
// 데이터 수정 작업 폼 (data-update 전용)
// ========================================

/** 연산 타입 한글 라벨 */
const OPERATION_LABELS: Record<string, { label: string; desc: string }> = {
  assign: { label: "값 지정", desc: "선택한 값으로 덮어씁니다" },
  add: { label: "더하기", desc: "기존 값에 더합니다" },
  subtract: { label: "빼기", desc: "기존 값에서 뺍니다" },
  multiply: { label: "곱하기", desc: "기존 값에 곱합니다" },
  divide: { label: "나누기", desc: "기존 값을 나눕니다" },
  conditional: { label: "조건 분기", desc: "입력된 값에 따라 다른 결과를 지정합니다" },
  "db-conditional": { label: "조건 비교", desc: "DB 컬럼 값을 비교해서 결과를 정합니다" },
};

/** 비교 연산자 한글 라벨 */
const COMPARE_OP_LABELS: Record<string, string> = {
  "=": "= (같으면)",
  "!=": "!= (다르면)",
  ">": "> (크면)",
  "<": "< (작으면)",
  ">=": ">= (이상이면)",
  "<=": "<= (이하이면)",
};

/** data-update 설정 요약 생성 */
function buildUpdateSummaryText(task: ButtonTask): string | null {
  if (!task.targetTable || !task.targetColumn) return null;
  const op = task.operationType || "assign";
  const opLabel = OPERATION_LABELS[op]?.label || op;

  if (op === "db-conditional") {
    const a = task.compareColumn || "?";
    const b = task.compareWith || "?";
    const opStr = task.compareOperator || ">=";
    return `[${task.targetTable}].${task.targetColumn}을 ${opLabel}로 변경\n${a} ${opStr} ${b} → "${task.dbThenValue || "?"}", 그 외 → "${task.dbElseValue || "?"}"`;
  }
  if (op === "conditional") {
    const conds = task.conditionalValue?.conditions ?? [];
    const lines = conds.map((c) => `${c.whenColumn} ${c.operator} ${c.whenValue} → "${c.thenValue}"`);
    const def = task.conditionalValue?.defaultValue;
    if (def) lines.push(`그 외 → "${def}"`);
    return `[${task.targetTable}].${task.targetColumn}을 ${opLabel}로 변경\n${lines.join("\n")}`;
  }

  const val = task.valueSource === "linked"
    ? `연결 데이터(${task.sourceField || "?"})`
    : `"${task.fixedValue || "?"}"`;
  return `[${task.targetTable}].${task.targetColumn} ${opLabel} ${val}`;
}

function DataUpdateTaskForm({
  task,
  onUpdate,
  tables,
  columns,
  cardFields,
}: {
  task: ButtonTask;
  onUpdate: (partial: Partial<ButtonTask>) => void;
  tables: TableInfo[];
  columns: ColumnInfo[];
  cardFields: { value: string; label: string; source: string }[];
}) {
  const [showAdvanced, setShowAdvanced] = useState(task.lookupMode === "manual");
  const conditions = task.conditionalValue?.conditions ?? [];
  const defaultValue = task.conditionalValue?.defaultValue ?? "";
  const isConditional = task.operationType === "conditional" || task.operationType === "db-conditional";
  const summaryText = buildUpdateSummaryText(task);

  const updateCondition = (cIdx: number, partial: Partial<(typeof conditions)[0]>) => {
    const next = [...conditions];
    next[cIdx] = { ...next[cIdx], ...partial };
    onUpdate({ conditionalValue: { conditions: next, defaultValue } });
  };

  const removeCondition = (cIdx: number) => {
    const next = [...conditions];
    next.splice(cIdx, 1);
    onUpdate({ conditionalValue: { conditions: next, defaultValue } });
  };

  const addCondition = () => {
    onUpdate({
      conditionalValue: {
        conditions: [...conditions, { whenColumn: "", operator: "=" as const, whenValue: "", thenValue: "" }],
        defaultValue,
      },
    });
  };

  return (
    <div className="space-y-3">
      {/* 1. 대상 테이블 */}
      <div className="space-y-1.5">
        <Label className="text-xs font-medium">어떤 테이블을 수정할까요?</Label>
        <TableCombobox
          tables={tables}
          value={task.targetTable || ""}
          onSelect={(v) => onUpdate({ targetTable: v, targetColumn: "" })}
        />
      </div>

      {/* 2. 변경 컬럼 */}
      {task.targetTable && (
        <div className="space-y-1.5">
          <Label className="text-xs font-medium">어떤 항목(컬럼)을 바꿀까요?</Label>
          <ColumnCombobox
            columns={columns}
            value={task.targetColumn || ""}
            onSelect={(v) => onUpdate({ targetColumn: v })}
          />
        </div>
      )}

      {/* 3. 연산 방식 */}
      {task.targetColumn && (
        <>
          <div className="space-y-1.5">
            <Label className="text-xs font-medium">어떻게 바꿀까요?</Label>
            <Select
              value={task.operationType || "assign"}
              onValueChange={(v) => onUpdate({ operationType: v as UpdateOperationType })}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(OPERATION_LABELS).map(([key, { label }]) => (
                  <SelectItem key={key} value={key} className="text-xs">{label}</SelectItem>
                ))}
              </SelectContent>
            </Select>
            <p className="text-[11px] text-muted-foreground">
              {OPERATION_LABELS[task.operationType || "assign"]?.desc}
            </p>
          </div>

          {/* 4. 단순 연산: 값 출처 + 값 입력 */}
          {!isConditional && (
            <div className="space-y-2">
              <div className="space-y-1.5">
                <Label className="text-xs font-medium">바꿀 값을 어디서 가져올까요?</Label>
                <Select
                  value={task.valueSource || "fixed"}
                  onValueChange={(v) => onUpdate({ valueSource: v as UpdateValueSource })}
                >
                  <SelectTrigger className="h-8 text-xs">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="fixed" className="text-xs">직접 입력</SelectItem>
                    <SelectItem value="linked" className="text-xs">연결된 데이터 가져오기</SelectItem>
                  </SelectContent>
                </Select>
              </div>
              {task.valueSource === "fixed" && (
                <div className="space-y-1.5">
                  <Label className="text-xs font-medium">변경할 값</Label>
                  <Input
                    value={task.fixedValue || ""}
                    onChange={(e) => onUpdate({ fixedValue: e.target.value })}
                    className="h-8 text-xs"
                    placeholder="변경할 값 입력"
                  />
                </div>
              )}

              {task.valueSource === "linked" && (
                <div className="space-y-1.5">
                  <Label className="text-xs font-medium">어떤 데이터를 사용할까요?</Label>
                  {cardFields.length > 0 ? (
                    <Select
                      value={task.sourceField || ""}
                      onValueChange={(v) => onUpdate({ sourceField: v })}
                    >
                      <SelectTrigger className="h-8 text-xs">
                        <SelectValue placeholder="데이터 선택" />
                      </SelectTrigger>
                      <SelectContent>
                        {cardFields.map((f) => (
                          <SelectItem key={f.value} value={f.value} className="text-xs">
                            <div className="flex flex-col">
                              <span>{f.label}</span>
                              <span className="text-[10px] text-muted-foreground">{f.source}</span>
                            </div>
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  ) : (
                    <Input
                      value={task.sourceField || ""}
                      onChange={(e) => onUpdate({ sourceField: e.target.value })}
                      className="h-8 text-xs"
                      placeholder="필드명 직접 입력 (예: qty)"
                    />
                  )}
                  <p className="text-[11px] text-muted-foreground">
                    {cardFields.length > 0
                      ? "연결된 컴포넌트의 데이터 중 하나를 선택합니다"
                      : "연결된 컴포넌트가 없습니다. 연결 탭에서 먼저 연결해주세요"}
                  </p>
                </div>
              )}
            </div>
          )}

          {/* 5. 조건 비교 (db-conditional) - 세로 스택 */}
          {task.operationType === "db-conditional" && (
            <div className="space-y-3 rounded-md border border-dashed border-muted-foreground/30 p-3">
              <p className="text-[11px] text-muted-foreground">
                DB 컬럼 값을 비교해서 결과를 정합니다
              </p>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">비교 컬럼</Label>
                <ColumnCombobox
                  columns={columns}
                  value={task.compareColumn ?? ""}
                  onSelect={(v) => onUpdate({ compareColumn: v })}
                  placeholder="비교할 컬럼 선택"
                />
              </div>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">비교 방법</Label>
                <Select
                  value={task.compareOperator ?? ">="}
                  onValueChange={(v) => onUpdate({ compareOperator: v as ButtonTask["compareOperator"] })}
                >
                  <SelectTrigger className="h-8 text-xs">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {Object.entries(COMPARE_OP_LABELS).map(([op, label]) => (
                      <SelectItem key={op} value={op} className="text-xs">{label}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">비교 대상</Label>
                <ColumnCombobox
                  columns={columns}
                  value={task.compareWith ?? ""}
                  onSelect={(v) => onUpdate({ compareWith: v })}
                  placeholder="비교 대상 컬럼 선택"
                />
              </div>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">조건이 맞으면</Label>
                <Input
                  value={task.dbThenValue ?? ""}
                  onChange={(e) => onUpdate({ dbThenValue: e.target.value })}
                  className="h-8 text-xs"
                  placeholder="예: 입고완료"
                />
              </div>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">조건이 틀리면</Label>
                <Input
                  value={task.dbElseValue ?? ""}
                  onChange={(e) => onUpdate({ dbElseValue: e.target.value })}
                  className="h-8 text-xs"
                  placeholder="예: 부분입고"
                />
              </div>
            </div>
          )}

          {/* 6. 조건 분기 (conditional) */}
          {task.operationType === "conditional" && (
            <div className="space-y-3 rounded-md border border-dashed border-muted-foreground/30 p-3">
              <p className="text-[11px] text-muted-foreground">
                입력된 값에 따라 다른 결과를 지정합니다
              </p>

              {conditions.map((cond, cIdx) => (
                <div key={cIdx} className="space-y-2 rounded border border-border bg-muted/20 p-2.5">
                  <div className="flex items-center justify-between">
                    <span className="text-xs font-medium">조건 {cIdx + 1}</span>
                    <Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => removeCondition(cIdx)}>
                      <X className="h-3.5 w-3.5" />
                    </Button>
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs">만약 이 컬럼이</Label>
                    <ColumnCombobox
                      columns={columns}
                      value={cond.whenColumn}
                      onSelect={(v) => updateCondition(cIdx, { whenColumn: v })}
                      placeholder="컬럼 선택"
                    />
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs">이 값과</Label>
                    <div className="flex gap-2">
                      <Select value={cond.operator} onValueChange={(v) => updateCondition(cIdx, { operator: v as "=" | "!=" | ">" | "<" | ">=" | "<=" })}>
                        <SelectTrigger className="h-8 w-24 shrink-0 text-xs"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          {Object.entries(COMPARE_OP_LABELS).map(([op, label]) => (
                            <SelectItem key={op} value={op} className="text-xs">{label}</SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                      <Input
                        value={cond.whenValue}
                        onChange={(e) => updateCondition(cIdx, { whenValue: e.target.value })}
                        className="h-8 flex-1 text-xs"
                        placeholder="비교할 값"
                      />
                    </div>
                  </div>
                  <div className="space-y-1.5">
                    <Label className="text-xs">이면 이 값으로 변경</Label>
                    <Input
                      value={cond.thenValue}
                      onChange={(e) => updateCondition(cIdx, { thenValue: e.target.value })}
                      className="h-8 text-xs"
                      placeholder="변경할 값"
                    />
                  </div>
                </div>
              ))}

              <Button variant="outline" size="sm" className="h-8 w-full text-xs" onClick={addCondition}>
                <Plus className="mr-1.5 h-3.5 w-3.5" /> 조건 추가
              </Button>

              <div className="space-y-1.5">
                <Label className="text-xs font-medium">위 조건에 해당하지 않으면</Label>
                <Input
                  value={defaultValue}
                  onChange={(e) => onUpdate({ conditionalValue: { conditions, defaultValue: e.target.value } })}
                  className="h-8 text-xs"
                  placeholder="기본값 입력"
                />
              </div>
            </div>
          )}

          {/* 7. 고급 설정 (조회 키) */}
          <div className="pt-1">
            <button
              type="button"
              className="flex w-full items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
              onClick={() => setShowAdvanced(!showAdvanced)}
            >
              <ChevronRight className={cn("h-3.5 w-3.5 transition-transform", showAdvanced && "rotate-90")} />
              <span>고급 설정 (대상 행 직접 지정)</span>
            </button>
            {showAdvanced && (
              <div className="mt-2 space-y-2 rounded-md border border-border bg-muted/10 p-3">
                <div className="space-y-1.5">
                  <Label className="text-xs font-medium">대상 행 찾기</Label>
                  <Select
                    value={task.lookupMode ?? "auto"}
                    onValueChange={(v) => onUpdate({ lookupMode: v as "auto" | "manual" })}
                  >
                    <SelectTrigger className="h-8 text-xs">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="auto" className="text-xs">자동 매칭 (권장)</SelectItem>
                      <SelectItem value="manual" className="text-xs">수동 지정</SelectItem>
                    </SelectContent>
                  </Select>
                  <p className="text-[11px] text-muted-foreground">
                    {task.lookupMode === "manual"
                      ? "카드 항목의 필드를 직접 지정하여 대상 행을 찾습니다"
                      : "카드 항목과 테이블 PK를 자동으로 매칭합니다"}
                  </p>
                </div>
                {task.lookupMode === "manual" && (
                  <div className="space-y-2">
                    <div className="space-y-1.5">
                      <Label className="text-xs">카드 항목 필드</Label>
                      <Select value={task.manualItemField ?? ""} onValueChange={(v) => onUpdate({ manualItemField: v })}>
                        <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="필드 선택" /></SelectTrigger>
                        <SelectContent>
                          {KNOWN_ITEM_FIELDS.map((f) => (
                            <SelectItem key={f.value} value={f.value} className="text-xs">
                              <div className="flex flex-col">
                                <span>{f.label}</span>
                                <span className="text-[10px] text-muted-foreground">{f.desc}</span>
                              </div>
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                    <div className="space-y-1.5">
                      <Label className="text-xs">대상 PK 컬럼</Label>
                      <ColumnCombobox
                        columns={columns}
                        value={task.manualPkColumn ?? ""}
                        onSelect={(v) => onUpdate({ manualPkColumn: v })}
                        placeholder="PK 컬럼 선택"
                      />
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>

          {/* 8. 설정 요약 */}
          {summaryText && (
            <div className="rounded-md bg-muted/50 p-3">
              <p className="mb-1 text-xs font-medium text-muted-foreground">설정 요약</p>
              <p className="whitespace-pre-line text-xs leading-relaxed">{summaryText}</p>
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ========================================
// 액션 세부 필드 (타입별)
// ========================================

function ActionDetailFields({
  action,
  onUpdate,
  disabled,
}: {
  action?: ButtonMainAction;
  onUpdate: (updates: Partial<ButtonMainAction>) => void;
  disabled?: boolean;
}) {
  // 디자이너 컨텍스트 (뷰어에서는 null)
  const designerCtx = usePopDesignerContext();
  const actionType = action?.type || "save";

  switch (actionType) {
    case "save":
    case "delete":
      return (
        <div>
          <Label className="text-xs">대상 테이블</Label>
          <Input
            value={action?.targetTable || ""}
            onChange={(e) => onUpdate({ targetTable: e.target.value })}
            placeholder="테이블명 입력"
            className="h-8 text-xs"
            disabled={disabled}
          />
        </div>
      );

    case "api":
      return (
        <div className="space-y-2">
          <div>
            <Label className="text-xs">엔드포인트</Label>
            <Input
              value={action?.apiEndpoint || ""}
              onChange={(e) => onUpdate({ apiEndpoint: e.target.value })}
              placeholder="/api/..."
              className="h-8 text-xs"
              disabled={disabled}
            />
          </div>
          <div>
            <Label className="text-xs">HTTP 메서드</Label>
            <Select
              value={action?.apiMethod || "POST"}
              onValueChange={(v) =>
                onUpdate({
                  apiMethod: v as "GET" | "POST" | "PUT" | "DELETE",
                })
              }
              disabled={disabled}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(API_METHOD_LABELS).map(([key, label]) => (
                  <SelectItem key={key} value={key} className="text-xs">
                    {label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
        </div>
      );

    case "modal":
      return (
        <div className="space-y-2">
          <div>
            <Label className="text-xs">모달 방식</Label>
            <Select
              value={action?.modalMode || "fullscreen"}
              onValueChange={(v) => onUpdate({ modalMode: v as ModalMode })}
              disabled={disabled}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {Object.entries(MODAL_MODE_LABELS).map(([key, label]) => (
                  <SelectItem key={key} value={key} className="text-xs">
                    {label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          {action?.modalMode === "screen-ref" && (
            <div>
              <Label className="text-xs">화면 ID</Label>
              <Input
                value={action?.modalScreenId || ""}
                onChange={(e) => onUpdate({ modalScreenId: e.target.value })}
                placeholder="화면 ID"
                className="h-8 text-xs"
                disabled={disabled}
              />
            </div>
          )}
          <div>
            <Label className="text-xs">모달 제목</Label>
            <Input
              value={action?.modalTitle || ""}
              onChange={(e) => onUpdate({ modalTitle: e.target.value })}
              placeholder="모달 제목 (선택)"
              className="h-8 text-xs"
              disabled={disabled}
            />
          </div>
          {/* 모달 캔버스 생성/열기 (fullscreen 모드 + 디자이너 내부) */}
          {action?.modalMode === "fullscreen" && designerCtx && (
            <div>
              {action?.modalScreenId ? (
                <Button
                  variant="outline"
                  size="sm"
                  className="h-8 w-full text-xs"
                  onClick={() => designerCtx.navigateToCanvas(action.modalScreenId!)}
                >
                  모달 캔버스 열기
                </Button>
              ) : (
                <Button
                  variant="outline"
                  size="sm"
                  className="h-8 w-full text-xs"
                  onClick={() => {
                    const selectedId = designerCtx.selectedComponentId;
                    if (!selectedId) return;
                    const modalId = designerCtx.createModalCanvas(selectedId, action?.modalTitle || "새 모달");
                    onUpdate({ modalScreenId: modalId });
                  }}
                >
                  모달 캔버스 생성
                </Button>
              )}
            </div>
          )}
        </div>
      );

    case "event":
      return (
        <div className="space-y-2">
          <div>
            <Label className="text-xs">이벤트명</Label>
            <Input
              value={action?.eventName || ""}
              onChange={(e) => onUpdate({ eventName: e.target.value })}
              placeholder="예: data-saved, item-selected"
              className="h-8 text-xs"
              disabled={disabled}
            />
          </div>
        </div>
      );

    default:
      return null;
  }
}

// ========================================
// 후속 액션 편집기
// ========================================

function FollowUpActionsEditor({
  actions,
  onUpdate,
}: {
  actions: FollowUpAction[];
  onUpdate: (actions: FollowUpAction[]) => void;
}) {
  // 추가
  const handleAdd = () => {
    onUpdate([...actions, { type: "event" }]);
  };

  // 삭제
  const handleRemove = (index: number) => {
    onUpdate(actions.filter((_, i) => i !== index));
  };

  // 수정
  const handleUpdate = (index: number, updates: Partial<FollowUpAction>) => {
    const newActions = [...actions];
    newActions[index] = { ...newActions[index], ...updates };
    onUpdate(newActions);
  };

  return (
    <div className="space-y-2">
      {actions.length === 0 && (
        <p className="text-muted-foreground text-[10px]">메인 액션 성공 후 순차 실행할 후속 동작</p>
      )}

      {actions.map((fa, idx) => (
        <div key={idx} className="space-y-1.5 rounded border p-2">
          <div className="flex items-center justify-between">
            <span className="text-muted-foreground text-[10px] font-medium">후속 {idx + 1}</span>
            <Button
              type="button"
              variant="ghost"
              size="sm"
              onClick={() => handleRemove(idx)}
              className="text-destructive h-5 px-1 text-[10px]"
            >
              삭제
            </Button>
          </div>

          {/* 타입 선택 */}
          <Select value={fa.type} onValueChange={(v) => handleUpdate(idx, { type: v as FollowUpActionType })}>
            <SelectTrigger className="h-7 text-xs">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {Object.entries(FOLLOWUP_TYPE_LABELS).map(([key, label]) => (
                <SelectItem key={key} value={key} className="text-xs">
                  {label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>

          {/* 타입별 추가 입력 */}
          {fa.type === "event" && (
            <Input
              value={fa.eventName || ""}
              onChange={(e) => handleUpdate(idx, { eventName: e.target.value })}
              placeholder="이벤트명"
              className="h-7 text-xs"
            />
          )}
          {fa.type === "navigate" && (
            <Input
              value={fa.targetScreenId || ""}
              onChange={(e) => handleUpdate(idx, { targetScreenId: e.target.value })}
              placeholder="화면 ID"
              className="h-7 text-xs"
            />
          )}
        </div>
      ))}

      <Button type="button" variant="outline" size="sm" onClick={handleAdd} className="h-7 w-full text-xs">
        후속 액션 추가
      </Button>
    </div>
  );
}

// ========================================
// STEP 4: 미리보기 + 레지스트리 등록
// ========================================

function PopButtonPreviewComponent({ config }: { config?: PopButtonConfig }) {
  const buttonLabel = config?.label || "버튼";
  const variant = config?.variant || "default";
  const iconName = config?.icon || "";
  const isIconOnly = config?.iconOnly || false;

  return (
    <div className="flex h-full w-full items-center justify-center overflow-hidden">
      <Button variant={variant} className={cn("pointer-events-none", isIconOnly && "px-2")} tabIndex={-1}>
        {iconName && <DynamicLucideIcon name={iconName} size={16} className={isIconOnly ? "" : "mr-1.5"} />}
        {!isIconOnly && <span>{buttonLabel}</span>}
      </Button>
    </div>
  );
}

// ========================================
// 상태 변경 규칙 편집기
// ========================================

const KNOWN_ITEM_FIELDS = [
  { value: "__cart_row_key", label: "카드 항목의 원본 키", desc: "DB에서 가져온 데이터의 PK (가장 일반적)" },
  { value: "__cart_id", label: "카드 항목 ID", desc: "장바구니 내부 고유 ID" },
  { value: "id", label: "id", desc: "데이터의 id 컬럼" },
  { value: "row_key", label: "row_key", desc: "데이터의 row_key 컬럼" },
];

function StatusChangeRuleEditor({
  rules,
  onUpdate,
}: {
  rules: StatusChangeRule[];
  onUpdate: (rules: StatusChangeRule[]) => void;
}) {
  const [tables, setTables] = useState<TableInfo[]>([]);
  const [columnsMap, setColumnsMap] = useState<Record<string, ColumnInfo[]>>({});

  useEffect(() => {
    fetchTableList().then(setTables);
  }, []);

  const loadColumns = (tableName: string) => {
    if (!tableName || columnsMap[tableName]) return;
    fetchTableColumns(tableName).then((cols) => {
      setColumnsMap((prev) => ({ ...prev, [tableName]: cols }));
    });
  };

  const updateRule = (idx: number, partial: Partial<StatusChangeRule>) => {
    const next = [...rules];
    next[idx] = { ...next[idx], ...partial };
    onUpdate(next);
  };

  const removeRule = (idx: number) => {
    const next = [...rules];
    next.splice(idx, 1);
    onUpdate(next);
  };

  const addRule = () => {
    onUpdate([
      ...rules,
      {
        targetTable: "",
        targetColumn: "",
        valueType: "fixed",
        fixedValue: "",
      },
    ]);
  };

  return (
    <div className="space-y-2 px-1">
      {rules.map((rule, idx) => (
        <SingleRuleEditor
          key={idx}
          rule={rule}
          idx={idx}
          tables={tables}
          columns={columnsMap[rule.targetTable] ?? []}
          onLoadColumns={loadColumns}
          onUpdate={(partial) => updateRule(idx, partial)}
          onRemove={() => removeRule(idx)}
        />
      ))}
      <Button variant="outline" size="sm" className="w-full text-xs" onClick={addRule}>
        <Plus className="mr-1 h-3 w-3" />
        규칙 추가
      </Button>
    </div>
  );
}

function SingleRuleEditor({
  rule,
  idx,
  tables,
  columns,
  onLoadColumns,
  onUpdate,
  onRemove,
}: {
  rule: StatusChangeRule;
  idx: number;
  tables: TableInfo[];
  columns: ColumnInfo[];
  onLoadColumns: (tableName: string) => void;
  onUpdate: (partial: Partial<StatusChangeRule>) => void;
  onRemove: () => void;
}) {
  useEffect(() => {
    if (rule.targetTable) onLoadColumns(rule.targetTable);
  }, [rule.targetTable]); // eslint-disable-line react-hooks/exhaustive-deps

  const conditions = rule.conditionalValue?.conditions ?? [];
  const defaultValue = rule.conditionalValue?.defaultValue ?? "";

  const updateCondition = (cIdx: number, partial: Partial<(typeof conditions)[0]>) => {
    const next = [...conditions];
    next[cIdx] = { ...next[cIdx], ...partial };
    onUpdate({
      conditionalValue: { ...rule.conditionalValue, conditions: next, defaultValue },
    });
  };

  const removeCondition = (cIdx: number) => {
    const next = [...conditions];
    next.splice(cIdx, 1);
    onUpdate({
      conditionalValue: { ...rule.conditionalValue, conditions: next, defaultValue },
    });
  };

  const addCondition = () => {
    onUpdate({
      conditionalValue: {
        ...rule.conditionalValue,
        conditions: [...conditions, { whenColumn: "", operator: "=" as const, whenValue: "", thenValue: "" }],
        defaultValue,
      },
    });
  };

  return (
    <div className="border-border space-y-2 rounded border p-2">
      <div className="flex items-center justify-between">
        <span className="text-muted-foreground text-[10px] font-medium">규칙 {idx + 1}</span>
        <Button variant="ghost" size="icon" className="h-5 w-5" onClick={onRemove}>
          <Trash2 className="h-3 w-3" />
        </Button>
      </div>

      {/* 대상 테이블 */}
      <div className="space-y-1">
        <Label className="text-[10px]">대상 테이블</Label>
        <TableCombobox
          tables={tables}
          value={rule.targetTable}
          onSelect={(v) => onUpdate({ targetTable: v, targetColumn: "" })}
        />
      </div>

      {/* 변경 컬럼 */}
      {rule.targetTable && (
        <div className="space-y-1">
          <Label className="text-[10px]">변경 컬럼</Label>
          <ColumnCombobox columns={columns} value={rule.targetColumn} onSelect={(v) => onUpdate({ targetColumn: v })} />
        </div>
      )}

      {/* 조회 키 */}
      {rule.targetColumn && (
        <div className="space-y-1">
          <div className="flex items-center gap-2">
            <Label className="text-[10px]">조회 키</Label>
            <Select
              value={rule.lookupMode ?? "auto"}
              onValueChange={(v) => onUpdate({ lookupMode: v as "auto" | "manual" })}
            >
              <SelectTrigger className="h-6 w-16 text-[10px]">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="auto" className="text-[10px]">
                  자동
                </SelectItem>
                <SelectItem value="manual" className="text-[10px]">
                  수동
                </SelectItem>
              </SelectContent>
            </Select>
          </div>
          {(rule.lookupMode ?? "auto") === "auto" ? (
            <p className="text-muted-foreground text-[10px]">
              {rule.targetTable === "cart_items"
                ? `카드 항목.__cart_id → ${rule.targetTable}.id`
                : `카드 항목.row_key → ${rule.targetTable}.${columns.find((c) => c.isPrimaryKey)?.name ?? "PK(조회중)"}`}
            </p>
          ) : (
            <div className="flex items-center gap-1">
              <Select value={rule.manualItemField ?? ""} onValueChange={(v) => onUpdate({ manualItemField: v })}>
                <SelectTrigger className="h-7 flex-1 text-[10px]">
                  <SelectValue placeholder="카드 항목 필드" />
                </SelectTrigger>
                <SelectContent>
                  {KNOWN_ITEM_FIELDS.map((f) => (
                    <SelectItem key={f.value} value={f.value} className="text-[10px]">
                      {f.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <span className="text-muted-foreground shrink-0 text-[10px]">→</span>
              <ColumnCombobox
                columns={columns}
                value={rule.manualPkColumn ?? ""}
                onSelect={(v) => onUpdate({ manualPkColumn: v })}
                placeholder="대상 PK 컬럼"
              />
            </div>
          )}
        </div>
      )}

      {/* 변경 값 타입 */}
      {rule.targetColumn && (
        <>
          <div className="space-y-1">
            <Label className="text-[10px]">변경 값</Label>
            <div className="flex items-center gap-3">
              <label className="flex items-center gap-1 text-[10px]">
                <input
                  type="radio"
                  name={`valueType-${idx}`}
                  checked={rule.valueType === "fixed"}
                  onChange={() => onUpdate({ valueType: "fixed" })}
                  className="h-3 w-3"
                />
                고정값
              </label>
              <label className="flex items-center gap-1 text-[10px]">
                <input
                  type="radio"
                  name={`valueType-${idx}`}
                  checked={rule.valueType === "conditional"}
                  onChange={() => onUpdate({ valueType: "conditional" })}
                  className="h-3 w-3"
                />
                조건부
              </label>
            </div>
          </div>

          {/* 고정값 */}
          {rule.valueType === "fixed" && (
            <div className="space-y-1">
              <Input
                value={rule.fixedValue ?? ""}
                onChange={(e) => onUpdate({ fixedValue: e.target.value })}
                className="h-7 text-xs"
                placeholder="변경할 값 입력"
              />
            </div>
          )}

          {/* 조건부 */}
          {rule.valueType === "conditional" && (
            <div className="border-muted-foreground/30 space-y-2 rounded border border-dashed p-2">
              {conditions.map((cond, cIdx) => (
                <div key={cIdx} className="space-y-1">
                  <div className="flex items-center gap-1">
                    <span className="text-muted-foreground shrink-0 text-[10px]">만약</span>
                    <ColumnCombobox
                      columns={columns}
                      value={cond.whenColumn}
                      onSelect={(v) => updateCondition(cIdx, { whenColumn: v })}
                      placeholder="컬럼"
                    />
                    <Select
                      value={cond.operator}
                      onValueChange={(v) =>
                        updateCondition(cIdx, { operator: v as "=" | "!=" | ">" | "<" | ">=" | "<=" })
                      }
                    >
                      <SelectTrigger className="h-7 w-14 text-[10px]">
                        <SelectValue />
                      </SelectTrigger>
                      <SelectContent>
                        {["=", "!=", ">", "<", ">=", "<="].map((op) => (
                          <SelectItem key={op} value={op} className="text-xs">
                            {op}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                    <Input
                      value={cond.whenValue}
                      onChange={(e) => updateCondition(cIdx, { whenValue: e.target.value })}
                      className="h-7 w-16 text-[10px]"
                      placeholder="값"
                    />
                    <Button
                      variant="ghost"
                      size="icon"
                      className="h-5 w-5 shrink-0"
                      onClick={() => removeCondition(cIdx)}
                    >
                      <X className="h-3 w-3" />
                    </Button>
                  </div>
                  <div className="flex items-center gap-1 pl-4">
                    <span className="text-muted-foreground shrink-0 text-[10px]">이면 -&gt;</span>
                    <Input
                      value={cond.thenValue}
                      onChange={(e) => updateCondition(cIdx, { thenValue: e.target.value })}
                      className="h-7 text-[10px]"
                      placeholder="변경할 값"
                    />
                  </div>
                </div>
              ))}
              <Button variant="ghost" size="sm" className="w-full text-[10px]" onClick={addCondition}>
                <Plus className="mr-1 h-3 w-3" />
                조건 추가
              </Button>
              <div className="flex items-center gap-1">
                <span className="text-muted-foreground shrink-0 text-[10px]">그 외 -&gt;</span>
                <Input
                  value={defaultValue}
                  onChange={(e) =>
                    onUpdate({
                      conditionalValue: { ...rule.conditionalValue, conditions, defaultValue: e.target.value },
                    })
                  }
                  className="h-7 text-[10px]"
                  placeholder="기본값"
                />
              </div>
            </div>
          )}
        </>
      )}
    </div>
  );
}

// ========================================
// 제어 실행 작업 폼 (custom-event -> 제어 플로우)
// ========================================

function ControlFlowTaskForm({
  task,
  onUpdate,
}: {
  task: ButtonTask;
  onUpdate: (partial: Partial<ButtonTask>) => void;
}) {
  const [flows, setFlows] = useState<{ flowId: number; flowName: string; flowDescription?: string }[]>([]);
  const [loading, setLoading] = useState(false);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    setLoading(true);
    apiClient
      .get("/dataflow/node-flows")
      .then((res) => {
        const data = res.data?.data ?? res.data ?? [];
        if (Array.isArray(data)) {
          setFlows(data);
        }
      })
      .catch(() => setFlows([]))
      .finally(() => setLoading(false));
  }, []);

  const selectedFlow = flows.find((f) => f.flowId === task.flowId);

  return (
    <div className="space-y-2">
      <Label className="text-xs font-medium">제어 플로우 선택</Label>
      {loading ? (
        <p className="text-[11px] text-muted-foreground">목록 불러오는 중...</p>
      ) : (
        <Popover open={open} onOpenChange={setOpen}>
          <PopoverTrigger asChild>
            <Button
              variant="outline"
              role="combobox"
              aria-expanded={open}
              className="h-8 w-full justify-between text-xs"
            >
              {selectedFlow ? selectedFlow.flowName : "플로우를 선택하세요"}
              <ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
            </Button>
          </PopoverTrigger>
          <PopoverContent
            className="p-0"
            style={{ width: "var(--radix-popover-trigger-width)" }}
            align="start"
          >
            <Command>
              <CommandInput placeholder="플로우 검색..." className="text-xs" />
              <CommandList>
                <CommandEmpty className="p-2 text-center text-xs">
                  검색 결과 없음
                </CommandEmpty>
                <CommandGroup>
                  {flows.map((f) => (
                    <CommandItem
                      key={f.flowId}
                      value={f.flowName}
                      onSelect={() => {
                        onUpdate({
                          flowId: f.flowId,
                          flowName: f.flowName,
                          eventName: `__node_flow_${f.flowId}`,
                        });
                        setOpen(false);
                      }}
                      className="text-xs"
                    >
                      <Check
                        className={cn(
                          "mr-2 h-3 w-3",
                          task.flowId === f.flowId ? "opacity-100" : "opacity-0",
                        )}
                      />
                      <div className="flex flex-col">
                        <span>{f.flowName}</span>
                        {f.flowDescription && (
                          <span className="text-[10px] text-muted-foreground">
                            {f.flowDescription}
                          </span>
                        )}
                      </div>
                    </CommandItem>
                  ))}
                </CommandGroup>
              </CommandList>
            </Command>
          </PopoverContent>
        </Popover>
      )}
    </div>
  );
}

// 레지스트리 등록
PopComponentRegistry.registerComponent({
  id: "pop-button",
  name: "버튼",
  description: "액션 버튼 (저장/삭제/API/모달/이벤트/장바구니)",
  category: "action",
  icon: "MousePointerClick",
  component: PopButtonComponent,
  configPanel: PopButtonConfigPanel,
  preview: PopButtonPreviewComponent,
  defaultProps: {
    label: "버튼",
    variant: "default",
    preset: "custom",
    confirm: { enabled: false },
    action: { type: "save" },
  } as PopButtonConfig,
  connectionMeta: {
    sendable: [
      {
        key: "cart_save_trigger",
        label: "저장 요청",
        type: "event",
        category: "event",
        description: "장바구니 일괄 저장 요청 (카드 목록에 전달)",
      },
      {
        key: "collect_data",
        label: "데이터 수집 요청",
        type: "event",
        category: "event",
        description: "연결된 컴포넌트에 데이터+매핑 수집 요청",
      },
      {
        key: "action_completed",
        label: "액션 완료",
        type: "event",
        category: "event",
        description: "확정/저장 완료 후 결과 전달",
      },
    ],
    receivable: [
      {
        key: "cart_updated",
        label: "장바구니 상태",
        type: "event",
        category: "event",
        description: "카드 목록에서 장바구니 변경 시 count/isDirty 수신",
      },
      {
        key: "cart_save_completed",
        label: "저장 완료",
        type: "event",
        category: "event",
        description: "카드 목록에서 DB 저장 완료 시 수신",
      },
      {
        key: "collected_data",
        label: "수집된 데이터",
        type: "event",
        category: "event",
        description: "컴포넌트에서 수집한 데이터+매핑 응답",
      },
      {
        key: "selected_items",
        label: "선택된 항목",
        type: "event",
        category: "event",
        description: "카드 목록에서 체크박스로 선택된 항목 수 수신",
      },
    ],
  },
  touchOptimized: true,
  supportedDevices: ["mobile", "tablet"],
});
