"use client";

import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { toast } from "sonner";
import {
  Search, Hash, Plus, RefreshCw, Trash2, Save, RotateCcw, Loader2,
  Table2, Copy, Download, Share2, AlertTriangle, ArrowRight, Edit3,
  Calendar, Type, Hash as HashIcon, Link2, Code, Minus, Layers,
  CheckCircle, Eye, Upload, LayoutGrid, X, Sparkles,
} from "lucide-react";
import { showErrorToast } from "@/lib/utils/toastUtils";
import {
  getNumberingRulesFromTest,
  getNumberingRuleById,
  deleteNumberingRuleFromTest,
  resetSequence,
  updateRuleSequence,
  saveNumberingRuleToTest,
} from "@/lib/api/numberingRule";
import {
  NumberingRuleConfig,
  NumberingRulePart,
  CodePartType,
  DATE_FORMAT_OPTIONS,
  RESET_PERIOD_OPTIONS,
} from "@/types/numbering-rule";

/* ===================================================================
   채번 관리 — 통짜 작업대 (좌측 list + 우측 디테일 + ⌘K 팔레트)
   디자인: notes/gbpark/2026-05-12-numbering-workbench.html
   공통 CSS: frontend/styles/v5-layout.css (v5-nrm-* 클래스)
   =================================================================== */

type ResetPeriod = "none" | "daily" | "monthly" | "yearly";
type SideFilter = "all" | "active" | "warn" | "unused";

const PART_TONE_BY_TYPE: Record<CodePartType, string> = {
  text: "text",         // prefix
  date: "date",
  sequence: "sequence",
  number: "number",
  category: "category",
  reference: "reference",
};

const PART_LABEL_BY_TYPE: Record<CodePartType, string> = {
  text: "TEXT",
  date: "DATE",
  sequence: "SEQ",
  number: "NUM",
  category: "CAT",
  reference: "REF",
};

const RESET_LABEL: Record<ResetPeriod, string> = {
  none: "없음",
  daily: "일",
  monthly: "월",
  yearly: "년",
};

