"use client";

/**
 * 시퀀스 관리 (채번 규칙) — 사용자가 코드 패턴(년도 + 4자리 순번 + 카테고리…)을 조합해서 만들고,
 * 테이블 타입 관리의 numbering 타입 컬럼이 이 룰을 가져다 사용한다.
 *
 *  - 좌측: 회사 단위 룰 목록 (검색·신규)
 *  - 우측: 선택된 룰의 빌더
 *      · 기본 정보 (룰명, 적용범위, 구분자, 리셋주기, 적용 테이블/컬럼)
 *      · 파트 토큰 빌더 (순번 / 날짜 / 문자 / 숫자 / 카테고리 / 참조)
 *      · 라이브 미리보기
 */
import { useCallback, useEffect, useMemo, useState } from "react";
import { Plus, RefreshCw, Search, Trash2, GripVertical, Eye, RotateCcw, Save, X, Check, ChevronsUpDown } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import {
  getNumberingRules,
  getNumberingRuleById,
  createNumberingRule,
  updateNumberingRule,
  deleteNumberingRule,
  previewNumberingCode,
  resetSequence,
} from "@/lib/api/numberingRule";
import { tableManagementApi, type TableInfo, type ColumnTypeInfo } from "@/lib/api/tableManagement";
import {
  CODE_PART_TYPE_OPTIONS,
  DATE_FORMAT_OPTIONS,
  RESET_PERIOD_OPTIONS,
  SEPARATOR_OPTIONS,
  type CodePartType,
  type DateFormat,
  type NumberingRuleConfig,
  type NumberingRulePart,
} from "@/types/numbering-rule";
import { cn } from "@/lib/utils";

// 토큰 색상 매핑
const PART_COLOR: Record<CodePartType, string> = {
  sequence: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/30 dark:text-blue-300",
  number: "bg-indigo-50 text-indigo-700 border-indigo-200 dark:bg-indigo-950/30 dark:text-indigo-300",
  date: "bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/30 dark:text-amber-300",
  text: "bg-slate-50 text-slate-700 border-slate-200 dark:bg-slate-900/50 dark:text-slate-300",
  category: "bg-teal-50 text-teal-700 border-teal-200 dark:bg-teal-950/30 dark:text-teal-300",
  reference: "bg-violet-50 text-violet-700 border-violet-200 dark:bg-violet-950/30 dark:text-violet-300",
};

const PART_LABEL: Record<CodePartType, string> = {
  sequence: "순번",
  number: "숫자",
  date: "날짜",
  text: "문자",
  category: "카테고리",
  reference: "참조",
};