export default function NumberingRuleManagementPage() {
  const [rules, setRules] = useState<NumberingRuleConfig[]>([]);
  const [loading, setLoading] = useState(false);
  const [selectedRuleId, setSelectedRuleId] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [filter, setFilter] = useState<SideFilter>("all");
  const [selectedPartOrder, setSelectedPartOrder] = useState<number | null>(null);
  const [cmdkOpen, setCmdkOpen] = useState(false);
  const [refreshKey, setRefreshKey] = useState(0);

  // selected rule (편집 중 상태)
  const [editingRule, setEditingRule] = useState<NumberingRuleConfig | null>(null);
  const [dirty, setDirty] = useState(false);
  const [draftSequence, setDraftSequence] = useState<string>("");
  const [mutating, setMutating] = useState(false);

  // 회사별 채번 목록 로드
  const loadRules = useCallback(async () => {
    setLoading(true);
    try {
      const resp = await getNumberingRulesFromTest();
      if (resp.success && resp.data) {
        setRules(resp.data);
      } else {
        setRules([]);
        if (resp.error) showErrorToast("규칙 목록 조회 실패", resp.error);
      }
    } catch (e) {
      setRules([]);
      showErrorToast("규칙 목록 조회 실패", e);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    loadRules();
  }, [loadRules, refreshKey]);

  // 선택된 채번 상세 로드
  useEffect(() => {
    if (!selectedRuleId) {
      setEditingRule(null);
      setDirty(false);
      setSelectedPartOrder(null);
      return;
    }
    const fromList = rules.find((r) => String(r.rule_id) === selectedRuleId);
    if (fromList) {
      const cloned = structuredClone(fromList);
      setEditingRule(cloned);
      setDraftSequence(String(cloned.current_sequence ?? 0));
      setDirty(false);
      setSelectedPartOrder(cloned.parts?.[0]?.order ?? null);
    }
  }, [selectedRuleId, rules]);

  // ⌘K 단축키
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "k") {
        e.preventDefault();
        setCmdkOpen((v) => !v);
      }
      if (e.key === "Escape") setCmdkOpen(false);
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, []);

  // 필터/검색
  const visibleRules = useMemo(() => {
    const term = search.trim().toLowerCase();
    return rules.filter((r) => {
      if (filter === "active" && (r.parts?.length ?? 0) === 0) return false;
      if (filter === "unused" && !!r.table_name && !!r.column_name) return false;
      if (term) {
        const hay = `${r.rule_name ?? ""} ${r.table_name ?? ""} ${r.column_name ?? ""} ${r.description ?? ""}`.toLowerCase();
        if (!hay.includes(term)) return false;
      }
      return true;
    });
  }, [rules, search, filter]);

  // 사이드바 섹션 분류
  const groupedRules = useMemo(() => {
    const live: NumberingRuleConfig[] = [];
    const warn: NumberingRuleConfig[] = [];
    const others: NumberingRuleConfig[] = [];
    const unused: NumberingRuleConfig[] = [];
    visibleRules.forEach((r) => {
      const hasUsage = !!r.table_name && !!r.column_name;
      if (!hasUsage) {
        unused.push(r);
      } else {
        others.push(r);
      }
    });
    // live = 최근 발번 상위 3개 (실제론 마지막 발번 시각으로 정렬해야 하나 데이터 없음 → updated_at 사용)
    const sortedByUpdate = [...others].sort((a, b) =>
      (b.updated_at ?? "").localeCompare(a.updated_at ?? "")
    );
    return {
      live: sortedByUpdate.slice(0, 3),
      warn,
      others: sortedByUpdate.slice(3),
      unused,
    };
  }, [visibleRules]);

  // 통계
  const stats = useMemo(() => {
    const total = rules.length;
    const active = rules.filter((r) => (r.parts?.length ?? 0) > 0).length;
    const linked = rules.filter((r) => !!r.table_name && !!r.column_name).length;
    const unused = rules.filter((r) => !r.table_name || !r.column_name).length;
    return { total, active, warn: 0, unused, linked };
  }, [rules]);

  // 액션
  const handleSelect = (id: string) => {
    if (dirty && !confirm("저장하지 않은 변경이 있습니다. 무시하고 이동할까요?")) return;
    setSelectedRuleId(id);
  };

  const handleSave = async () => {
    if (!editingRule || !dirty) return;
    setMutating(true);
    try {
      const resp = await saveNumberingRuleToTest(editingRule);
      if (resp.success) {
        toast.success("채번 규칙이 저장되었습니다");
        setDirty(false);
        setRefreshKey((k) => k + 1);
      } else {
        showErrorToast("저장 실패", resp.error);
      }
    } catch (e) {
      showErrorToast("저장 실패", e);
    } finally {
      setMutating(false);
    }
  };

  const handleRevert = () => {
    if (!selectedRuleId) return;
    const fromList = rules.find((r) => String(r.rule_id) === selectedRuleId);
    if (fromList) {
      setEditingRule(structuredClone(fromList));
      setDraftSequence(String(fromList.current_sequence ?? 0));
      setDirty(false);
    }
  };

  const handleDelete = async () => {
    if (!editingRule) return;
    if (!confirm(`"${editingRule.rule_name}" 규칙을 삭제하시겠습니까?`)) return;
    try {
      const resp = await deleteNumberingRuleFromTest(String(editingRule.rule_id));
      if (resp.success) {
        toast.success("규칙이 삭제되었습니다");
        setSelectedRuleId(null);
        setRefreshKey((k) => k + 1);
      } else {
        showErrorToast("삭제 실패", resp.error);
      }
    } catch (e) {
      showErrorToast("삭제 실패", e);
    }
  };

  const handleResetSequence = async () => {
    if (!editingRule?.rule_id) return;
    if (!confirm("시퀀스를 0 으로 초기화 하시겠습니까?")) return;
    setMutating(true);
    try {
      const resp = await resetSequence(String(editingRule.rule_id));
      if (resp.success) {
        toast.success("시퀀스가 초기화되었습니다");
        setDraftSequence("0");
        setRefreshKey((k) => k + 1);
      } else {
        showErrorToast("초기화 실패", resp.error);
      }
    } catch (e) {
      showErrorToast("초기화 실패", e);
    } finally {
      setMutating(false);
    }
  };

  const handleApplySequence = async () => {
    if (!editingRule?.rule_id) return;
    const newSeq = parseInt(draftSequence, 10);
    if (isNaN(newSeq) || newSeq < 0) {
      toast.error("0 이상의 정수를 입력하세요");
      return;
    }
    if ((editingRule.current_sequence ?? 0) === newSeq) {
      toast.info("현재 시퀀스와 동일합니다");
      return;
    }
    setMutating(true);
    try {
      const resp = await updateRuleSequence(String(editingRule.rule_id), newSeq);
      if (resp.success) {
        toast.success(`시퀀스 ${newSeq} 적용됨`);
        setRefreshKey((k) => k + 1);
      } else {
        showErrorToast("적용 실패", resp.error);
      }
    } catch (e) {
      showErrorToast("적용 실패", e);
    } finally {
      setMutating(false);
    }
  };

  const updatePart = (order: number, patch: Partial<NumberingRulePart>) => {
    if (!editingRule) return;
    setEditingRule({
      ...editingRule,
      parts: editingRule.parts.map((p) => (p.order === order ? { ...p, ...patch } : p)),
    });
    setDirty(true);
  };

  const updatePartAutoConfig = (order: number, patch: Partial<NonNullable<NumberingRulePart["auto_config"]>>) => {
    if (!editingRule) return;
    setEditingRule({
      ...editingRule,
      parts: editingRule.parts.map((p) =>
        p.order === order ? { ...p, auto_config: { ...p.auto_config, ...patch } } : p
      ),
    });
    setDirty(true);
  };

  const addPart = (type: CodePartType) => {
    if (!editingRule) return;
    const order = (editingRule.parts?.length ?? 0) + 1;
    const newPart: NumberingRulePart = {
      id: `part-${Date.now()}`,
      order,
      part_type: type,
      generation_method: "auto",
      auto_config:
        type === "sequence"
          ? { sequence_length: 4, start_from: 1 }
          : type === "date"
          ? { date_format: "YYYY" }
          : type === "text"
          ? { text_value: "PFX" }
          : type === "number"
          ? { number_length: 3, number_value: 1 }
          : {},
    };
    setEditingRule({ ...editingRule, parts: [...editingRule.parts, newPart] });
    setSelectedPartOrder(order);
    setDirty(true);
  };

  const removePart = (order: number) => {
    if (!editingRule) return;
    const filtered = editingRule.parts.filter((p) => p.order !== order);
    const reordered = filtered.map((p, i) => ({ ...p, order: i + 1 }));
    setEditingRule({ ...editingRule, parts: reordered });
    if (selectedPartOrder === order) setSelectedPartOrder(reordered[0]?.order ?? null);
    setDirty(true);
  };

  const selectedPart = useMemo(
    () => editingRule?.parts.find((p) => p.order === selectedPartOrder) ?? null,
    [editingRule, selectedPartOrder]
  );

  // 다음 발번 코드 미리보기
  const previewCode = useMemo(() => {
    if (!editingRule) return "";
    const sep = editingRule.separator ?? "-";
    const seq = (editingRule.current_sequence ?? 0) + 1;
    const today = new Date();
    return editingRule.parts
      .sort((a, b) => a.order - b.order)
      .map((p) => renderPartValue(p, today, seq))
      .join(sep);
  }, [editingRule]);

  const currentCode = useMemo(() => {
    if (!editingRule) return "";
    const sep = editingRule.separator ?? "-";
    const seq = editingRule.current_sequence ?? 0;
    const today = new Date();
    return editingRule.parts
      .sort((a, b) => a.order - b.order)
      .map((p) => renderPartValue(p, today, seq))
      .join(sep);
  }, [editingRule]);

  return (
    <div className="v5-nrm">
      <div
        style={{
          padding: "1.1rem 1.4rem .9rem",
          borderBottom: "1px solid var(--v5-border)",
          display: "flex",
          alignItems: "flex-end",
          justifyContent: "space-between",
          gap: "1rem",
          flexShrink: 0,
        }}
      >
        <div>
          <h1
            style={{
              margin: 0,
              fontSize: "1.1rem",
              fontWeight: 700,
              letterSpacing: "-.02em",
              color: "var(--v5-text)",
            }}
          >
            채번 관리
          </h1>
          <p
            style={{
              margin: ".3rem 0 0",
              fontSize: ".72rem",
              color: "var(--v5-text-muted)",
            }}
          >
            채번 규칙을 만들어두고 컬럼에 자유롭게 연결합니다 ·{" "}
            <b style={{ color: "var(--v5-text)" }}>{stats.total}</b> 규칙 ·{" "}
            <b style={{ color: "rgb(var(--v5-green-rgb))" }}>{stats.linked}</b> 연결
            {stats.unused > 0 && (
              <> · <b style={{ color: "var(--v5-text)" }}>{stats.unused}</b> 미사용</>
            )}
            {stats.warn > 0 && (
              <> · <b style={{ color: "rgb(var(--v5-amber-rgb))" }}>{stats.warn}</b> 충돌</>
            )}
          </p>
        </div>
        <div style={{ display: "flex", gap: ".35rem", alignItems: "center" }}>
          <span className="v5-nrm-kbd">⌘ K</span>
          <button
            className="v5-nrm-btn icon-only ghost"
            title="새로고침"
            onClick={() => setRefreshKey((k) => k + 1)}
          >
            {loading ? <Loader2 size={13} className="animate-spin" /> : <RefreshCw size={13} />}
          </button>
          <button className="v5-nrm-btn ghost">
            <Download size={13} /> 내보내기
          </button>
          <button className="v5-nrm-btn primary" onClick={() => setCmdkOpen(true)}>
            <Plus size={13} /> 새 채번
          </button>
        </div>
      </div>

      <div className="v5-nrm-body">
        {/* ─── Sidebar ─── */}
        <aside className="v5-nrm-side">
          <div className="v5-nrm-side-srch">
            <Search size={12} />
            <input
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              placeholder="채번 이름 · 코드 · 컬럼명"
            />
            <span className="kbd-hint">⌘ /</span>
          </div>
          <div className="v5-nrm-side-filters">
            <button
              className={`v5-nrm-filt${filter === "all" ? " on" : ""}`}
              onClick={() => setFilter("all")}
            >
              전체 <b>{rules.length}</b>
            </button>
            <button
              className={`v5-nrm-filt${filter === "active" ? " on" : ""}`}
              onClick={() => setFilter("active")}
            >
              활성 <b>{stats.active}</b>
            </button>
            <button
              className={`v5-nrm-filt${filter === "unused" ? " on" : ""}`}
              onClick={() => setFilter("unused")}
            >
              미사용 <b>{stats.unused}</b>
            </button>
          </div>

          <div className="v5-nrm-side-list">
            {loading && rules.length === 0 ? (
              <div className="v5-nrm-empty">
                <Loader2 size={18} className="animate-spin" />
                <div>로딩 중...</div>
              </div>
            ) : visibleRules.length === 0 ? (
              <div className="v5-nrm-empty">
                <Hash size={28} />
                <div>채번 규칙이 없습니다</div>
                <div className="hint">상단 "새 채번" 또는 ⌘K 로 시작</div>
              </div>
            ) : (
              <>
                {groupedRules.live.length > 0 && (
                  <>
                    <div className="v5-nrm-side-section">
                      <span>● 최근 활동</span>
                      <span>{groupedRules.live.length}</span>
                    </div>
                    {groupedRules.live.map((r) => (
                      <RuleListItem
                        key={r.rule_id}
                        rule={r}
                        live
                        selected={String(r.rule_id) === selectedRuleId}
                        onSelect={() => handleSelect(String(r.rule_id))}
                      />
                    ))}
                  </>
                )}
                {groupedRules.others.length > 0 && (
                  <>
                    <div className="v5-nrm-side-section">
                      <span>활성 · 기타</span>
                      <span>{groupedRules.others.length}</span>
                    </div>
                    {groupedRules.others.map((r) => (
                      <RuleListItem
                        key={r.rule_id}
                        rule={r}
                        selected={String(r.rule_id) === selectedRuleId}
                        onSelect={() => handleSelect(String(r.rule_id))}
                      />
                    ))}
                  </>
                )}
                {groupedRules.unused.length > 0 && (
                  <>
                    <div className="v5-nrm-side-section">
                      <span>미사용</span>
                      <span>{groupedRules.unused.length}</span>
                    </div>
                    {groupedRules.unused.map((r) => (
                      <RuleListItem
                        key={r.rule_id}
                        rule={r}
                        dim
                        selected={String(r.rule_id) === selectedRuleId}
                        onSelect={() => handleSelect(String(r.rule_id))}
                      />
                    ))}
                  </>
                )}
              </>
            )}
          </div>

          <div className="v5-nrm-side-foot">
            <button
              className="v5-nrm-btn sm"
              style={{ flex: 1 }}
              onClick={() => setCmdkOpen(true)}
            >
              <Plus size={11} /> 새 채번
            </button>
            <button className="v5-nrm-btn sm icon-only" title="가져오기">
              <Upload size={11} />
            </button>
            <button className="v5-nrm-btn sm icon-only" title="템플릿">
              <LayoutGrid size={11} />
            </button>
          </div>
        </aside>

        {/* ─── Main ─── */}
        <main className="v5-nrm-main">
          {!editingRule ? (
            <div className="v5-nrm-empty" style={{ height: "100%" }}>
              <Hash size={40} />
              <div style={{ fontSize: ".9rem", fontWeight: 600, color: "var(--v5-text)" }}>
                좌측에서 채번을 선택하세요
              </div>
              <div className="hint">또는 ⌘K 로 빠르게 검색 / 신규 생성</div>
            </div>
          ) : (
            <>
              {/* HERO */}
              <div className="v5-nrm-hero">
                <div className="v5-nrm-hero-top">
                  <div className="v5-nrm-tone cyan" style={{ width: 40, height: 40, borderRadius: 10 }}>
                    <Hash size={18} />
                  </div>
                  <div className="v5-nrm-hero-info">
                    <div className="v5-nrm-hero-row1">
                      <h2>{editingRule.rule_name || "(이름 없음)"}</h2>
                      {editingRule.parts?.length > 0 && (
                        <span className="v5-nrm-chip active live">LIVE</span>
                      )}
                      <span className="v5-nrm-chip">{editingRule.rule_id}</span>
                    </div>
                    <div className="v5-nrm-hero-meta">
                      {editingRule.created_at && (
                        <span><b>생성</b> {editingRule.created_at.slice(0, 10)}</span>
                      )}
                      {editingRule.updated_at && (
                        <span><b>수정</b> {editingRule.updated_at.slice(0, 16).replace("T", " ")}</span>
                      )}
                      {editingRule.created_by && (
                        <span><b>by</b> {editingRule.created_by}</span>
                      )}
                      <span>
                        <b>연결 컬럼</b> {editingRule.table_name && editingRule.column_name ? 1 : 0}
                      </span>
                    </div>
                  </div>
                  <div className="v5-nrm-hero-actions">
                    <button className="v5-nrm-btn sm icon-only ghost" title="복제">
                      <Copy size={11} />
                    </button>
                    <button className="v5-nrm-btn sm icon-only ghost" title="내보내기">
                      <Download size={11} />
                    </button>
                    <button className="v5-nrm-btn sm icon-only ghost" title="공유">
                      <Share2 size={11} />
                    </button>
                    <button
                      className="v5-nrm-btn sm icon-only ghost danger"
                      title="삭제"
                      onClick={handleDelete}
                    >
                      <Trash2 size={11} />
                    </button>
                  </div>
                </div>
                <div className="v5-nrm-hero-codes">
                  <div className="v5-nrm-hero-code-block">
                    <div className="lbl">현재 발번 시퀀스</div>
                    <div className="code" style={{ fontSize: "1.3rem" }}>
                      {currentCode || <span style={{ color: "var(--v5-text-muted)" }}>(파트 없음)</span>}
                    </div>
                  </div>
                  <span className="v5-nrm-hero-arrow">
                    <ArrowRight size={18} />
                  </span>
                  <div className="v5-nrm-hero-code-block right">
                    <div className="lbl">다음 발번</div>
                    <div className="code">
                      {previewCode || "—"}
                    </div>
                    <div className="sub">
                      시퀀스 {editingRule.current_sequence ?? 0} → {(editingRule.current_sequence ?? 0) + 1}
                      {editingRule.reset_period && editingRule.reset_period !== "none" && (
                        <> · {RESET_PERIOD_OPTIONS.find((o) => o.value === editingRule.reset_period)?.label}</>
                      )}
                    </div>
                  </div>
                </div>
              </div>

              {/* PIPELINE EDITOR */}
              <div className="v5-nrm-row">
                <div className="v5-nrm-row-hd">
                  <h3>코드 파이프라인</h3>
                  <span className="num">{editingRule.parts.length} / 8</span>
                  <span className="desc">part 클릭 = 인스펙터</span>
                  <div className="actions">
                    <button className="v5-nrm-btn sm ghost" onClick={handleRevert}>
                      <RotateCcw size={11} /> 초기화
                    </button>
                  </div>
                </div>

                <div className="v5-nrm-pipe-canvas">
                  {editingRule.parts.length === 0 ? (
                    <div style={{ flex: 1, textAlign: "center", color: "var(--v5-text-muted)", padding: ".75rem", fontSize: ".72rem" }}>
                      아래 팔레트에서 파트를 추가하세요
                    </div>
                  ) : (
                    editingRule.parts.sort((a, b) => a.order - b.order).map((p, idx, arr) => (
                      <PipelineBlock
                        key={`${p.order}-${p.id}`}
                        part={p}
                        selected={selectedPartOrder === p.order}
                        onClick={() => setSelectedPartOrder(p.order)}
                        onRemove={() => removePart(p.order)}
                        isLast={idx === arr.length - 1}
                        separator={editingRule.separator ?? "-"}
                      />
                    ))
                  )}
                  <button className="v5-nrm-drop" onClick={() => addPart("sequence")}>
                    <Plus size={12} /> 추가
                  </button>
                </div>

                {selectedPart && (
                  <PartInspector
                    part={selectedPart}
                    onUpdate={(patch) => updatePart(selectedPart.order, patch)}
                    onUpdateConfig={(patch) => updatePartAutoConfig(selectedPart.order, patch)}
                    onRemove={() => removePart(selectedPart.order)}
                    rule={editingRule}
                    onUpdateRule={(patch) => {
                      setEditingRule({ ...editingRule, ...patch });
                      setDirty(true);
                    }}
                  />
                )}

                <div className="v5-nrm-palette">
                  <span className="lbl">+ add part</span>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("text")}>
                    <Type size={11} /> text
                  </button>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("date")}>
                    <Calendar size={11} /> date
                  </button>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("sequence")}>
                    <HashIcon size={11} /> sequence
                  </button>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("number")}>
                    <Edit3 size={11} /> number
                  </button>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("category")}>
                    <Layers size={11} /> category
                  </button>
                  <button className="v5-nrm-palette-item" onClick={() => addPart("reference")}>
                    <Link2 size={11} /> reference
                  </button>
                </div>
              </div>

              {/* 2-col split: 연결 컬럼 + 시퀀스 통계 */}
              <div className="v5-nrm-row-split">
                {/* 연결된 컬럼 (사용처) */}
                <div>
                  <div className="v5-nrm-row-hd">
                    <h3>연결된 컬럼</h3>
                    <span className="num">{editingRule.table_name && editingRule.column_name ? 1 : 0}</span>
                    <span className="desc">이 채번이 적용된 사용처</span>
                  </div>
                  <UsageList rule={editingRule} />
                </div>

                {/* 시퀀스 통계 */}
                <div>
                  <div className="v5-nrm-row-hd">
                    <h3>시퀀스 · 발번 통계</h3>
                    <span className="desc">
                      {editingRule.reset_period && editingRule.reset_period !== "none"
                        ? `${RESET_PERIOD_OPTIONS.find((o) => o.value === editingRule.reset_period)?.label}`
                        : "리셋 없음"}
                    </span>
                  </div>
                  <div className="v5-nrm-seq-stats">
                    <div className="v5-nrm-seq-stat">
                      <span className="lbl">현재 시퀀스</span>
                      <span className="val accent">
                        {(editingRule.current_sequence ?? 0).toLocaleString()}
                      </span>
                    </div>
                    <div className="v5-nrm-seq-stat">
                      <span className="lbl">다음 발번</span>
                      <span className="val">
                        {((editingRule.current_sequence ?? 0) + 1).toLocaleString()}
                      </span>
                    </div>
                  </div>

                  <div className="v5-nrm-seq-edit">
                    <label>
                      <span className="lbl">시퀀스 수동 수정 (관리자)</span>
                      <input
                        className="v5-nrm-seq-edit-inp"
                        type="number"
                        value={draftSequence}
                        onChange={(e) => setDraftSequence(e.target.value)}
                      />
                    </label>
                    <button
                      className="v5-nrm-btn primary"
                      onClick={handleApplySequence}
                      disabled={mutating}
                    >
                      {mutating ? <Loader2 size={11} className="animate-spin" /> : <CheckCircle size={11} />}
                      적용
                    </button>
                    <button
                      className="v5-nrm-btn danger"
                      onClick={handleResetSequence}
                      disabled={mutating}
                    >
                      <RotateCcw size={11} /> 0
                    </button>
                  </div>
                  <div className="v5-nrm-warn">
                    <AlertTriangle size={11} />
                    <span>
                      <b>운영 중인 채번.</b> 시퀀스 수정은 기존 발번 코드와 충돌 가능.{" "}
                      <code>numbering_rule_sequences</code> 재시작됨.
                    </span>
                  </div>
                </div>
              </div>

              {/* Save bar */}
              <div className="v5-nrm-savebar">
                <div style={{ display: "flex", alignItems: "center", gap: ".5rem" }}>
                  {dirty && (
                    <span className="v5-nrm-unsaved">
                      <span className="d"></span>저장되지 않은 변경
                    </span>
                  )}
                  {!dirty && (
                    <span style={{ fontFamily: "var(--v5-font-mono)", fontSize: ".62rem", color: "var(--v5-text-muted)" }}>
                      모든 변경 저장됨
                    </span>
                  )}
                </div>
                <div style={{ display: "flex", gap: ".35rem" }}>
                  <button
                    className="v5-nrm-btn ghost"
                    onClick={handleRevert}
                    disabled={!dirty}
                  >
                    <RotateCcw size={11} /> 되돌리기
                  </button>
                  <button
                    className="v5-nrm-btn primary"
                    onClick={handleSave}
                    disabled={!dirty || mutating}
                  >
                    {mutating ? <Loader2 size={11} className="animate-spin" /> : <Save size={11} />}
                    저장
                    <span className="v5-nrm-kbd" style={{ background: "rgba(255,255,255,.2)", color: "#fff", marginLeft: 4 }}>⌘ S</span>
                  </button>
                </div>
              </div>
            </>
          )}
        </main>
      </div>

      {/* ⌘K COMMAND PALETTE */}
      <CommandPalette
        open={cmdkOpen}
        onClose={() => setCmdkOpen(false)}
        rules={rules}
        onSelectRule={(id) => {
          setSelectedRuleId(id);
          setCmdkOpen(false);
        }}
      />
    </div>
  );
}

/* ===================================================================
   Sub-components
   =================================================================== */

function RuleListItem({
  rule,
  selected,
  live,
  dim,
  onSelect,
}: {
  rule: NumberingRuleConfig;
  selected: boolean;
  live?: boolean;
  dim?: boolean;
  onSelect: () => void;
}) {
  const tone = useTone(rule.rule_name);
  const patternPreview = useMemo(() => buildPatternPreview(rule), [rule]);
  return (
    <div
      className={`v5-nrm-item${selected ? " on" : ""}`}
      style={dim ? { opacity: 0.55 } : undefined}
      onClick={onSelect}
    >
      <div className={`v5-nrm-tone ${tone}`}>
        <Hash size={12} />
      </div>
      <div className="v5-nrm-item-body">
        <div className="v5-nrm-item-name">{rule.rule_name || "(이름 없음)"}</div>
        <div className="v5-nrm-item-pat" dangerouslySetInnerHTML={{ __html: patternPreview }} />
      </div>
      <div className="v5-nrm-item-right">
        <span className={`v5-nrm-dot${live ? " live" : ""}${dim ? " off" : ""}`}></span>
        <span className="cnt" style={dim ? { color: "var(--v5-text-muted)" } : undefined}>
          {rule.table_name && rule.column_name ? 1 : 0} 컬럼
        </span>
        {rule.updated_at && !dim && (
          <span>{rule.updated_at.slice(11, 16)}</span>
        )}
      </div>
    </div>
  );
}