// ── 새 파트 기본값 ─────────────────────────────────────────────
function makeDefaultPart(type: CodePartType, order: number): NumberingRulePart {
  const base: NumberingRulePart = {
    id: `p_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
    order,
    partType: type,
    generationMethod: "auto",
  };
  switch (type) {
    case "sequence":
      return { ...base, autoConfig: { sequenceLength: 4, startFrom: 1 } };
    case "number":
      return { ...base, autoConfig: { numberLength: 3, numberValue: 0 } };
    case "date":
      return { ...base, autoConfig: { dateFormat: "YYYY" } };
    case "text":
      return { ...base, autoConfig: { textValue: "CODE" } };
    case "category":
      return { ...base, autoConfig: { categoryKey: "" } };
    case "reference":
      // 참조 파트는 모드 셀렉트 — 기본은 마스터 조회 (사용자가 가장 자주 쓰는 패턴)
      return {
        ...base,
        autoConfig: {
          mode: "master",
          sourceTableName: undefined,
          sourceColumnName: undefined,
          referenceColumnName: "",
        } as any,
      };
  }
}

// 토큰 라벨에 표시할 요약 문구
function partSummary(p: NumberingRulePart): string {
  const c = (p.autoConfig ?? {}) as any;
  switch (p.partType) {
    case "sequence":
      return `${c.sequenceLength ?? 3}자리`;
    case "number":
      return c.numberValue != null ? String(c.numberValue) : `${c.numberLength ?? 3}자리`;
    case "date":
      return c.dateFormat ?? "YYYYMMDD";
    case "text":
      return c.textValue ? `"${c.textValue}"` : "(빈값)";
    case "category":
      return c.categoryKey || "(미설정)";
    case "reference": {
      const mode = c.mode ?? (c.sourceTableName ? "master" : c.constantValue ? "constant" : "form");
      if (mode === "constant") return `"${c.constantValue ?? ""}"`;
      if (mode === "master") return `${c.sourceTableName ?? "?"}.${c.sourceColumnName ?? "?"}`;
      return c.referenceColumnName || "(미설정)";
    }
    default:
      return "";
  }
}

// 로컬 미리보기 — 서버 호출 없이 즉시 표시 (sequence/category/reference 는 서버 발번값을 알 수 없어 placeholder)
function localPreview(parts: NumberingRulePart[], separator: string): string {
  const sorted = [...parts].sort((a, b) => a.order - b.order);
  const tokens = sorted.map((p) => {
    const c = p.autoConfig ?? {};
    switch (p.partType) {
      case "sequence": {
        const len = c.sequenceLength ?? 3;
        return String(c.startFrom ?? 1).padStart(len, "0");
      }
      case "number": {
        if (c.numberValue != null) {
          const len = c.numberLength ?? String(c.numberValue).length;
          return String(c.numberValue).padStart(len, "0");
        }
        return "".padStart(c.numberLength ?? 3, "0");
      }
      case "date": {
        const d = new Date();
        const y = d.getFullYear();
        const m = String(d.getMonth() + 1).padStart(2, "0");
        const dd = String(d.getDate()).padStart(2, "0");
        switch (c.dateFormat as DateFormat | undefined) {
          case "YYYY": return String(y);
          case "YY":   return String(y).slice(-2);
          case "YYYYMM": return `${y}${m}`;
          case "YYMM":   return `${String(y).slice(-2)}${m}`;
          case "YYMMDD": return `${String(y).slice(-2)}${m}${dd}`;
          case "YYYYMMDD":
          default:       return `${y}${m}${dd}`;
        }
      }
      case "text":
        return c.textValue ?? "";
      case "category":
        return `{${c.categoryKey || "category"}}`;
      case "reference": {
        const mode = (c as any).mode ?? (c.sourceTableName ? "master" : (c as any).constantValue ? "constant" : "form");
        if (mode === "constant") return String((c as any).constantValue ?? "");
        if (mode === "master") {
          const tbl = c.sourceTableName || "?";
          const col = c.sourceColumnName || "?";
          return `{${tbl}.${col}}`;
        }
        return `{${c.referenceColumnName || "ref"}}`;
      }
      default:
        return "";
    }
  });
  // 파트별 separatorAfter 우선
  let out = "";
  tokens.forEach((t, i) => {
    out += t;
    if (i < tokens.length - 1) {
      // separatorAfter 는 파트 레벨 또는 autoConfig 안에 둘 다 들어올 수 있음 (서버 호환).
      const sep = sorted[i].separatorAfter
        ?? (sorted[i].autoConfig as Record<string, unknown> | undefined)?.separatorAfter as string | undefined
        ?? separator;
      out += sep;
    }
  });
  return out;
}

// ── 메인 페이지 ───────────────────────────────────────────────
export default function SequenceMngPage() {
  const [list, setList] = useState<NumberingRuleConfig[]>([]);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState("");
  const [selectedId, setSelectedId] = useState<string | null>(null);

  // 우측 편집 상태 (draft)
  const [draft, setDraft] = useState<NumberingRuleConfig | null>(null);
  const [saving, setSaving] = useState(false);
  const [serverPreview, setServerPreview] = useState<string>("");

  // 테이블/컬럼 카탈로그 — 적용 테이블/컬럼·참조 파트 셀렉트용
  const [tables, setTables] = useState<TableInfo[]>([]);
  const [columnsByTable, setColumnsByTable] = useState<Record<string, ColumnTypeInfo[]>>({});

  const loadList = useCallback(async () => {
    setLoading(true);
    try {
      const res = await getNumberingRules();
      if (res.success) setList(res.data ?? []);
      else toast.error(res.error ?? "목록 조회 실패");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => { loadList(); }, [loadList]);

  // 테이블 목록 1회 로드
  useEffect(() => {
    (async () => {
      const r = await tableManagementApi.getTableList();
      if (r.success && r.data) setTables(r.data);
    })();
  }, []);

  // 테이블 선택 시 컬럼 lazy-load (캐시)
  const loadColumnsFor = useCallback(async (tableName: string) => {
    if (!tableName) return;
    if (columnsByTable[tableName]) return; // 이미 로드됨
    const r = await tableManagementApi.getColumnList(tableName);
    if (r.success && r.data?.columns) {
      setColumnsByTable((prev) => ({ ...prev, [tableName]: r.data!.columns }));
    }
  }, [columnsByTable]);

  // draft 의 적용 테이블이 바뀔 때마다 컬럼 미리 로드
  useEffect(() => {
    if (draft?.tableName) loadColumnsFor(draft.tableName);
  }, [draft?.tableName, loadColumnsFor]);

  // draft 의 reference 파트들이 참조하는 테이블들도 미리 로드
  useEffect(() => {
    if (!draft?.parts) return;
    const refTables = draft.parts
      .filter((p) => p.partType === "reference")
      .map((p) => (p.autoConfig as Record<string, any> | undefined)?.sourceTableName as string | undefined)
      .filter((t): t is string => !!t);
    refTables.forEach((t) => loadColumnsFor(t));
  }, [draft?.parts, loadColumnsFor]);

  // 선택 시 상세 조회
  useEffect(() => {
    if (!selectedId) { setDraft(null); setServerPreview(""); return; }
    (async () => {
      const r = await getNumberingRuleById(selectedId);
      if (r.success && r.data) {
        // parts 가 없을 수도 있어 보정
        setDraft({ ...r.data, parts: r.data.parts ?? [] });
        // 저장된 룰이면 서버 미리보기 시도
        const p = await previewNumberingCode(selectedId);
        if (p.success && p.data?.generatedCode) setServerPreview(p.data.generatedCode);
      } else toast.error(r.error ?? "상세 조회 실패");
    })();
  }, [selectedId]);

  const filtered = useMemo(() => {
    const q = search.trim().toLowerCase();
    if (!q) return list;
    return list.filter((r) =>
      (r.ruleName ?? "").toLowerCase().includes(q) ||
      (r.description ?? "").toLowerCase().includes(q),
    );
  }, [list, search]);

  // 새 룰 — ruleId 는 시스템에서 timestamp 기반으로 자동 발급.
  // 룰은 재사용 가능한 패턴 정의로만 존재 — scope/table/column 정보 없음.
  const handleNew = () => {
    const draftRule: NumberingRuleConfig = {
      ruleId: `rule-${Date.now()}`,
      ruleName: "",
      description: "",
      parts: [makeDefaultPart("reference", 1), makeDefaultPart("date", 2), makeDefaultPart("sequence", 3)],
      separator: "-",
      resetPeriod: "yearly",
      scopeType: "global", // 백엔드 호환 — UI 노출 안 함
    };
    setSelectedId(null);
    setDraft(draftRule);
    setServerPreview("");
  };

  // 저장
  const handleSave = async () => {
    if (!draft) return;
    if (!draft.ruleName?.trim()) return toast.warning("룰명을 입력하세요.");
    if (!draft.parts?.length) return toast.warning("최소 1개 이상의 파트가 필요합니다.");
    // ruleId 가 비어있으면 신규로 간주, 자동 발급
    const ruleId = draft.ruleId?.trim() || `rule-${Date.now()}`;
    const payload = { ...draft, ruleId };
    setSaving(true);
    try {
      const isExisting = list.some((r) => r.ruleId === ruleId);
      const res = isExisting
        ? await updateNumberingRule(ruleId, payload)
        : await createNumberingRule(payload);
      if (res.success) {
        toast.success(isExisting ? "수정되었습니다." : "등록되었습니다.");
        await loadList();
        setSelectedId(ruleId);
      } else {
        toast.error(res.error ?? "저장 실패");
      }
    } finally {
      setSaving(false);
    }
  };

  // 삭제
  const handleDelete = async () => {
    if (!draft?.ruleId) return;
    if (!confirm(`"${draft.ruleName}" 룰을 삭제하시겠습니까?`)) return;
    const res = await deleteNumberingRule(draft.ruleId);
    if (res.success) {
      toast.success("삭제되었습니다.");
      setSelectedId(null);
      setDraft(null);
      loadList();
    } else toast.error(res.error ?? "삭제 실패");
  };

  // 시퀀스 리셋
  const handleReset = async () => {
    if (!draft?.ruleId) return;
    if (!confirm("이 룰의 현재 시퀀스를 0으로 초기화하시겠습니까?")) return;
    const r = await resetSequence(draft.ruleId);
    if (r.success) toast.success("시퀀스 초기화 완료.");
    else toast.error(r.error ?? "초기화 실패");
  };

  // 미리보기 (서버 호출 — 저장된 룰에 한해)
  const handlePreview = async () => {
    if (!draft?.ruleId) return toast.warning("먼저 저장 후 서버 미리보기를 사용할 수 있어요.");
    const r = await previewNumberingCode(draft.ruleId);
    if (r.success && r.data?.generatedCode) {
      setServerPreview(r.data.generatedCode);
      toast.success(`다음 발번 예상: ${r.data.generatedCode}`);
    } else toast.error(r.error ?? "미리보기 실패");
  };

  // 파트 조작
  const updateDraft = (patch: Partial<NumberingRuleConfig>) => setDraft((d) => (d ? { ...d, ...patch } : d));
  const updatePart = (idx: number, patch: Partial<NumberingRulePart>) =>
    setDraft((d) => {
      if (!d) return d;
      const next = [...d.parts];
      next[idx] = { ...next[idx], ...patch };
      return { ...d, parts: next };
    });
  const updatePartCfg = (idx: number, cfg: Record<string, any>) =>
    setDraft((d) => {
      if (!d) return d;
      const next = [...d.parts];
      next[idx] = { ...next[idx], autoConfig: { ...(next[idx].autoConfig ?? {}), ...cfg } };
      return { ...d, parts: next };
    });
  const movePart = (idx: number, dir: -1 | 1) =>
    setDraft((d) => {
      if (!d) return d;
      const next = [...d.parts];
      const target = idx + dir;
      if (target < 0 || target >= next.length) return d;
      [next[idx], next[target]] = [next[target], next[idx]];
      next.forEach((p, i) => (p.order = i + 1));
      return { ...d, parts: next };
    });
  const removePart = (idx: number) =>
    setDraft((d) => {
      if (!d) return d;
      const next = d.parts.filter((_, i) => i !== idx).map((p, i) => ({ ...p, order: i + 1 }));
      return { ...d, parts: next };
    });
  const addPart = (type: CodePartType) =>
    setDraft((d) => {
      if (!d) return d;
      return { ...d, parts: [...d.parts, makeDefaultPart(type, d.parts.length + 1)] };
    });

  const livePreview = draft ? localPreview(draft.parts ?? [], draft.separator ?? "-") : "";

  return (
    <div className="flex h-full min-h-0 flex-col bg-background p-4">
      {/* 헤더 */}
      <div className="flex flex-shrink-0 items-center justify-between border-b pb-2">
        <h1 className="text-lg font-semibold tracking-tight">시퀀스 관리</h1>
        <div className="flex items-center gap-1.5">
          <Button variant="outline" size="sm" className="h-7 gap-1 px-2 text-xs" onClick={loadList} disabled={loading}>
            <RefreshCw className={cn("h-3 w-3", loading && "animate-spin")} /> 새로고침
          </Button>
          <Button size="sm" className="h-7 gap-1 px-2 text-xs" onClick={handleNew}>
            <Plus className="h-3 w-3" /> 새 룰
          </Button>
        </div>
      </div>

      <div className="mt-3 flex min-h-0 flex-1 gap-3 lg:flex-row">
        {/* 좌측: 룰 목록 */}
        <div className="flex min-h-0 w-72 flex-col rounded-md border bg-card">
          <div className="flex h-9 items-center border-b px-3">
            <h2 className="text-xs font-semibold tracking-tight">채번 룰</h2>
            <span className="ml-auto font-mono text-[10px] text-muted-foreground">{filtered.length}건</span>
          </div>
          <div className="flex flex-shrink-0 items-center gap-1.5 border-b bg-muted/30 px-2 py-1.5">
            <div className="relative flex-1">
              <Search className="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
              <Input
                placeholder="룰명/설명으로 검색"
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                className="h-7 border-transparent bg-background pl-7 text-[11px] shadow-none focus-visible:border-input"
              />
            </div>
          </div>
          <div className="min-h-0 flex-1 overflow-y-auto">
            {filtered.length === 0 ? (
              <div className="py-8 text-center text-xs text-muted-foreground">
                {search ? "검색 결과가 없습니다." : "등록된 룰이 없습니다."}
              </div>
            ) : (
              filtered.map((r) => {
                const isActive = selectedId === r.ruleId;
                return (
                  <div
                    key={r.ruleId}
                    onClick={() => setSelectedId(r.ruleId)}
                    className={cn(
                      "group cursor-pointer border-l-2 px-2.5 py-1.5 transition-colors",
                      isActive
                        ? "border-l-primary bg-primary/5"
                        : "border-l-transparent hover:bg-muted/50",
                    )}
                  >
                    <div className={cn("truncate text-[12px]", isActive ? "font-semibold" : "font-medium")}>
                      {r.ruleName || "(이름 없음)"}
                    </div>
                    {r.description && (
                      <div className="mt-0.5 truncate text-[10px] text-muted-foreground" title={r.description}>
                        {r.description}
                      </div>
                    )}
                  </div>
                );
              })
            )}
          </div>
        </div>

        {/* 우측: 빌더 */}
        <div className="flex min-h-0 flex-1 flex-col rounded-md border bg-card">
          {!draft ? (
            <div className="flex flex-1 items-center justify-center">
              <p className="text-xs text-muted-foreground">왼쪽 목록에서 룰을 선택하거나 "새 룰" 을 눌러주세요.</p>
            </div>
          ) : (
            <>
              {/* 빌더 헤더 */}
              <div className="flex h-9 flex-shrink-0 items-center justify-between border-b px-3">
                <h2 className="truncate text-xs font-semibold tracking-tight">
                  {draft.ruleId ? (draft.ruleName || "(이름 없음)") : "새 룰"}
                </h2>
                <div className="flex items-center gap-1">
                  <Button variant="ghost" size="sm" className="h-6 gap-1 px-2 text-[10px]" onClick={handlePreview} disabled={!draft.ruleId}>
                    <Eye className="h-3 w-3" /> 서버 미리보기
                  </Button>
                  <Button variant="ghost" size="sm" className="h-6 gap-1 px-2 text-[10px] text-amber-700" onClick={handleReset} disabled={!draft.ruleId}>
                    <RotateCcw className="h-3 w-3" /> 시퀀스 리셋
                  </Button>
                  <Button variant="ghost" size="sm" className="h-6 gap-1 px-2 text-[10px] text-destructive" onClick={handleDelete} disabled={!draft.ruleId}>
                    <Trash2 className="h-3 w-3" /> 삭제
                  </Button>
                  <Button size="sm" className="h-6 gap-1 px-2 text-[10px]" onClick={handleSave} disabled={saving}>
                    <Save className="h-3 w-3" /> {saving ? "저장 중..." : "저장"}
                  </Button>
                </div>
              </div>

              <div className="flex-1 space-y-4 overflow-y-auto p-3">
                {/* 미리보기 바 */}
                <div className="rounded-md border-2 border-dashed border-primary/30 bg-primary/5 px-3 py-2">
                  <div className="flex items-center justify-between">
                    <span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
                      미리보기 (로컬)
                    </span>
                    {serverPreview && (
                      <span className="text-[10px] text-muted-foreground">
                        서버 미리보기: <span className="font-mono font-semibold text-foreground">{serverPreview}</span>
                      </span>
                    )}
                  </div>
                  <div className="mt-1 break-all font-mono text-base font-bold tracking-wide text-primary">
                    {livePreview || "(파트 없음)"}
                  </div>
                </div>

                {/* 기본 정보 */}
                <section className="space-y-2 rounded-md border bg-muted/20 p-3">
                  <div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">기본 정보</div>
                  {/* 룰 ID 는 시스템 자동 발급 — 사용자에게 노출하지 않는다 */}
                  <div className="space-y-1">
                    <Label className="text-[11px]">룰명 <span className="text-destructive">*</span></Label>
                    <Input
                      value={draft.ruleName}
                      onChange={(e) => updateDraft({ ruleName: e.target.value })}
                      placeholder="예: ECR 번호 채번"
                      className="h-7 text-xs"
                    />
                  </div>
                  {/* 룰은 재사용 가능한 패턴 정의이므로 "어디서 쓸지" 는 기록하지 않는다.
                      어느 테이블/컬럼에 쓸지는 테이블 타입 관리에서 컬럼 단위로 지정. */}
                  <div className="grid grid-cols-2 gap-2">
                    <div className="space-y-1">
                      <Label className="text-[11px]">리셋 주기</Label>
                      <Select
                        value={draft.resetPeriod ?? "none"}
                        onValueChange={(v) => updateDraft({ resetPeriod: v as NumberingRuleConfig["resetPeriod"] })}
                      >
                        <SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          {RESET_PERIOD_OPTIONS.map((o) => (
                            <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                    <div className="space-y-1">
                      <Label className="text-[11px]">기본 구분자</Label>
                      <Select
                        value={draft.separator ?? "-"}
                        onValueChange={(v) => updateDraft({ separator: v === "none" ? "" : v })}
                      >
                        <SelectTrigger className="h-7 text-xs"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          {SEPARATOR_OPTIONS.filter((o) => o.value !== "custom").map((o) => (
                            <SelectItem key={o.value} value={o.value === "none" ? "none" : o.displayValue}>
                              {o.label}
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                  </div>
                  <div className="space-y-1">
                    <Label className="text-[11px]">설명</Label>
                    <Input
                      value={draft.description ?? ""}
                      onChange={(e) => updateDraft({ description: e.target.value })}
                      placeholder="이 채번 룰이 어디에 쓰이는지 한 줄 설명"
                      className="h-7 text-xs"
                    />
                  </div>
                </section>

                {/* 파트 빌더 */}
                <section className="space-y-2 rounded-md border bg-muted/20 p-3">
                  <div className="flex items-center justify-between">
                    <div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
                      코드 구성 ({draft.parts.length} 파트)
                    </div>
                    <div className="flex items-center gap-1">
                      <span className="text-[10px] text-muted-foreground">추가:</span>
                      {/* '카테고리' 는 '참조(마스터 조회)' 와 기능이 동일 → 신규 추가에서는 노출하지 않는다.
                          기존 데이터에 category 파트가 남아있어도 화면에서는 그대로 편집 가능. */}
                      {CODE_PART_TYPE_OPTIONS.filter((o) => o.value !== "category").map((opt) => {
                        // '참조' 는 사용자에게 더 직관적인 라벨로 ("참조테이블")
                        const btnLabel = opt.value === "reference" ? "참조테이블" : opt.label;
                        const btnTitle = opt.value === "reference"
                          ? "다른 테이블/마스터/폼 값을 참조해서 코드에 끼움 (마스터 조회·폼 값·고정 라벨 모드)"
                          : opt.description;
                        return (
                          <button
                            key={opt.value}
                            type="button"
                            onClick={() => addPart(opt.value)}
                            title={btnTitle}
                            className={cn(
                              "rounded border px-1.5 py-0.5 text-[10px] font-medium transition-colors",
                              PART_COLOR[opt.value],
                              "hover:opacity-80",
                            )}
                          >
                            + {btnLabel}
                          </button>
                        );
                      })}
                    </div>
                  </div>

                  {draft.parts.length === 0 ? (
                    <div className="rounded border border-dashed py-4 text-center text-[11px] text-muted-foreground">
                      파트를 추가해주세요. 위쪽 "+ 순번/날짜/문자…" 버튼을 누르세요.
                    </div>
                  ) : (
                    <div className="space-y-1.5">
                      {draft.parts
                        .slice()
                        .sort((a, b) => a.order - b.order)
                        .map((p, idx) => (
                          <PartRow
                            key={p.id ?? `${p.order}-${p.partType}`}
                            part={p}
                            idx={idx}
                            total={draft.parts.length}
                            globalSeparator={draft.separator ?? "-"}
                            tables={tables}
                            columnsByTable={columnsByTable}
                            onLoadColumns={loadColumnsFor}
                            onChange={(patch) => updatePart(idx, patch)}
                            onChangeCfg={(cfg) => updatePartCfg(idx, cfg)}
                            onMove={(dir) => movePart(idx, dir)}
                            onRemove={() => removePart(idx)}
                          />
                        ))}
                    </div>
                  )}
                </section>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

// ── 파트 행 ───────────────────────────────────────────────────
function PartRow({
  part, idx, total, globalSeparator, tables, columnsByTable, onLoadColumns,
  onChange, onChangeCfg, onMove, onRemove,
}: {
  part: NumberingRulePart;
  idx: number;
  total: number;
  globalSeparator: string;
  tables: TableInfo[];
  columnsByTable: Record<string, ColumnTypeInfo[]>;
  onLoadColumns: (tableName: string) => void;
  onChange: (patch: Partial<NumberingRulePart>) => void;
  // 자유롭게 키 추가 가능 — 백엔드는 알 수 없는 키도 autoConfig JSONB 에 그대로 저장
  onChangeCfg: (cfg: Record<string, any>) => void;
  onMove: (dir: -1 | 1) => void;
  onRemove: () => void;
}) {
  const cfg = (part.autoConfig ?? {}) as Record<string, any>;
  // reference/category 파트가 가리키는 소스 테이블
  const sourceTableName: string | undefined =
    cfg.sourceTableName || (part.partType === "category" && typeof cfg.categoryKey === "string"
      ? cfg.categoryKey.split(".")[0]
      : undefined);
  const sourceColumns = sourceTableName ? columnsByTable[sourceTableName] ?? [] : [];
  return (
    <div className="flex items-center gap-2 rounded-md border bg-card px-2 py-1.5">
      {/* 순서 핸들 */}
      <div className="flex flex-col">
        <button
          type="button"
          onClick={() => onMove(-1)}
          disabled={idx === 0}
          className="text-[9px] text-muted-foreground hover:text-foreground disabled:opacity-30"
          title="위로"
        >
          ▲
        </button>
        <button
          type="button"
          onClick={() => onMove(1)}
          disabled={idx === total - 1}
          className="text-[9px] text-muted-foreground hover:text-foreground disabled:opacity-30"
          title="아래로"
        >
          ▼
        </button>
      </div>
      <GripVertical className="h-3 w-3 flex-shrink-0 text-muted-foreground/50" />

      {/* 타입 배지 */}
      <Badge
        variant="outline"
        className={cn("h-5 flex-shrink-0 px-1.5 text-[10px]", PART_COLOR[part.partType])}
      >
        {PART_LABEL[part.partType]}
      </Badge>

      {/* 타입별 설정 */}
      <div className="flex flex-1 flex-wrap items-center gap-1.5">
        {part.partType === "sequence" && (
          <>
            <Label className="text-[10px] text-muted-foreground">자리수</Label>
            <Input
              type="number"
              min={1}
              max={12}
              value={cfg.sequenceLength ?? 4}
              onChange={(e) => onChangeCfg({ sequenceLength: Math.max(1, Number(e.target.value) || 1) })}
              className="h-6 w-14 text-[11px]"
            />
            <Label className="text-[10px] text-muted-foreground">시작</Label>
            <Input
              type="number"
              min={0}
              value={cfg.startFrom ?? 1}
              onChange={(e) => onChangeCfg({ startFrom: Number(e.target.value) || 1 })}
              className="h-6 w-14 text-[11px]"
            />
          </>
        )}
        {part.partType === "number" && (
          <>
            <Label className="text-[10px] text-muted-foreground">자리수</Label>
            <Input
              type="number"
              min={1}
              max={12}
              value={cfg.numberLength ?? 3}
              onChange={(e) => onChangeCfg({ numberLength: Math.max(1, Number(e.target.value) || 1) })}
              className="h-6 w-14 text-[11px]"
            />
            <Label className="text-[10px] text-muted-foreground">값</Label>
            <Input
              type="number"
              value={cfg.numberValue ?? 0}
              onChange={(e) => onChangeCfg({ numberValue: Number(e.target.value) || 0 })}
              className="h-6 w-16 text-[11px]"
            />
          </>
        )}
        {part.partType === "date" && (
          <>
            <Label className="text-[10px] text-muted-foreground">형식</Label>
            <Select
              value={cfg.dateFormat ?? "YYYY"}
              onValueChange={(v) => onChangeCfg({ dateFormat: v as DateFormat })}
            >
              <SelectTrigger className="h-6 w-[140px] text-[11px]"><SelectValue /></SelectTrigger>
              <SelectContent>
                {DATE_FORMAT_OPTIONS.map((o) => (
                  <SelectItem key={o.value} value={o.value} className="text-xs">
                    {o.label} <span className="ml-1 font-mono text-[10px] text-muted-foreground">({o.example})</span>
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </>
        )}
        {part.partType === "text" && (
          <>
            <Label className="text-[10px] text-muted-foreground">값</Label>
            <Input
              value={cfg.textValue ?? ""}
              onChange={(e) => onChangeCfg({ textValue: e.target.value })}
              placeholder="예: ECR"
              className="h-6 w-32 font-mono text-[11px]"
            />
          </>
        )}
        {part.partType === "category" && (() => {
          // categoryKey 는 "table.column" 형식. 분리해서 두 콤보로.
          const [catTable, catCol] = (cfg.categoryKey ?? "").split(".") as [string?, string?];
          const cols = catTable ? columnsByTable[catTable] ?? [] : [];
          return (
            <>
              <Label className="text-[10px] text-muted-foreground">테이블</Label>
              <TableCombobox
                value={catTable ?? ""}
                tables={tables}
                onChange={(v) => { onChangeCfg({ categoryKey: v ? `${v}.` : "" }); if (v) onLoadColumns(v); }}
                placeholder="테이블"
                compact
              />
              <Label className="text-[10px] text-muted-foreground">컬럼</Label>
              <ColumnCombobox
                value={catCol ?? ""}
                columns={cols}
                onChange={(v) => onChangeCfg({ categoryKey: catTable ? `${catTable}.${v}` : v })}
                placeholder="컬럼"
                disabled={!catTable}
                compact
              />
            </>
          );
        })()}
        {part.partType === "reference" && (() => {
          /*
            "참조" 파트는 모드 셀렉트로 자유롭게 — 3개 필드 고정이 아니라 모드별로 필드가 달라진다.
              · form     : 폼에 들어있는 키 값을 그대로 (단순)
              · master   : 마스터 테이블에서 값 컬럼을 조회해서 코드에 끼움 (예: customer_id → HKMC)
              · constant : 정적 라벨 (참조와 함께 그룹화하고 싶을 때 — text 파트 대체용)

            여러 마스터 조합이 필요하면 + 참조 버튼으로 reference 파트를 N 개 추가하면 됨.
          */
          const mode: "form" | "master" | "constant" =
            cfg.mode ?? (cfg.sourceTableName ? "master" : cfg.constantValue ? "constant" : "form");
          return (
            <>
              <Label className="text-[10px] text-muted-foreground">모드</Label>
              <Select
                value={mode}
                onValueChange={(v) => onChangeCfg({ mode: v })}
              >
                <SelectTrigger className="h-6 w-[110px] text-[11px]"><SelectValue /></SelectTrigger>
                <SelectContent>
                  <SelectItem value="form" className="text-xs">폼 값</SelectItem>
                  <SelectItem value="master" className="text-xs">마스터 조회</SelectItem>
                  <SelectItem value="constant" className="text-xs">고정 라벨</SelectItem>
                </SelectContent>
              </Select>

              {/* 모드별 필드 */}
              {mode === "form" && (
                <>
                  <Label className="text-[10px] text-muted-foreground">폼 키</Label>
                  <Input
                    value={cfg.referenceColumnName ?? ""}
                    onChange={(e) => onChangeCfg({ referenceColumnName: e.target.value || undefined })}
                    placeholder="예: customer_code"
                    className="h-6 w-40 font-mono text-[11px]"
                    title="저장 시점 폼 데이터의 이 키 값을 그대로 채번에 사용"
                  />
                </>
              )}

              {mode === "master" && (
                <>
                  <Label className="text-[10px] text-muted-foreground">마스터</Label>
                  <TableCombobox
                    value={cfg.sourceTableName ?? ""}
                    tables={tables}
                    onChange={(v) => {
                      onChangeCfg({
                        sourceTableName: v || undefined,
                        sourceColumnName: undefined,
                      });
                      if (v) onLoadColumns(v);
                    }}
                    placeholder="마스터 테이블"
                    compact
                    allowClear
                  />
                  <Label className="text-[10px] text-muted-foreground">값</Label>
                  <ColumnCombobox
                    value={cfg.sourceColumnName ?? ""}
                    columns={sourceColumns}
                    onChange={(v) => onChangeCfg({ sourceColumnName: v || undefined })}
                    placeholder={cfg.sourceTableName ? "값 컬럼" : "먼저 마스터 선택"}
                    disabled={!cfg.sourceTableName || sourceColumns.length === 0}
                    compact
                  />
                  <Label className="text-[10px] text-muted-foreground">폼 키</Label>
                  <Input
                    value={cfg.referenceColumnName ?? ""}
                    onChange={(e) => onChangeCfg({ referenceColumnName: e.target.value || undefined })}
                    placeholder="예: customer_id"
                    className="h-6 w-28 font-mono text-[11px]"
                    title="폼이 들고있는 마스터 PK 키. 이 값으로 마스터 행을 조회해서 값 컬럼을 채번에 사용."
                  />
                </>
              )}

              {mode === "constant" && (
                <>
                  <Label className="text-[10px] text-muted-foreground">라벨</Label>
                  <Input
                    value={cfg.constantValue ?? ""}
                    onChange={(e) => onChangeCfg({ constantValue: e.target.value })}
                    placeholder="예: HKMC"
                    className="h-6 w-40 font-mono text-[11px]"
                  />
                </>
              )}
            </>
          );
        })()}

        <span className="ml-2 truncate font-mono text-[10px] text-muted-foreground">
          → {partSummary(part)}
        </span>
      </div>

      {/* 파트별 구분자 */}
      {idx < total - 1 && (
        <div className="flex items-center gap-1">
          <Label className="text-[10px] text-muted-foreground">구분자</Label>
          <Input
            value={part.separatorAfter ?? ""}
            onChange={(e) => onChange({ separatorAfter: e.target.value })}
            placeholder={globalSeparator}
            className="h-6 w-10 text-center font-mono text-[11px]"
          />
        </div>
      )}

      {/* 삭제 */}
      <Button
        type="button"
        variant="ghost"
        size="icon"
        onClick={onRemove}
        className="h-6 w-6 text-destructive hover:bg-destructive/10"
        title="파트 삭제"
      >
        <X className="h-3 w-3" />
      </Button>
    </div>
  );
}

// ── 테이블 콤보박스 ───────────────────────────────────────────
function TableCombobox({
  value, tables, onChange, placeholder, compact, allowClear,
}: {
  value: string;
  tables: TableInfo[];
  onChange: (v: string) => void;
  placeholder?: string;
  compact?: boolean;
  allowClear?: boolean;
}) {
  const [open, setOpen] = useState(false);
  const selected = tables.find((t) => t.tableName === value);
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          className={cn(
            "justify-between font-mono",
            compact ? "h-6 w-40 text-[11px]" : "h-7 w-full text-xs",
          )}
        >
          <span className="truncate">
            {selected
              ? selected.displayName && selected.displayName !== selected.tableName
                ? `${selected.displayName} (${selected.tableName})`
                : selected.tableName
              : <span className="text-muted-foreground">{placeholder ?? "테이블 선택"}</span>}
          </span>
          <ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[300px] p-0" align="start">
        <Command>
          <CommandInput placeholder="테이블 검색..." className="h-8 text-xs" />
          <CommandList className="max-h-[260px]">
            <CommandEmpty className="py-2 text-center text-xs">테이블을 찾을 수 없습니다.</CommandEmpty>
            <CommandGroup>
              {allowClear && (
                <CommandItem
                  value="__clear__"
                  onSelect={() => { onChange(""); setOpen(false); }}
                  className="text-xs text-muted-foreground"
                >
                  <Check className={cn("mr-2 h-3 w-3", !value ? "opacity-100" : "opacity-0")} />
                  (선택 해제)
                </CommandItem>
              )}
              {tables.map((t) => {
                const hasKo = t.displayName && t.displayName !== t.tableName;
                return (
                  <CommandItem
                    key={t.tableName}
                    value={`${t.displayName ?? ""} ${t.tableName}`}
                    onSelect={() => { onChange(t.tableName); setOpen(false); }}
                    className="text-xs"
                  >
                    <Check className={cn("mr-2 h-3 w-3", value === t.tableName ? "opacity-100" : "opacity-0")} />
                    {hasKo ? (
                      <div className="flex flex-col">
                        <span className="font-medium">{t.displayName}</span>
                        <span className="font-mono text-[10px] text-muted-foreground">{t.tableName}</span>
                      </div>
                    ) : (
                      <span className="font-mono">{t.tableName}</span>
                    )}
                  </CommandItem>
                );
              })}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

// ── 컬럼 콤보박스 ─────────────────────────────────────────────
function ColumnCombobox({
  value, columns, onChange, placeholder, disabled, compact, allowFreeInput,
}: {
  value: string;
  columns: ColumnTypeInfo[];
  onChange: (v: string) => void;
  placeholder?: string;
  disabled?: boolean;
  compact?: boolean;
  /** 컬럼 목록이 없거나 임의값 입력이 가능해야 할 때 (예: reference 의 same-form 케이스) */
  allowFreeInput?: boolean;
}) {
  const [open, setOpen] = useState(false);
  const selected = columns.find((c) => c.columnName === value);

  if (allowFreeInput && columns.length === 0) {
    // 자유 입력 모드 — 같은 폼 컨텍스트 컬럼명
    return (
      <Input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        disabled={disabled}
        className={cn("font-mono", compact ? "h-6 w-40 text-[11px]" : "h-7 w-full text-xs")}
      />
    );
  }

  return (
    <Popover open={open} onOpenChange={(o) => !disabled && setOpen(o)}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          disabled={disabled}
          className={cn(
            "justify-between font-mono",
            compact ? "h-6 w-40 text-[11px]" : "h-7 w-full text-xs",
          )}
        >
          <span className="truncate">
            {selected
              ? selected.displayName && selected.displayName !== selected.columnName
                ? `${selected.displayName} (${selected.columnName})`
                : selected.columnName
              : value
                ? value
                : <span className="text-muted-foreground">{placeholder ?? "컬럼 선택"}</span>}
          </span>
          <ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-[280px] p-0" align="start">
        <Command>
          <CommandInput placeholder="컬럼 검색..." className="h-8 text-xs" />
          <CommandList className="max-h-[260px]">
            <CommandEmpty className="py-2 text-center text-xs">컬럼을 찾을 수 없습니다.</CommandEmpty>
            <CommandGroup>
              {columns.map((c) => {
                const hasKo = c.displayName && c.displayName !== c.columnName;
                return (
                  <CommandItem
                    key={c.columnName}
                    value={`${c.displayName ?? ""} ${c.columnName}`}
                    onSelect={() => { onChange(c.columnName); setOpen(false); }}
                    className="text-xs"
                  >
                    <Check className={cn("mr-2 h-3 w-3", value === c.columnName ? "opacity-100" : "opacity-0")} />
                    {hasKo ? (
                      <div className="flex flex-col">
                        <span className="font-medium">{c.displayName}</span>
                        <span className="font-mono text-[10px] text-muted-foreground">{c.columnName}</span>
                      </div>
                    ) : (
                      <span className="font-mono">{c.columnName}</span>
                    )}
                  </CommandItem>
                );
              })}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