function PipelineBlock({
  part,
  selected,
  onClick,
  onRemove,
  isLast,
  separator,
}: {
  part: NumberingRulePart;
  selected: boolean;
  onClick: () => void;
  onRemove: () => void;
  isLast: boolean;
  separator: string;
}) {
  const today = new Date();
  const previewSeq = 1;
  const val = renderPartValue(part, today, previewSeq);
  return (
    <>
      <div
        className={`v5-nrm-block ${part.part_type}${selected ? " sel" : ""}`}
        onClick={onClick}
      >
        <span className="pin">{part.order}</span>
        <span
          className="x"
          onClick={(e) => {
            e.stopPropagation();
            onRemove();
          }}
        >×</span>
        <span className="typ">
          {PART_LABEL_BY_TYPE[part.part_type]}
          {part.part_type === "date" && part.auto_config?.date_format ? ` · ${part.auto_config.date_format}` : ""}
          {part.part_type === "sequence" && part.auto_config?.sequence_length
            ? ` · ${part.auto_config.sequence_length}d`
            : ""}
        </span>
        <span className="val">{val || "—"}</span>
      </div>
      {!isLast && <span className="v5-nrm-jn">{separator}</span>}
    </>
  );
}

function PartInspector({
  part,
  onUpdate,
  onUpdateConfig,
  onRemove,
  rule,
  onUpdateRule,
}: {
  part: NumberingRulePart;
  onUpdate: (patch: Partial<NumberingRulePart>) => void;
  onUpdateConfig: (patch: Partial<NonNullable<NumberingRulePart["auto_config"]>>) => void;
  onRemove: () => void;
  rule: NumberingRuleConfig;
  onUpdateRule: (patch: Partial<NumberingRuleConfig>) => void;
}) {
  return (
    <div className="v5-nrm-insp">
      <div className="v5-nrm-insp-hd">
        <div className="l">
          <span className="pin">#{part.order}</span>
          <span>
            <b>{PART_LABEL_BY_TYPE[part.part_type]}</b> 설정
          </span>
        </div>
        <button className="v5-nrm-btn sm ghost danger" onClick={onRemove}>
          <Trash2 size={11} /> 삭제
        </button>
      </div>
      <div className="v5-nrm-insp-grid">
        {part.part_type === "text" && (
          <div className="v5-nrm-insp-field grow">
            <label>고정 텍스트</label>
            <input
              className="v5-nrm-insp-inp"
              value={part.auto_config?.text_value ?? ""}
              onChange={(e) => onUpdateConfig({ text_value: e.target.value })}
              placeholder="예: SO, PFX"
            />
          </div>
        )}
        {part.part_type === "sequence" && (
          <>
            <div className="v5-nrm-insp-field w80">
              <label>자릿수</label>
              <input
                className="v5-nrm-insp-inp"
                type="number"
                min={1}
                max={10}
                value={part.auto_config?.sequence_length ?? 4}
                onChange={(e) => onUpdateConfig({ sequence_length: parseInt(e.target.value, 10) })}
              />
            </div>
            <div className="v5-nrm-insp-field w80">
              <label>시작값</label>
              <input
                className="v5-nrm-insp-inp"
                type="number"
                min={0}
                value={part.auto_config?.start_from ?? 1}
                onChange={(e) => onUpdateConfig({ start_from: parseInt(e.target.value, 10) })}
              />
            </div>
            <div className="v5-nrm-insp-field grow">
              <label>리셋 주기 (규칙 전체)</label>
              <div className="v5-nrm-seg">
                {(["none", "daily", "monthly", "yearly"] as ResetPeriod[]).map((p) => (
                  <button
                    key={p}
                    className={rule.reset_period === p ? "on" : ""}
                    onClick={() => onUpdateRule({ reset_period: p })}
                  >
                    {RESET_LABEL[p]}
                  </button>
                ))}
              </div>
            </div>
          </>
        )}
        {part.part_type === "number" && (
          <>
            <div className="v5-nrm-insp-field w80">
              <label>자릿수</label>
              <input
                className="v5-nrm-insp-inp"
                type="number"
                min={1}
                max={10}
                value={part.auto_config?.number_length ?? 3}
                onChange={(e) => onUpdateConfig({ number_length: parseInt(e.target.value, 10) })}
              />
            </div>
            <div className="v5-nrm-insp-field w80">
              <label>값</label>
              <input
                className="v5-nrm-insp-inp"
                type="number"
                value={part.auto_config?.number_value ?? 1}
                onChange={(e) => onUpdateConfig({ number_value: parseInt(e.target.value, 10) })}
              />
            </div>
          </>
        )}
        {part.part_type === "date" && (
          <div className="v5-nrm-insp-field grow">
            <label>날짜 형식</label>
            <div className="v5-nrm-seg">
              {DATE_FORMAT_OPTIONS.map((f) => (
                <button
                  key={f.value}
                  className={part.auto_config?.date_format === f.value ? "on" : ""}
                  onClick={() => onUpdateConfig({ date_format: f.value })}
                >
                  {f.value}
                </button>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function UsageList({ rule }: { rule: NumberingRuleConfig }) {
  const hasUsage = !!rule.table_name && !!rule.column_name;
  return (
    <div className="v5-nrm-usage-list">
      {hasUsage ? (
        <div className="v5-nrm-usage-row">
          <span className="ic">
            <Table2 size={11} />
          </span>
          <div className="target">
            <div className="nm">
              <span className="tbl">{rule.table_name}</span>
              <span className="sep">·</span>
              {rule.column_name}
            </div>
            <div className="desc">단일 연결 · 향후 N:M 확장 예정</div>
          </div>
          <div className="uses">
            <span className="lbl">발번</span>
            {(rule.current_sequence ?? 0).toLocaleString()}
          </div>
          <div className="mini-spark">
            {/* 발번 통계 데이터 없으므로 placeholder bars */}
            {[30, 42, 55, 48, 62, 75, 70, 82, 90, 78, 85, 92].map((h, i) => (
              <div key={i} style={{ height: `${h}%` }} />
            ))}
          </div>
          <span className="last-use">{rule.updated_at?.slice(11, 16) ?? "—"}</span>
          <button className="x-btn" title="연결 해제">
            <X size={11} />
          </button>
        </div>
      ) : (
        <div style={{ padding: "1rem 0", textAlign: "center", color: "var(--v5-text-muted)", fontSize: ".7rem" }}>
          아직 어떤 컬럼에도 연결되지 않음
        </div>
      )}
      <button className="v5-nrm-usage-add">
        <Plus size={11} /> 컬럼에 이 채번 연결 — 테이블 타입 관리로 이동
      </button>
    </div>
  );
}

function CommandPalette({
  open,
  onClose,
  rules,
  onSelectRule,
}: {
  open: boolean;
  onClose: () => void;
  rules: NumberingRuleConfig[];
  onSelectRule: (id: string) => void;
}) {
  const [query, setQuery] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (open) {
      setTimeout(() => inputRef.current?.focus(), 50);
    } else {
      setQuery("");
    }
  }, [open]);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return rules.slice(0, 5);
    return rules
      .filter((r) =>
        `${r.rule_name ?? ""} ${r.table_name ?? ""} ${r.column_name ?? ""}`.toLowerCase().includes(q)
      )
      .slice(0, 8);
  }, [rules, query]);

  if (!open) return null;

  return (
    <div className="v5-nrm-cmdk-bg on" onClick={(e) => e.target === e.currentTarget && onClose()}>
      <div className="v5-nrm-cmdk">
        <div className="v5-nrm-cmdk-srch">
          <Search size={16} />
          <input
            ref={inputRef}
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="채번 검색 또는 새로 만들기..."
          />
          <span className="v5-nrm-kbd">⌘ K</span>
        </div>
        <div className="v5-nrm-cmdk-body">
          {/* 빠른 액션 */}
          <div className="v5-nrm-cmdk-group">
            <span>빠른 액션</span>
          </div>
          <div className="v5-nrm-cmdk-item action focus">
            <span className="ic">
              <Plus size={12} />
            </span>
            <div className="v5-nrm-cmdk-row">
              <div className="v5-nrm-cmdk-title">
                새 채번{query && <> — <b>"{query}"</b> 이름으로</>} · 빈 캔버스
              </div>
              <div className="v5-nrm-cmdk-meta">파이프라인 디자이너 열기</div>
            </div>
            <div className="v5-nrm-cmdk-right">
              <span className="v5-nrm-kbd">↵</span>
            </div>
          </div>

          {/* 프리셋 */}
          <div className="v5-nrm-cmdk-group">
            <span>프리셋</span>
            <span className="num">3</span>
          </div>
          <div className="v5-nrm-cmdk-item preset">
            <span className="ic"><HashIcon size={12} /></span>
            <div className="v5-nrm-cmdk-row">
              <div className="v5-nrm-cmdk-title">간단형</div>
              <div className="v5-nrm-cmdk-meta">
                <span className="pfx">PFX</span>-<span className="seq">####</span> · prefix + sequence
              </div>
            </div>
          </div>
          <div className="v5-nrm-cmdk-item preset">
            <span className="ic"><Calendar size={12} /></span>
            <div className="v5-nrm-cmdk-row">
              <div className="v5-nrm-cmdk-title">월별 리셋 (권장)</div>
              <div className="v5-nrm-cmdk-meta">
                <span className="pfx">PFX</span>-YYYY-MM-<span className="seq">####</span>
              </div>
            </div>
          </div>
          <div className="v5-nrm-cmdk-item preset">
            <span className="ic"><Sparkles size={12} /></span>
            <div className="v5-nrm-cmdk-row">
              <div className="v5-nrm-cmdk-title">일별 리셋</div>
              <div className="v5-nrm-cmdk-meta">
                <span className="pfx">PFX</span>-YYYYMMDD-<span className="seq">###</span>
              </div>
            </div>
          </div>

          {/* 기존 채번 */}
          {filtered.length > 0 && (
            <>
              <div className="v5-nrm-cmdk-group">
                <span>기존 채번 ({filtered.length})</span>
              </div>
              {filtered.map((r) => (
                <div
                  key={r.rule_id}
                  className="v5-nrm-cmdk-item"
                  onClick={() => onSelectRule(String(r.rule_id))}
                >
                  <span className="ic">
                    <Hash size={12} />
                  </span>
                  <div className="v5-nrm-cmdk-row">
                    <div className="v5-nrm-cmdk-title">{r.rule_name}</div>
                    <div className="v5-nrm-cmdk-meta">
                      {r.rule_id} · {r.table_name && r.column_name
                        ? `${r.table_name}.${r.column_name}`
                        : "미연결"}
                    </div>
                  </div>
                  <div className="v5-nrm-cmdk-right">
                    <span className="cnt">{r.table_name && r.column_name ? 1 : 0} 컬럼</span>
                    {r.updated_at && <span>{r.updated_at.slice(11, 16)}</span>}
                  </div>
                </div>
              ))}
            </>
          )}
        </div>
        <div className="v5-nrm-cmdk-foot">
          <div style={{ display: "flex", gap: ".85rem" }}>
            <span className="hint">
              <span className="v5-nrm-kbd">↑</span>
              <span className="v5-nrm-kbd">↓</span> 이동
            </span>
            <span className="hint">
              <span className="v5-nrm-kbd">↵</span> 선택
            </span>
          </div>
          <span className="hint">
            <span className="v5-nrm-kbd">Esc</span> 닫기
          </span>
        </div>
      </div>
    </div>
  );
}

/* ===================================================================
   Helpers
   =================================================================== */

const TONE_BY_HASH = ["cyan", "primary", "pink", "green", "amber", "red"] as const;

function useTone(seed: string): string {
  return useMemo(() => {
    let hash = 0;
    for (let i = 0; i < seed.length; i++) {
      hash = (hash << 5) - hash + seed.charCodeAt(i);
      hash |= 0;
    }
    return TONE_BY_HASH[Math.abs(hash) % TONE_BY_HASH.length];
  }, [seed]);
}

function renderPartValue(p: NumberingRulePart, today: Date, seq: number): string {
  switch (p.part_type) {
    case "text":
      return p.auto_config?.text_value ?? p.manual_config?.value ?? "TXT";
    case "sequence": {
      const len = p.auto_config?.sequence_length ?? 4;
      return String(seq).padStart(len, "0");
    }
    case "number": {
      const len = p.auto_config?.number_length ?? 3;
      const val = p.auto_config?.number_value ?? 1;
      return String(val).padStart(len, "0");
    }
    case "date": {
      const fmt = p.auto_config?.date_format ?? "YYYY";
      const yyyy = today.getFullYear();
      const mm = String(today.getMonth() + 1).padStart(2, "0");
      const dd = String(today.getDate()).padStart(2, "0");
      switch (fmt) {
        case "YYYY": return String(yyyy);
        case "YY": return String(yyyy).slice(-2);
        case "YYYYMM": return `${yyyy}${mm}`;
        case "YYMM": return `${String(yyyy).slice(-2)}${mm}`;
        case "YYYYMMDD": return `${yyyy}${mm}${dd}`;
        case "YYMMDD": return `${String(yyyy).slice(-2)}${mm}${dd}`;
        default: return "DATE";
      }
    }
    case "category":
      return "CAT";
    case "reference":
      return "REF";
    default:
      return "?";
  }
}

function buildPatternPreview(rule: NumberingRuleConfig): string {
  const sep = rule.separator ?? "-";
  if (!rule.parts || rule.parts.length === 0) return '<span style="opacity:.5">(빈 규칙)</span>';
  const parts = [...rule.parts].sort((a, b) => a.order - b.order);
  return parts
    .map((p) => {
      switch (p.part_type) {
        case "text":
          return `<span class="pfx">${p.auto_config?.text_value ?? "TXT"}</span>`;
        case "date":
          return `<span>${p.auto_config?.date_format ?? "YYYY"}</span>`;
        case "sequence": {
          const len = p.auto_config?.sequence_length ?? 4;
          return `<span class="seq">${"#".repeat(len)}</span>`;
        }
        case "number": {
          const len = p.auto_config?.number_length ?? 3;
          return `<span>${"0".repeat(len)}</span>`;
        }
        case "category":
          return `<span>CAT</span>`;
        case "reference":
          return `<span>REF</span>`;
        default:
          return "?";
      }
    })
    .join(sep);
}
