"use client";

import React, { useState, useEffect, useCallback, useMemo } from "react";
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 {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
  DialogDescription,
} from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable";
import {
  ChevronRight,
  ChevronDown,
  ChevronsDown,
  ChevronsUp,
  Plus,
  Trash2,
  Search,
  Loader2,
  Inbox,
  Upload,
  Download,
  GitBranch,
  History,
  CheckCircle2,
  RotateCcw,
  X,
  FileText,
  Play,
  Copy,
  Settings2,
  Save,
  Package,
  Pencil,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth";
import { toast } from "sonner";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
import { exportToExcel } from "@/lib/utils/excelExport";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";

// ─── 상수 ───────────────────────────────────────
const BOM_TABLE = "bom";
const BOM_DETAIL_TABLE = "bom_detail";

const BOM_COLUMNS = [
  { key: "item_code", label: "품목코드" },
  { key: "item_name", label: "품명" },
  { key: "bom_type", label: "BOM 유형" },
  { key: "version", label: "버전" },
  { key: "effective_date", label: "유효시작일" },
  { key: "status", label: "상태" },
];


const STATUS_MAP: Record<string, { label: string; className: string }> = {
  active: { label: "활성", className: "bg-success/10 text-success" },
  draft: { label: "초안", className: "bg-warning/10 text-warning" },
  expired: { label: "만료", className: "bg-destructive/10 text-destructive" },
  inactive: { label: "비활성", className: "bg-muted text-muted-foreground" },
  활성: { label: "활성", className: "bg-success/10 text-success" },
  초안: { label: "초안", className: "bg-warning/10 text-warning" },
  만료: { label: "만료", className: "bg-destructive/10 text-destructive" },
  비활성: { label: "비활성", className: "bg-muted text-muted-foreground" },
};

const VERSION_STATUS_MAP: Record<string, { label: string; className: string }> = {
  active: { label: "사용중", className: "bg-success/10 text-success" },
  developing: { label: "개발중", className: "bg-warning/10 text-warning" },
  inactive: { label: "비활성", className: "bg-muted text-muted-foreground" },
};

const BOM_TYPE_OPTIONS = [
  { code: "production", label: "생산 BOM" },
  { code: "design", label: "설계 BOM" },
  { code: "cost", label: "원가 BOM" },
];

// ─── 인터페이스 ─────────────────────────────────
interface BomRow {
  id: string;
  item_id?: string;
  item_code?: string;
  item_name?: string;
  bom_type?: string;
  version?: string;
  base_qty?: string;
  unit?: string;
  status?: string;
  effective_date?: string;
  expiry_date?: string;
  remark?: string;
  current_version_id?: string;
  [key: string]: any;
}

interface BomDetail {
  id: string;
  bom_id: string;
  version_id?: string;
  parent_detail_id?: string | null;
  child_item_id?: string;
  item_number?: string;
  item_name?: string;
  item_type?: string;
  quantity?: string;
  unit?: string;
  process_type?: string;
  loss_rate?: string;
  remark?: string;
  level?: string;
  seq_no?: string;
  spec?: string;
  writer?: string;
  updated_date?: string;
  [key: string]: any;
}

interface BomVersion {
  id: string;
  bom_id: string;
  version_name: string;
  revision?: number;
  status: string;
  created_by?: string;
  created_date?: string;
  detail_count?: number;
}

interface BomHistoryEntry {
  id: string;
  bom_id: string;
  revision?: string;
  version?: string;
  change_type: string;
  change_description?: string;
  changed_by?: string;
  changed_date?: string;
}

interface TreeNode extends BomDetail {
  children: TreeNode[];
  expanded?: boolean;
  _isNew?: boolean;        // 신규 노드 (DB에 없음)
  _isModified?: boolean;   // 수정된 노드
  _tempId?: string;        // 임시 ID
  _level?: number;         // flattenTree에서 사용
  _isVirtualRoot?: boolean; // 가상 루트(BOM 마스터) 노드
}

// ─── 트리 구성 헬퍼 ─────────────────────────────
function buildTree(details: BomDetail[]): TreeNode[] {
  const nodeMap = new Map<string, TreeNode>();
  const roots: TreeNode[] = [];

  for (const d of details) {
    nodeMap.set(d.id, { ...d, children: [], expanded: true });
  }

  for (const d of details) {
    const node = nodeMap.get(d.id)!;
    if (d.parent_detail_id && nodeMap.has(d.parent_detail_id)) {
      nodeMap.get(d.parent_detail_id)!.children.push(node);
    } else {
      roots.push(node);
    }
  }

  return roots;
}

function flattenTree(nodes: TreeNode[], level = 0): (TreeNode & { _level: number })[] {
  const result: (TreeNode & { _level: number })[] = [];
  for (const node of nodes) {
    result.push({ ...node, _level: level });
    if (node.expanded && node.children.length > 0) {
      result.push(...flattenTree(node.children, level + 1));
    }
  }
  return result;
}

// ─── 타입 뱃지 색상 ────────────────────────────
// BOM 이력 변경 유형 색상
const CHANGE_TYPE_STYLE: Record<string, string> = {
  "등록": "bg-primary/10 text-primary ring-primary/20",
  "수정": "bg-amber-50 text-amber-600 ring-amber-200",
  "추가": "bg-emerald-50 text-emerald-600 ring-emerald-200",
  "삭제": "bg-destructive/10 text-destructive ring-red-200",
  "버전변경": "bg-blue-50 text-blue-600 ring-blue-200",
  "excel_upload": "bg-blue-50 text-blue-600 ring-blue-200",
};
const CHANGE_TYPE_OPTIONS = ["등록", "수정", "추가", "삭제", "버전변경", "기타"];

function getItemTypeBadge(type?: string) {
  switch (type) {
    case "원자재":
    case "raw":
      return { label: "원자재", className: "bg-success/10 text-success border-success/20" };
    case "반제품":
    case "semi":
      return { label: "반제품", className: "bg-warning/10 text-warning border-warning/20" };
    case "완제품":
    case "finished":
      return { label: "완제품", className: "bg-primary/10 text-primary border-primary/20" };
    default:
      return { label: type || "-", className: "bg-muted text-muted-foreground" };
  }
}

// ─── 메인 컴포넌트 ──────────────────────────────
export default function BomManagementPage() {
  const { user } = useAuth();
  const { confirm, ConfirmDialogComponent } = useConfirmDialog();
  const ts = useTableSettings("c16-bom", BOM_TABLE, BOM_COLUMNS);

  // BOM 목록 상태
  const [bomList, setBomList] = useState<BomRow[]>([]);
  const [loading, setLoading] = useState(false);
  const [totalCount, setTotalCount] = useState(0);
  const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
  const [selectedBomId, setSelectedBomId] = useState<string | null>(null);
  const [checkedIds, setCheckedIds] = useState<string[]>([]);

  // 상세 데이터
  const [bomHeader, setBomHeader] = useState<BomRow | null>(null);
  const [bomDetails, setBomDetails] = useState<BomDetail[]>([]);
  const [treeNodes, setTreeNodes] = useState<TreeNode[]>([]);
  const [detailLoading, setDetailLoading] = useState(false);

  // 트리 토글
  const [treeDirection, setTreeDirection] = useState<"forward" | "reverse">("forward");
  const [treeBasis, setTreeBasis] = useState<"material" | "process">("material");

  // 버전 관리
  const [versions, setVersions] = useState<BomVersion[]>([]);
  const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
  const [versionLoading, setVersionLoading] = useState(false);
  const [newVersionName, setNewVersionName] = useState("");
  const [showNewVersionDialog, setShowNewVersionDialog] = useState(false);

  // 이력 관리
  const [historyList, setHistoryList] = useState<BomHistoryEntry[]>([]);
  const [historyLoading, setHistoryLoading] = useState(false);
  const [showHistoryDialog, setShowHistoryDialog] = useState(false);
  const [newHistory, setNewHistory] = useState({ change_type: "", change_description: "" });

  // 등록 모달
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [isEditMode, setIsEditMode] = useState(false);
  const [saving, setSaving] = useState(false);
  const [masterForm, setMasterForm] = useState<Record<string, any>>({
    item_id: "",
    item_code: "",
    item_name: "",
    bom_type: "",
    base_qty: "1",
    unit: "",
    version: "1.0",
    status: "draft",
    effective_date: "",
    expiry_date: "",
    remark: "",
  });
  const [modalDetailRows, setModalDetailRows] = useState<any[]>([]);

  // 품목 검색 모달
  const [showItemSearchModal, setShowItemSearchModal] = useState(false);
  const [itemSearchKeyword, setItemSearchKeyword] = useState("");
  const [itemSearchResults, setItemSearchResults] = useState<any[]>([]);
  const [itemSearchLoading, setItemSearchLoading] = useState(false);
  const [itemSearchTarget, setItemSearchTarget] = useState<"master" | "detail">("master");

  // 엑셀 업로드
  const [showExcelUpload, setShowExcelUpload] = useState(false);
  const [excelFile, setExcelFile] = useState<File | null>(null);
  const [excelUploading, setExcelUploading] = useState(false);

  // 우측 탭
  const [rightTab, setRightTab] = useState<"tree" | "version" | "history">("tree");

  // 카테고리 옵션
  const [categoryOptions, setCategoryOptions] = useState<Record<string, { code: string; label: string }[]>>({});
  // 사용자 맵 (userId → userName)
  const [userMap, setUserMap] = useState<Record<string, string>>({});

  // 트리 편집 state
  const [editingTree, setEditingTree] = useState<TreeNode[]>([]);
  const [treeHasChanges, setTreeHasChanges] = useState(false);

  // 공정 마스터 목록
  const [processOptions, setProcessOptions] = useState<{ code: string; label: string }[]>([]);
  useEffect(() => {
    apiClient.get("/process-info/processes?useYn=Y").then((res) => {
      const list = res.data?.data || [];
      setProcessOptions(list.map((p: any) => ({ code: p.process_code, label: `${p.process_name} (${p.process_code})` })));
    }).catch(() => {});
  }, []);

  // 트리뷰 행 선택 / 수정 모달
  const [selectedTreeNodeId, setSelectedTreeNodeId] = useState<string | null>(null);
  const [editingNodeId, setEditingNodeId] = useState<string | null>(null);
  const [treeEditModalOpen, setTreeEditModalOpen] = useState(false);
  const [treeEditTarget, setTreeEditTarget] = useState<TreeNode | null>(null);
  const [treeEditForm, setTreeEditForm] = useState<Record<string, any>>({});

  // 역전개 (Where Used)
  const [treeViewMode, setTreeViewMode] = useState<"forward" | "reverse">("forward");
  const [whereUsedData, setWhereUsedData] = useState<any[]>([]);
  const [whereUsedLoading, setWhereUsedLoading] = useState(false);
  const [originalDetailIds, setOriginalDetailIds] = useState<Set<string>>(new Set());
  const [addTargetParentId, setAddTargetParentId] = useState<string | null>(null);
  const [treeItemSearchOpen, setTreeItemSearchOpen] = useState(false);
  const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());

  // ─── 데이터 로드 ──────────────────────────────
  const fetchBomList = useCallback(async () => {
    setLoading(true);
    try {
      const filters = searchFilters.map((f) => ({ columnName: f.columnName, operator: f.operator, value: f.value }));

      const res = await apiClient.post(`/table-management/tables/${BOM_TABLE}/data`, {
        page: 1,
        size: 0,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
        sort: { columnName: "created_at", order: "desc" },
      });

      // DB 컬럼이 item_type/expired_date → 프론트 내부에서는 bom_type/expiry_date로 통일
      const rawRows = res.data?.data?.data || res.data?.data?.rows || [];
      const rows = rawRows.map((r: any) => ({
        ...r,
        bom_type: r.bom_type ?? r.item_type,
        expiry_date: r.expiry_date ?? r.expired_date,
      }));
      setBomList(rows);
      setTotalCount(rows.length);
    } catch (err: any) {
      toast.error("BOM 목록 조회에 실패했어요");
    } finally {
      setLoading(false);
    }
  }, [searchFilters]);

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

  // 카테고리 옵션 로드
  useEffect(() => {
    const loadCategories = async () => {
      try {
        const columns = ["bom_type", "status"];
        const detailColumns = ["process_type"];
        const results: Record<string, { code: string; label: string }[]> = {};

        for (const col of columns) {
          try {
            const res = await apiClient.get(`/table-categories/${BOM_TABLE}/${col}/values`);
            if (res.data?.data?.length > 0) {
              const flatten = (items: any[]): { code: string; label: string }[] => {
                const result: { code: string; label: string }[] = [];
                for (const item of items) {
                  result.push({ code: item.value || item.code, label: item.label || item.value });
                  if (item.children?.length) {
                    result.push(...flatten(item.children));
                  }
                }
                return result;
              };
              results[col] = flatten(res.data.data);
            }
          } catch {}
        }

        // bom_detail 테이블의 카테고리
        for (const col of detailColumns) {
          try {
            const res = await apiClient.get(`/table-categories/bom_detail/${col}/values`);
            if (res.data?.data?.length > 0) {
              const flatten = (items: any[]): { code: string; label: string }[] => {
                const result: { code: string; label: string }[] = [];
                for (const item of items) {
                  result.push({ code: item.value || item.code, label: item.label || item.value });
                  if (item.children?.length) result.push(...flatten(item.children));
                }
                return result;
              };
              results[col] = flatten(res.data.data);
            }
          } catch {}
        }

        // item_info의 division, inventory_unit 카테고리
        for (const itemCol of ["division", "inventory_unit"]) {
          try {
            const res = await apiClient.get(`/table-categories/item_info/${itemCol}/values`);
            if (res.data?.data?.length > 0) {
              const flatten = (items: any[]): { code: string; label: string }[] => {
                const result: { code: string; label: string }[] = [];
                for (const item of items) {
                  result.push({ code: item.valueCode || item.value || item.code, label: item.valueLabel || item.label || item.value });
                  if (item.children?.length) result.push(...flatten(item.children));
                }
                return result;
              };
              results[itemCol] = flatten(res.data.data);
            }
          } catch {}
        }

        setCategoryOptions(results);
      } catch {}
    };
    loadCategories();
    // 사용자 목록 로드
    apiClient.get("/admin/users", { params: { size: 9999 } }).then((res) => {
      const users = res.data?.data || res.data || [];
      const map: Record<string, string> = {};
      for (const u of users) {
        const id = u.userId || u.user_id || u.id;
        if (id) map[id] = u.userName || u.user_name || u.name || id;
      }
      setUserMap(map);
    }).catch(() => {});
  }, []);

  // ─── BOM 상세 로드 ────────────────────────────
  const fetchBomDetail = useCallback(async (bomId: string) => {
    setDetailLoading(true);
    try {
      // 헤더 조회 (DB 컬럼 item_type/expired_date → bom_type/expiry_date로 매핑)
      const headerRes = await apiClient.get(`/bom/${bomId}/header`);
      const rawHeader = headerRes.data?.data || headerRes.data;
      const header = rawHeader
        ? {
            ...rawHeader,
            bom_type: rawHeader.bom_type ?? rawHeader.item_type,
            expiry_date: rawHeader.expiry_date ?? rawHeader.expired_date,
          }
        : null;
      setBomHeader(header);
      setCurrentVersionId(header?.current_version_id || null);

      // 디테일 조회 (현재 버전)
      const versionId = header?.current_version_id;
      const detailFilters: any[] = [{ columnName: "bom_id", operator: "equals", value: bomId }];
      if (versionId) {
        detailFilters.push({ columnName: "version_id", operator: "equals", value: versionId });
      }

      const detailRes = await apiClient.post(`/table-management/tables/${BOM_DETAIL_TABLE}/data`, {
        page: 1,
        size: 1000,
        dataFilter: { enabled: true, filters: detailFilters },
        sort: { columnName: "parent_detail_id", order: "asc" },
      });

      let details = detailRes.data?.data?.data || detailRes.data?.data?.rows || [];

      // item_info 조인을 위해 child_item_id 수집
      const itemIds = [...new Set(details.map((d: any) => d.child_item_id).filter(Boolean))];
      if (itemIds.length > 0) {
        try {
          const itemRes = await apiClient.post(`/table-management/tables/item_info/data`, {
            page: 1,
            size: 1000,
            dataFilter: {
              enabled: true,
              filters: [{ columnName: "id", operator: "in", value: itemIds }],
            },
          });
          const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || [];
          const itemMap = new Map(items.map((i: any) => [i.id, i]));

          details = details.map((d: any) => {
            const item = itemMap.get(d.child_item_id) as any;
            const divisionRaw = item?.division || "";
            // 카테고리 코드 → 라벨 변환 (쉼표 구분 다중값 지원)
            const divisionLabel = divisionRaw.split(",").map((code: string) => {
              const c = code.trim();
              return categoryOptions["division"]?.find((o) => o.code === c)?.label || c;
            }).filter((v: string) => v && v !== "s").join(", ");
            const rawUnit = d.unit || item?.inventory_unit || "";
            const unitLabel = categoryOptions["inventory_unit"]?.find((o) => o.code === rawUnit)?.label || rawUnit;
            return {
              ...d,
              item_number: item?.item_number || "",
              item_name: item?.item_name || "",
              item_type: divisionLabel,
              unit: unitLabel,
              spec: item?.size || item?.spec || "",
              writer: d.writer || "",
              updated_date: d.updated_at || d.updated_date || "",
            };
          });
        } catch {}
      }

      setBomDetails(details);
      const tree = buildTree(details);
      setTreeNodes(tree);

      // 편집용 트리 동기화
      setEditingTree(buildTree(details));
      setOriginalDetailIds(new Set(details.map((d: any) => d.id)));
      setTreeHasChanges(false);
      // 기본 전개: 모든 노드 + 가상 루트 펼침
      const allIds = new Set<string>();
      if (header?.id) allIds.add(`__root_${header.id}`);
      const collectIds = (nodes: TreeNode[]) => {
        for (const n of nodes) {
          allIds.add(n.id || "");
          if (n.children.length > 0) collectIds(n.children);
        }
      };
      collectIds(tree);
      setExpandedNodes(allIds);
    } catch (err: any) {
      toast.error("BOM 상세 조회에 실패했어요");
    } finally {
      setDetailLoading(false);
    }
  }, [categoryOptions]);

  // 버전 목록 로드
  const fetchVersions = useCallback(async (bomId: string) => {
    setVersionLoading(true);
    try {
      const res = await apiClient.get(`/bom/${bomId}/versions`);
      const resData = res.data;
      setVersions(resData?.data || []);
      setCurrentVersionId(resData?.currentVersionId || null);
    } catch (err: any) {
      toast.error("버전 목록 조회에 실패했어요");
    } finally {
      setVersionLoading(false);
    }
  }, []);

  // 이력 목록 로드
  const fetchHistory = useCallback(async (bomId: string) => {
    setHistoryLoading(true);
    try {
      const res = await apiClient.get(`/bom/${bomId}/history`);
      setHistoryList(res.data?.data || []);
    } catch (err: any) {
      toast.error("이력 조회에 실패했어요");
    } finally {
      setHistoryLoading(false);
    }
  }, []);

  // BOM 선택 시 상세 로드
  useEffect(() => {
    if (selectedBomId) {
      fetchBomDetail(selectedBomId);
      if (rightTab === "version") fetchVersions(selectedBomId);
      if (rightTab === "history") fetchHistory(selectedBomId);
    } else {
      setBomHeader(null);
      setBomDetails([]);
      setTreeNodes([]);
      setEditingTree([]);
      setTreeHasChanges(false);
      setOriginalDetailIds(new Set());
      setExpandedNodes(new Set());
      setVersions([]);
      setHistoryList([]);
    }
  }, [selectedBomId, fetchBomDetail, fetchVersions, fetchHistory, rightTab]);

  // ─── 트리 토글 핸들러 ─────────────────────────
  const toggleTreeNode = useCallback((nodeId: string) => {
    const toggle = (nodes: TreeNode[]): TreeNode[] =>
      nodes.map((n) =>
        n.id === nodeId
          ? { ...n, expanded: !n.expanded }
          : { ...n, children: toggle(n.children) }
      );
    setTreeNodes((prev) => toggle(prev));
  }, []);

  const flatTree = useMemo(() => flattenTree(treeNodes), [treeNodes]);

  // ─── 트리 편집 헬퍼 ──────────────────────────

  // 트리 노드 토글 (펼침/접음)
  const toggleEditTreeNode = useCallback((nodeId: string) => {
    setExpandedNodes((prev) => {
      const next = new Set(prev);
      if (next.has(nodeId)) next.delete(nodeId);
      else next.add(nodeId);
      return next;
    });
  }, []);

  // 전체 펼침
  const expandAll = useCallback(() => {
    const allIds = new Set<string>();
    if (bomHeader?.id) allIds.add(`__root_${bomHeader.id}`);
    const collect = (nodes: TreeNode[]) => {
      for (const n of nodes) {
        allIds.add(n.id || n._tempId || "");
        if (n.children.length > 0) collect(n.children);
      }
    };
    collect(editingTree);
    setExpandedNodes(allIds);
  }, [editingTree, bomHeader]);

  // 역전개 (Where Used) 조회 — 현재 BOM 품목이 어디에 사용되는지
  const fetchWhereUsed = useCallback(async () => {
    if (!bomHeader?.item_code && !bomHeader?.item_id) return;
    setWhereUsedLoading(true);
    try {
      // bom_detail에서 child_item_id가 현재 품목인 행 조회
      const itemId = bomHeader.item_id || bomHeader.id;
      const res = await apiClient.post(`/table-management/tables/bom_detail/data`, {
        page: 1, size: 0,
        dataFilter: { enabled: true, filters: [{ columnName: "child_item_id", operator: "equals", value: itemId }] },
        autoFilter: true,
      });
      const details = res.data?.data?.data || res.data?.data?.rows || [];

      // 각 bom_id로 BOM 마스터 정보 조회
      const bomIds = [...new Set(details.map((d: any) => d.bom_id).filter(Boolean))];
      const results: any[] = [];
      for (const bomId of bomIds) {
        try {
          const bomRes = await apiClient.post(`/table-management/tables/bom/data`, {
            page: 1, size: 1,
            dataFilter: { enabled: true, filters: [{ columnName: "id", operator: "equals", value: bomId }] },
            autoFilter: true,
          });
          const bom = (bomRes.data?.data?.data || bomRes.data?.data?.rows || [])[0];
          if (bom) {
            const detail = details.find((d: any) => d.bom_id === bomId);
            results.push({
              bom_id: bomId,
              item_code: bom.item_code || bom.item_number || "",
              item_name: bom.item_name || "",
              quantity: detail?.quantity || "1",
              unit: detail?.unit || "",
              process_type: detail?.process_type || "",
            });
          }
        } catch {}
      }
      setWhereUsedData(results);
    } catch {
      setWhereUsedData([]);
    } finally {
      setWhereUsedLoading(false);
    }
  }, [bomHeader]);

  // 전체 접음
  const collapseAll = useCallback(() => {
    // 루트는 항상 펼침 유지, 나머지만 접음
    const rootOnly = new Set<string>();
    if (bomHeader?.id) rootOnly.add(`__root_${bomHeader.id}`);
    setExpandedNodes(rootOnly);
  }, [bomHeader]);

  // 편집 트리 평탄화 (useMemo) — 가상 루트(BOM 마스터) 포함
  const editingFlatTree = useMemo(() => {
    const result: TreeNode[] = [];
    // 가상 루트 (BOM 마스터)
    if (bomHeader) {
      const rootId = `__root_${bomHeader.id}`;
      result.push({
        id: rootId,
        bom_id: bomHeader.id,
        _tempId: undefined,
        _isNew: false,
        _isModified: false,
        _level: 0,
        _isVirtualRoot: true,
        item_number: bomHeader.item_code || bomHeader.item_number || "",
        item_name: bomHeader.item_name || "",
        quantity: bomHeader.base_qty || "1",
        unit: bomHeader.unit || "",
        process_type: "",
        loss_rate: "",
        remark: bomHeader.remark || "",
        spec: "",
        writer: "",
        updated_date: "",
        item_type: "",
        children: editingTree,
      } as any);
    }
    // 하위 노드 평탄화 (레벨 +1)
    const flatten = (nodes: TreeNode[], level: number) => {
      for (const node of nodes) {
        const nodeKey = node.id || node._tempId || "";
        result.push({ ...node, _level: level });
        if (node.children.length > 0 && expandedNodes.has(nodeKey)) {
          flatten(node.children, level + 1);
        }
      }
    };
    const rootId = bomHeader ? `__root_${bomHeader.id}` : "";
    if (!bomHeader || expandedNodes.has(rootId)) {
      flatten(editingTree, bomHeader ? 1 : 0);
    }
    return result;
  }, [editingTree, expandedNodes, bomHeader]);

  // 트리에서 노드를 찾아서 업데이트
  const updateTreeNode = (tree: TreeNode[], nodeId: string, updater: (node: TreeNode) => TreeNode): TreeNode[] => {
    return tree.map(node => {
      if ((node.id || node._tempId) === nodeId) return updater(node);
      if (node.children.length > 0) return { ...node, children: updateTreeNode(node.children, nodeId, updater) };
      return node;
    });
  };

  // 트리에서 노드 삭제 (하위 포함)
  const removeTreeNode = (tree: TreeNode[], nodeId: string): TreeNode[] => {
    return tree.filter(node => {
      if ((node.id || node._tempId) === nodeId) return false;
      if (node.children.length > 0) node.children = removeTreeNode(node.children, nodeId);
      return true;
    });
  };

  // 트리에 자식 노드 추가
  const addChildToNode = (tree: TreeNode[], parentId: string | null, newNode: TreeNode): TreeNode[] => {
    if (parentId === null) return [...tree, newNode];
    return tree.map(node => {
      const nodeKey = node.id || node._tempId;
      if (nodeKey === parentId) {
        return { ...node, children: [...node.children, newNode], expanded: true };
      }
      if (node.children.length > 0) return { ...node, children: addChildToNode(node.children, parentId, newNode) };
      return node;
    });
  };

  // 노드 찾기 헬퍼
  const findNodeById = (nodes: TreeNode[], id: string): TreeNode | null => {
    for (const node of nodes) {
      if ((node.id || node._tempId) === id) return node;
      const found = findNodeById(node.children, id);
      if (found) return found;
    }
    return null;
  };

  // 인라인 필드 변경
  const handleTreeFieldChange = (nodeId: string, field: string, value: string) => {
    setEditingTree(prev => updateTreeNode(prev, nodeId, node => ({
      ...node, [field]: value, _isModified: !node._isNew ? true : node._isModified,
    })));
    setTreeHasChanges(true);
  };

  // 노드 삭제
  const handleTreeDeleteNode = async (nodeId: string) => {
    if (!await confirm("이 품목과 하위 품목을 삭제할까요?", { variant: "destructive" })) return;
    setEditingTree(prev => removeTreeNode(prev, nodeId));
    setTreeHasChanges(true);
  };

  // 하위 품목 추가 시작
  const handleTreeAddChild = (parentId: string | null) => {
    setAddTargetParentId(parentId);
    setTreeItemSearchOpen(true);
    searchItems("");
  };

  // 트리 품목 선택 완료 (트리에 추가)
  const handleTreeItemSelect = (item: any) => {
    // 1. 자기 자신 추가 방지 (BOM 마스터 품목과 동일한 품목)
    if (bomHeader && (item.id === bomHeader.item_id || item.item_number === bomHeader.item_code)) {
      toast.error("자기 자신을 BOM에 추가할 수 없습니다");
      return;
    }

    // 2. 같은 경로에 동일 품목 중복 방지 (조상 노드에 같은 품목이 있으면 순환)
    const getAncestorItemIds = (tree: TreeNode[], targetId: string | null): Set<string> => {
      const ids = new Set<string>();
      if (!targetId) return ids;
      const collectPath = (nodes: TreeNode[], path: TreeNode[]): boolean => {
        for (const n of nodes) {
          const nk = n.id || n._tempId || "";
          if (nk === targetId) {
            path.forEach(p => { if (p.child_item_id) ids.add(p.child_item_id); });
            if (n.child_item_id) ids.add(n.child_item_id);
            return true;
          }
          if (n.children.length > 0 && collectPath(n.children, [...path, n])) return true;
        }
        return false;
      };
      collectPath(tree, []);
      return ids;
    };
    const ancestorIds = getAncestorItemIds(editingTree, addTargetParentId);
    if (ancestorIds.has(item.id)) {
      toast.error("상위 경로에 이미 동일 품목이 있어 순환 참조가 발생합니다");
      return;
    }

    // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음

    const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`;
    const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null;
    const newLevel = parentNode ? ((parentNode._level ?? parentNode.level ?? 0) as number) + 1 : 0;

    const newNode: TreeNode = {
      id: "",
      _tempId: tempId,
      _isNew: true,
      bom_id: selectedBomId || "",
      parent_detail_id: addTargetParentId,
      child_item_id: item.id,
      item_number: item.item_number || "",
      item_name: item.item_name || "",
      item_type: item.division || "",
      level: String(newLevel),
      quantity: "1",
      unit: item.inventory_unit || "",
      process_type: "",
      loss_rate: "0",
      remark: "",
      children: [],
      expanded: true,
    };

    setEditingTree(prev => addChildToNode(prev, addTargetParentId, newNode));
    setExpandedNodes(prev => {
      const next = new Set(prev);
      next.add(tempId);
      if (addTargetParentId) next.add(addTargetParentId);
      return next;
    });
    setTreeHasChanges(true);
    setTreeItemSearchOpen(false);
  };

  // ─── 트리 저장 ────────────────────────────────
  const handleSaveTree = async () => {
    if (!selectedBomId || !treeHasChanges) return;

    setSaving(true);
    try {
      // 1. version_id 확보
      let versionId = currentVersionId;
      if (!versionId) {
        const initRes = await apiClient.post(`/bom/${selectedBomId}/initialize-version`, {});
        versionId = initRes.data?.data?.versionId;
      }

      // 2. 트리 평탄화
      const flatNodes: any[] = [];
      const flattenForSave = (nodes: TreeNode[], parentId: string | null, level: number) => {
        nodes.forEach((node, idx) => {
          const nodeKey = node.id || node._tempId || "";
          flatNodes.push({
            ...node,
            _nodeKey: nodeKey,
            parent_detail_id: parentId,
            level: String(level),
            seq_no: String(idx + 1),
            bom_id: selectedBomId,
            version_id: versionId,
          });
          if (node.children.length > 0) flattenForSave(node.children, nodeKey, level + 1);
        });
      };
      flattenForSave(editingTree, null, 0);

      // 3. 분류: INSERT / UPDATE / DELETE
      const currentIds = new Set(flatNodes.filter(n => n.id && !n._isNew).map(n => n.id));
      const toDelete = [...originalDetailIds].filter(id => !currentIds.has(id));
      const toInsert = flatNodes.filter(n => n._isNew);
      const toUpdate = flatNodes.filter(n => n.id && !n._isNew && n._isModified);

      // 4. DELETE
      if (toDelete.length > 0) {
        await apiClient.delete(`/table-management/tables/bom_detail/delete`, {
          data: toDelete.map(id => ({ id })),
        });
      }

      // 5. INSERT (순서 중요: 부모 먼저)
      const tempToRealId: Record<string, string> = {};
      for (const node of toInsert) {
        let parentId = node.parent_detail_id;
        if (parentId && tempToRealId[parentId]) parentId = tempToRealId[parentId];

        const newId = crypto.randomUUID();
        const res = await apiClient.post(`/table-management/tables/bom_detail/add`, {
          id: newId,
          bom_id: selectedBomId,
          version_id: versionId,
          parent_detail_id: parentId && !parentId.startsWith("temp_") ? parentId : null,
          child_item_id: node.child_item_id,
          level: node.level,
          seq_no: node.seq_no,
          quantity: node.quantity || "1",
          unit: node.unit || "",
          process_type: node.process_type || "",
          loss_rate: node.loss_rate || "0",
          remark: node.remark || "",
          writer: user?.userId || "",
          company_code: user?.company_code || "",
        });

        const realId = res.data?.data?.id || newId;
        if (node._tempId) tempToRealId[node._tempId] = realId;
      }

      // 6. INSERT된 노드의 자식들 parent_detail_id 업데이트
      for (const node of toInsert) {
        if (!node._tempId) continue;
        const childNodes = flatNodes.filter(n => n.parent_detail_id === node._tempId);
        for (const child of childNodes) {
          if (child.id && !child._isNew && tempToRealId[node._tempId]) {
            await apiClient.put(`/table-management/tables/bom_detail/edit`, {
              originalData: { id: child.id },
              updatedData: { parent_detail_id: tempToRealId[node._tempId] },
            });
          }
        }
      }

      // 7. UPDATE
      for (const node of toUpdate) {
        let parentId = node.parent_detail_id;
        if (parentId && tempToRealId[parentId]) parentId = tempToRealId[parentId];

        await apiClient.put(`/table-management/tables/bom_detail/edit`, {
          originalData: { id: node.id },
          updatedData: {
            bom_id: selectedBomId,
            version_id: versionId,
            quantity: node.quantity,
            unit: node.unit,
            process_type: node.process_type,
            loss_rate: node.loss_rate,
            remark: node.remark,
            level: node.level,
            seq_no: node.seq_no,
            parent_detail_id: parentId,
          },
        });
      }

      // 이력 자동 기록 (변경 내용 상세)
      const historyParts: string[] = [];
      // 추가된 품목
      for (const n of toInsert) {
        historyParts.push(`${n.item_number || "품목"} 추가 (소요량: ${n.quantity || 1})`);
      }
      // 수정된 품목 (변경 전후 비교)
      for (const n of toUpdate) {
        const orig = bomDetails.find((d: any) => d.id === n.id);
        if (orig) {
          const changes: string[] = [];
          if (String(orig.quantity || "") !== String(n.quantity || "")) changes.push(`소요량 ${orig.quantity || "-"} → ${n.quantity || "-"}`);
          if (String(orig.process_type || "") !== String(n.process_type || "")) changes.push(`공정 변경`);
          if (String(orig.loss_rate || "") !== String(n.loss_rate || "")) changes.push(`손실율 ${orig.loss_rate || "0"} → ${n.loss_rate || "0"}`);
          if (String(orig.remark || "") !== String(n.remark || "")) changes.push(`비고 변경`);
          if (changes.length > 0) historyParts.push(`${n.item_number || "품목"}: ${changes.join(", ")}`);
        }
      }
      // 삭제된 품목
      for (const id of toDelete) {
        const orig = bomDetails.find((d: any) => d.id === id);
        historyParts.push(`${orig?.item_number || "품목"} 삭제`);
      }
      if (historyParts.length > 0) {
        apiClient.post(`/bom/${selectedBomId}/history`, {
          change_type: historyParts.some(p => p.includes("추가")) ? "추가" : historyParts.some(p => p.includes("삭제")) ? "삭제" : "수정",
          change_description: historyParts.join("; "),
        }).catch(() => {});
      }

      toast.success("BOM 트리가 저장되었어요");
      setTreeHasChanges(false);
      fetchBomDetail(selectedBomId);
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "트리 저장에 실패했어요");
    } finally {
      setSaving(false);
    }
  };

  // ─── BOM 삭제 ────────────────────────────────
  const handleDeleteBom = async () => {
    if (checkedIds.length === 0) {
      toast.error("삭제할 BOM을 선택해주세요");
      return;
    }
    const ok = await confirm(`선택한 ${checkedIds.length}건의 BOM을 삭제할까요?`);
    if (!ok) return;
    try {
      await apiClient.delete(`/table-management/tables/${BOM_TABLE}/delete`, {
        data: checkedIds.map((id) => ({ id })),
      });
      toast.success("BOM이 삭제되었어요");
      setCheckedIds([]);
      setSelectedBomId(null);
      fetchBomList();
    } catch {
      toast.error("BOM 삭제에 실패했어요");
    }
  };

  // ─── BOM 등록/수정 ───────────────────────────
  const openCreateModal = () => {
    setIsEditMode(false);
    setMasterForm({
      item_id: "",
      item_code: "",
      item_name: "",
      bom_type: "",
      base_qty: "1",
      unit: "",
      version: "1.0",
      status: "draft",
      effective_date: "",
      expiry_date: "",
      remark: "",
    });
    setModalDetailRows([]);
    setIsModalOpen(true);
  };

  const openEditModal = () => {
    if (!bomHeader) return;
    setIsEditMode(true);
    setMasterForm({
      id: bomHeader.id,
      item_id: bomHeader.item_id || "",
      item_code: bomHeader.item_code || bomHeader.item_number || "",
      item_name: bomHeader.item_name || "",
      bom_type: bomHeader.bom_type || "",
      base_qty: bomHeader.base_qty || "1",
      unit: bomHeader.unit || "",
      version: bomHeader.version || "1.0",
      status: bomHeader.status || "draft",
      effective_date: bomHeader.effective_date || "",
      expiry_date: bomHeader.expiry_date || "",
      remark: bomHeader.remark || "",
    });

    setModalDetailRows(
      bomDetails.map((d, i) => ({
        _idx: i,
        child_item_id: d.child_item_id || "",
        item_number: d.item_number || "",
        item_name: d.item_name || "",
        quantity: d.quantity || "1",
        unit: d.unit || "",
        process_type: d.process_type || "",
        loss_rate: d.loss_rate || "0",
        remark: d.remark || "",
      }))
    );
    setIsModalOpen(true);
  };

  const handleSaveBom = async () => {
    if (!masterForm.item_id) {
      toast.error("품목을 선택해주세요");
      return;
    }
    if (!masterForm.bom_type) {
      toast.error("BOM 유형을 선택해주세요");
      return;
    }

    setSaving(true);
    try {
      // DB 실제 컬럼: item_type / expired_date (프론트 내부 bom_type/expiry_date와 다름)
      const bomFields: Record<string, any> = {
        item_id: masterForm.item_id,
        item_code: masterForm.item_code,
        item_name: masterForm.item_name,
        item_type: masterForm.bom_type,
        base_qty: masterForm.base_qty || "1",
        unit: masterForm.unit || "",
        version: masterForm.version || "1.0",
        status: masterForm.status || "draft",
        effective_date: masterForm.effective_date || null,
        expired_date: masterForm.expiry_date || null,
        remark: masterForm.remark || "",
        writer: user?.userId || "",
        company_code: user?.company_code || "",
      };

      let bomId: string;

      if (isEditMode && masterForm.id) {
        await apiClient.put(`/table-management/tables/${BOM_TABLE}/edit`, {
          originalData: { id: masterForm.id },
          updatedData: bomFields,
        });
        bomId = masterForm.id;
      } else {
        const res = await apiClient.post(`/table-management/tables/${BOM_TABLE}/add`, { id: crypto.randomUUID(), ...bomFields });
        bomId = res.data?.data?.id || res.data?.id;
      }

      // 초기 버전 생성 (신규 BOM)
      if (!isEditMode && bomId) {
        try {
          await apiClient.post(`/bom/${bomId}/initialize-version`, {});
        } catch {}
      }

      // 디테일 저장: 기존 삭제 후 재등록
      if (isEditMode && masterForm.id) {
        const existingDetails = bomDetails.filter((d) => d.bom_id === bomId);
        if (existingDetails.length > 0) {
          await apiClient.delete(`/table-management/tables/${BOM_DETAIL_TABLE}/delete`, {
            data: existingDetails.map((d) => ({ id: d.id })),
          });
        }
      }

      // 현재 버전 ID 가져오기 (initialize-version 후 최신 값)
      let versionId: string | null = null;
      if (bomId) {
        try {
          const verRes = await apiClient.get(`/bom/${bomId}/versions`);
          versionId = verRes.data?.currentVersionId || null;
        } catch {}
      }

      for (let i = 0; i < modalDetailRows.length; i++) {
        const row = modalDetailRows[i];
        if (!row.child_item_id) continue;
        await apiClient.post(`/table-management/tables/${BOM_DETAIL_TABLE}/add`, {
          id: crypto.randomUUID(),
          bom_id: bomId,
          version_id: versionId || null,
          parent_detail_id: null,
          child_item_id: row.child_item_id,
          quantity: row.quantity || "1",
          unit: row.unit || "",
          process_type: row.process_type || "",
          loss_rate: row.loss_rate || "0",
          remark: row.remark || "",
          level: "0",
          seq_no: String(i + 1),
          writer: user?.userId || "",
          company_code: user?.company_code || "",
        });
      }

      toast.success(isEditMode ? "BOM이 수정되었어요" : "BOM이 등록되었어요");
      setIsModalOpen(false);
      fetchBomList();
      if (bomId) {
        setSelectedBomId(bomId);
        fetchBomDetail(bomId);
        fetchVersions(bomId);
      }
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "BOM 저장에 실패했어요");
    } finally {
      setSaving(false);
    }
  };

  // ─── 품목 검색 ───────────────────────────────
  const searchItems = async (keyword?: string) => {
    const kw = (keyword ?? itemSearchKeyword).trim();
    setItemSearchLoading(true);
    try {
      const filters: any[] = [];
      if (kw) {
        filters.push({ columnName: "item_name", operator: "contains", value: kw });
      }
      const res = await apiClient.post(`/table-management/tables/item_info/data`, {
        page: 1,
        size: 50,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      let rows = res.data?.data?.data || res.data?.data?.rows || [];

      // 키워드가 있고 품명으로 못 찾으면 품목코드로 재시도
      if (kw && rows.length === 0) {
        const res2 = await apiClient.post(`/table-management/tables/item_info/data`, {
          page: 1, size: 50,
          dataFilter: { enabled: true, filters: [{ columnName: "item_number", operator: "contains", value: kw }] },
          autoFilter: true,
        });
        rows = res2.data?.data?.data || res2.data?.data?.rows || [];
      }
      // 카테고리 코드 → 라벨 변환 (division + inventory_unit)
      const resolved = rows.map((r: any) => {
        const out = { ...r };
        if (out.division) {
          out.division = out.division.split(",").map((c: string) => {
            const t = c.trim();
            return categoryOptions["division"]?.find((o) => o.code === t)?.label || t;
          }).filter((v: string) => v && v !== "s").join(", ");
        }
        if (out.inventory_unit) {
          out.inventory_unit = categoryOptions["inventory_unit"]?.find((o) => o.code === out.inventory_unit)?.label || out.inventory_unit;
        }
        return out;
      });
      setItemSearchResults(resolved);
    } catch {
      toast.error("품목 검색에 실패했어요");
    } finally {
      setItemSearchLoading(false);
    }
  };

  const resolveUnit = (code: string) => {
    if (!code) return "";
    return categoryOptions["inventory_unit"]?.find((o) => o.code === code)?.label || code;
  };

  const selectItem = (item: any) => {
    const unitLabel = resolveUnit(item.inventory_unit);
    if (itemSearchTarget === "master") {
      setMasterForm((prev) => ({
        ...prev,
        item_id: item.id,
        item_code: item.item_number || "",
        item_name: item.item_name || "",
        unit: unitLabel || prev.unit,
      }));
      setShowItemSearchModal(false);
    } else {
      setModalDetailRows((prev) => [
        ...prev,
        {
          _idx: prev.length,
          child_item_id: item.id,
          item_number: item.item_number || "",
          item_name: item.item_name || "",
          quantity: "1",
          unit: unitLabel,
          process_type: "",
          loss_rate: "0",
          remark: "",
        },
      ]);
      setShowItemSearchModal(false);
    }
    setItemSearchKeyword("");
    setItemSearchResults([]);
  };

  // ─── 버전 관리 ───────────────────────────────
  const handleCreateVersion = async () => {
    if (!selectedBomId) return;
    try {
      await apiClient.post(`/bom/${selectedBomId}/versions`, {
        versionName: newVersionName.trim() || undefined,
      });
      apiClient.post(`/bom/${selectedBomId}/history`, { change_type: "버전변경", change_description: `새 버전 생성: ${newVersionName.trim() || "자동"}` }).catch(() => {});
      toast.success("새 버전이 생성되었어요");
      setShowNewVersionDialog(false);
      setNewVersionName("");
      fetchVersions(selectedBomId);
      fetchBomDetail(selectedBomId);
      fetchBomList();
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "버전 생성에 실패했어요");
    }
  };

  const handleLoadVersion = async (versionId: string) => {
    if (!selectedBomId) return;
    try {
      await apiClient.post(`/bom/${selectedBomId}/versions/${versionId}/load`);
      toast.success("버전을 불러왔어요");
      fetchVersions(selectedBomId);
      fetchBomDetail(selectedBomId);
      fetchBomList();
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "버전 불러오기에 실패했어요");
    }
  };

  const handleActivateVersion = async (versionId: string) => {
    if (!selectedBomId) return;
    const ok = await confirm("이 버전을 사용 확정할까요? 기존 활성 버전은 비활성 처리돼요.");
    if (!ok) return;
    try {
      await apiClient.post(`/bom/${selectedBomId}/versions/${versionId}/activate`);
      apiClient.post(`/bom/${selectedBomId}/history`, { change_type: "버전변경", change_description: `버전 사용 확정` }).catch(() => {});
      toast.success("버전이 사용 확정되었어요");
      fetchVersions(selectedBomId);
      fetchBomDetail(selectedBomId);
      fetchBomList();
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "버전 확정에 실패했어요");
    }
  };

  const handleDeleteVersion = async (versionId: string) => {
    if (!selectedBomId) return;
    const ok = await confirm("이 버전을 삭제할까요? 관련 디테일 데이터도 함께 삭제돼요.");
    if (!ok) return;
    try {
      await apiClient.delete(`/bom/${selectedBomId}/versions/${versionId}`);
      toast.success("버전이 삭제되었어요");
      fetchVersions(selectedBomId);
      fetchBomDetail(selectedBomId);
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "버전 삭제에 실패했어요");
    }
  };

  // ─── 이력 관리 ───────────────────────────────
  const handleAddHistory = async () => {
    if (!selectedBomId) return;
    if (!newHistory.change_type) {
      toast.error("변경 유형을 입력해주세요");
      return;
    }
    try {
      await apiClient.post(`/bom/${selectedBomId}/history`, {
        change_type: newHistory.change_type,
        change_description: newHistory.change_description,
        changed_by: user?.userId || "",
      });
      toast.success("이력이 추가되었어요");
      setShowHistoryDialog(false);
      setNewHistory({ change_type: "", change_description: "" });
      fetchHistory(selectedBomId);
    } catch (err: any) {
      toast.error("이력 추가에 실패했어요");
    }
  };

  // ─── 엑셀 업로드 ────────────────────────────
  const handleExcelUpload = async () => {
    if (!excelFile) {
      toast.error("엑셀 파일을 선택해주세요");
      return;
    }
    setExcelUploading(true);
    try {
      const formData = new FormData();
      formData.append("file", excelFile);

      const endpoint = selectedBomId
        ? `/bom/${selectedBomId}/excel-upload-version`
        : `/bom/excel-upload`;

      const res = await apiClient.post(endpoint, formData, {
        headers: { "Content-Type": "multipart/form-data" },
      });

      const result = res.data?.data || res.data;
      if (result?.success) {
        toast.success(`엑셀 업로드 완료 (${result.insertedCount}건 등록)`);
        setShowExcelUpload(false);
        setExcelFile(null);
        fetchBomList();
        if (result.createdBomId) {
          setSelectedBomId(result.createdBomId);
        }
      } else {
        toast.error(result?.errors?.join("\n") || "엑셀 업로드에 실패했어요");
      }
    } catch (err: any) {
      toast.error(err?.response?.data?.message || "엑셀 업로드에 실패했어요");
    } finally {
      setExcelUploading(false);
    }
  };

  // 엑셀 다운로드
  const handleExcelDownload = async () => {
    if (!selectedBomId) {
      toast.error("BOM을 선택해주세요");
      return;
    }
    try {
      const res = await apiClient.get(`/bom/${selectedBomId}/excel-download`);
      const data = res.data?.data || [];
      if (data.length === 0) {
        toast.error("다운로드할 데이터가 없어요");
        return;
      }
      exportToExcel(data, `BOM_${bomHeader?.item_code || selectedBomId}`);
      toast.success("엑셀 다운로드를 시작했어요");
    } catch {
      toast.error("엑셀 다운로드에 실패했어요");
    }
  };

  // ─── 상태 뱃지 렌더 ───────────────────────────
  const renderStatusBadge = (status?: string) => {
    const raw = status || "";
    const categoryLabel = categoryOptions["status"]?.find((o) => o.code === raw)?.label;
    const displayLabel = categoryLabel || raw || "-";
    const info = STATUS_MAP[raw] || STATUS_MAP[categoryLabel || ""] || { label: displayLabel, className: "bg-muted text-muted-foreground" };
    return (
      <span className={cn("inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium", info.className)}>
        <span className="w-1.5 h-1.5 rounded-full bg-current" />
        {info.label}
      </span>
    );
  };

  // ─── 렌더링 ──────────────────────────────────
  return (
    <div className="flex flex-col h-full">
      {/* 브레드크럼 */}
      <nav className="flex items-center gap-1.5 px-5 py-3 text-xs text-muted-foreground bg-muted/50 border-b shrink-0">
        <span>생산관리</span>
        <ChevronRight className="w-3.5 h-3.5 text-muted-foreground/50" />
        <span className="font-semibold text-foreground">BOM 관리</span>
      </nav>

      {/* 검색 필터 */}
      <div className="px-4 py-2.5 border-b bg-muted/50 shrink-0">
        <DynamicSearchFilter
          tableName={BOM_TABLE}
          filterId="c16-bom"
          onFilterChange={setSearchFilters}
          externalFilterConfig={ts.filterConfig}
          dataCount={totalCount}
          extraActions={
            <div className="flex items-center gap-2">
              <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => setShowExcelUpload(true)}>
                <Upload className="w-3.5 h-3.5 mr-1.5" />
                엑셀 업로드
              </Button>
              <Button size="sm" variant="outline" className="h-8 text-xs" onClick={handleExcelDownload} disabled={!selectedBomId}>
                <Download className="w-3.5 h-3.5 mr-1.5" />
                엑셀 다운로드
              </Button>
            </div>
          }
        />
      </div>

      {/* 메인 콘텐츠: 좌우 분할 */}
      <div className="flex-1 overflow-hidden rounded-lg border bg-card mx-3 mb-3">
        <ResizablePanelGroup direction="horizontal">
          {/* 좌측: BOM 목록 */}
          <ResizablePanel defaultSize={45} minSize={25}>
            <div className="flex flex-col h-full">
              {/* 패널 헤더 */}
              <div className="flex items-center justify-between px-4 py-2.5 border-b bg-muted/50 shrink-0">
                <div className="flex items-center gap-2">
                  <h3 className="text-[13px] font-bold text-foreground">BOM 목록</h3>
                  <span className="text-[11px] font-semibold text-primary bg-primary/10 border border-primary/15 px-2 py-0.5 rounded-full font-mono">
                    {totalCount}건
                  </span>
                  {loading && <Loader2 className="w-3.5 h-3.5 animate-spin text-muted-foreground" />}
                </div>
                <div className="flex items-center gap-1.5">
                  <Button size="sm" onClick={openCreateModal}>
                    <Plus className="w-3.5 h-3.5 mr-1" />
                    등록
                  </Button>
                  <Button
                    size="sm"
                    variant="outline"
                    onClick={() => {
                      if (!selectedBomId || !bomHeader) {
                        toast.error("수정할 BOM을 선택해주세요");
                        return;
                      }
                      openEditModal();
                    }}
                    disabled={!selectedBomId || !bomHeader}
                  >
                    <Pencil className="w-3.5 h-3.5 mr-1" />
                    수정
                  </Button>
                  <div className="w-px h-4 bg-border mx-0.5" />
                  <Button
                    size="sm"
                    variant="outline"
                    className="text-destructive border-destructive/20 bg-destructive/5 hover:bg-destructive/10"
                    onClick={handleDeleteBom}
                    disabled={checkedIds.length === 0}
                  >
                    <Trash2 className="w-3.5 h-3.5 mr-1" />
                    삭제
                  </Button>
                  <Button variant="ghost" size="sm" onClick={() => ts.setOpen(true)} title="테이블 설정">
                    <Settings2 className="w-3.5 h-3.5" />
                  </Button>
                </div>
              </div>

              {/* BOM 목록 테이블 */}
              <div className="flex-1 overflow-auto">
                <EDataTable
                  columns={ts.visibleColumns.map((col): EDataTableColumn => ({
                    key: col.key,
                    label: col.label,
                    render: col.key === "item_code"
                      ? (_val: any, row: any) => <span className="font-mono text-[13px]">{row.item_code || row.item_number || "-"}</span>
                      : col.key === "bom_type"
                      ? (_val: any, row: any) => <span className="text-[13px]">{BOM_TYPE_OPTIONS.find((o) => o.code === row.bom_type)?.label || row.bom_type || "-"}</span>
                      : col.key === "status"
                      ? (_val: any, row: any) => renderStatusBadge(row.status)
                      : undefined,
                  }))}
                  data={ts.groupData(bomList)}
                  loading={loading}
                  emptyMessage="등록된 BOM이 없어요"
                  showCheckbox
                  checkedIds={checkedIds}
                  onCheckedChange={setCheckedIds}
                  selectedId={selectedBomId}
                  onSelect={(id) => setSelectedBomId(id)}
                  onRowClick={(row) => setSelectedBomId(row.id)}
                  showPagination
                  draggableColumns={false}
                  columnOrderKey="c16-bom"
                />
              </div>
            </div>
          </ResizablePanel>

          <ResizableHandle withHandle />

          {/* 우측: 상세 + 트리뷰 */}
          <ResizablePanel defaultSize={55} minSize={30}>
            {!selectedBomId ? (
              <div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-2">
                <Inbox className="w-10 h-10 text-muted-foreground/40" />
                <p className="text-sm">좌측에서 BOM을 선택해주세요</p>
              </div>
            ) : (
              <div className="flex flex-col h-full">
                {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
                <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
                  <Tabs value={rightTab} onValueChange={(v) => {
                    setRightTab(v as any);
                    if (v === "version" && selectedBomId) fetchVersions(selectedBomId);
                    if (v === "history" && selectedBomId) fetchHistory(selectedBomId);
                  }}>
                    <div className="flex items-center justify-between px-4 py-2 border-b bg-muted/50 shrink-0">
                      <TabsList className="h-8">
                        <TabsTrigger value="tree" className="text-xs px-3 h-7">
                          <GitBranch className="w-3.5 h-3.5 mr-1" />
                          트리뷰
                        </TabsTrigger>
                        <TabsTrigger value="version" className="text-xs px-3 h-7">
                          <Copy className="w-3.5 h-3.5 mr-1" />
                          버전
                        </TabsTrigger>
                        <TabsTrigger value="history" className="text-xs px-3 h-7">
                          <History className="w-3.5 h-3.5 mr-1" />
                          이력
                        </TabsTrigger>
                      </TabsList>

                    </div>

                    {/* 트리뷰 탭 */}
                    <TabsContent value="tree" className="flex-1 overflow-auto p-0 m-0">
                      {/* 상단 액션바 */}
                      <div className="flex items-center justify-between border-b px-3 py-2 bg-muted/30">
                        <div className="flex items-center gap-2">
                          <Button size="sm" variant="outline" onClick={() => handleTreeAddChild(null)}>
                            <Plus className="w-3.5 h-3.5 mr-1" />루트 품목 추가
                          </Button>
                          <Button
                            size="sm"
                            variant="outline"
                            onClick={() => {
                              if (selectedTreeNodeId && !selectedTreeNodeId.startsWith("__root_")) {
                                handleTreeAddChild(selectedTreeNodeId);
                              }
                            }}
                            disabled={!selectedTreeNodeId || selectedTreeNodeId.startsWith("__root_")}
                          >
                            <Plus className="w-3.5 h-3.5 mr-1" />하위 품목 추가
                          </Button>
                          <Button
                            size="sm"
                            variant="outline"
                            className="text-destructive border-destructive/20 bg-destructive/5 hover:bg-destructive/10"
                            onClick={() => {
                              if (selectedTreeNodeId && !selectedTreeNodeId.startsWith("__root_")) {
                                handleTreeDeleteNode(selectedTreeNodeId);
                                setSelectedTreeNodeId(null);
                                setEditingNodeId(null);
                              }
                            }}
                            disabled={!selectedTreeNodeId || selectedTreeNodeId.startsWith("__root_")}
                          >
                            <Trash2 className="w-3.5 h-3.5 mr-1" />선택 삭제
                          </Button>
                          <div className="w-px h-5 bg-border" />
                          <div className="flex overflow-hidden rounded-md border">
                            <button onClick={() => { setTreeViewMode("forward"); }} className={cn("h-7 px-2.5 text-[11px] font-medium transition-colors", treeViewMode === "forward" ? "bg-primary text-primary-foreground" : "bg-background text-muted-foreground hover:bg-muted")}>
                              정전개
                            </button>
                            <button onClick={() => { setTreeViewMode("reverse"); fetchWhereUsed(); }} className={cn("h-7 border-l px-2.5 text-[11px] font-medium transition-colors", treeViewMode === "reverse" ? "bg-primary text-primary-foreground" : "bg-background text-muted-foreground hover:bg-muted")}>
                              역전개
                            </button>
                          </div>
                          {treeViewMode === "forward" && (
                            <>
                              <Button size="sm" variant="ghost" onClick={expandAll}>
                                <ChevronsDown className="w-3.5 h-3.5 mr-1" />펼침
                              </Button>
                              <Button size="sm" variant="ghost" onClick={collapseAll}>
                                <ChevronsUp className="w-3.5 h-3.5 mr-1" />접음
                              </Button>
                            </>
                          )}
                        </div>
                        <div className="flex items-center gap-2">
                          {treeHasChanges && <Badge variant="secondary" className="text-warning">변경됨</Badge>}
                          <Button size="sm" onClick={handleSaveTree} disabled={!treeHasChanges || saving}>
                            {saving ? <Loader2 className="w-3.5 h-3.5 animate-spin mr-1" /> : <Save className="w-3.5 h-3.5 mr-1" />}
                            저장
                          </Button>
                        </div>
                      </div>

                      {/* 역전개 (Where Used) 뷰 */}
                      {treeViewMode === "reverse" ? (
                        <div className="flex-1 overflow-auto">
                          {whereUsedLoading ? (
                            <div className="flex h-40 items-center justify-center">
                              <Loader2 className="h-5 w-5 animate-spin text-primary" />
                            </div>
                          ) : (
                            <div className="p-4">
                              <div className="flex items-center gap-2 mb-4">
                                <Package className="h-4 w-4 text-muted-foreground" />
                                <span className="text-sm font-semibold">{bomHeader?.item_name || ""}</span>
                                <span className="font-mono text-xs text-muted-foreground">({bomHeader?.item_code || ""})</span>
                              </div>
                              <div className="text-xs text-muted-foreground mb-3">사용처 (Where Used)</div>
                              {whereUsedData.length === 0 ? (
                                <div className="flex flex-col items-center justify-center h-32 text-muted-foreground gap-2">
                                  <Inbox className="w-8 h-8 text-muted-foreground/40" />
                                  <p className="text-xs">이 품목을 사용하는 BOM이 없습니다</p>
                                </div>
                              ) : (
                                <div className="space-y-2">
                                  {whereUsedData.map((wu, idx) => (
                                    <div key={idx} className="flex items-center gap-3 rounded-lg border p-3 hover:bg-accent/50 cursor-pointer" onClick={() => {
                                      // 해당 BOM 선택
                                      setSelectedBomId(wu.bom_id);
                                      setTreeViewMode("forward");
                                    }}>
                                      <Package className="h-4 w-4 text-primary shrink-0" />
                                      <div className="min-w-0 flex-1">
                                        <div className="flex items-center gap-2">
                                          <span className="text-sm font-semibold">{wu.item_name}</span>
                                          <span className="font-mono text-[11px] text-muted-foreground">({wu.item_code})</span>
                                        </div>
                                        <div className="mt-0.5 flex gap-3 text-[11px] text-muted-foreground">
                                          <span>소요량: <b className="text-foreground">{wu.quantity}</b> {wu.unit}</span>
                                          {wu.process_type && <span>공정: <b className="text-foreground">{wu.process_type}</b></span>}
                                        </div>
                                      </div>
                                      <ChevronRight className="h-4 w-4 text-muted-foreground/50 shrink-0" />
                                    </div>
                                  ))}
                                </div>
                              )}
                            </div>
                          )}
                        </div>
                      ) : editingFlatTree.length === 0 ? (
                        <div className="flex flex-col items-center justify-center h-40 text-muted-foreground gap-2">
                          <Inbox className="w-8 h-8 text-muted-foreground/40" />
                          <p className="text-xs">등록된 하위 품목이 없습니다</p>
                          <Button size="sm" variant="outline" onClick={() => handleTreeAddChild(null)}>
                            <Plus className="w-3.5 h-3.5 mr-1" />첫 품목 추가
                          </Button>
                        </div>
                      ) : (
                        <table className="w-full border-collapse text-xs" style={{ minWidth: "1100px" }}>
                          <colgroup><col style={{ width: "60px" }} /><col style={{ width: "140px" }} /><col /><col style={{ width: "70px" }} /><col style={{ width: "50px" }} /><col style={{ width: "80px" }} /><col style={{ width: "80px" }} /><col style={{ width: "80px" }} /><col style={{ width: "80px" }} /><col style={{ width: "150px" }} /><col style={{ width: "80px" }} /></colgroup>
                          <thead className="sticky top-0 z-10">
                            <tr className="border-b bg-muted">
                              <th className="px-2 py-2.5 text-[11px] font-semibold text-muted-foreground text-center">레벨</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">품번</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">품명</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-center">소요량</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-center">단위</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">공정구분</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">규격</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">비고</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">작성자</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-left">수정일시</th>
                              <th className="px-3 py-2.5 text-[11px] font-semibold text-muted-foreground text-center">관리품목</th>
                            </tr>
                          </thead>
                          <tbody>
                            {editingFlatTree.map((node) => {
                              const nodeKey = node.id || node._tempId || "";
                              const isVirtualRoot = (node as any)._isVirtualRoot === true;
                              const typeBadge = getItemTypeBadge(node.item_type);
                              const hasChildren = isVirtualRoot ? editingTree.length > 0 : node.children.length > 0;
                              const isExpanded = expandedNodes.has(nodeKey);
                              const isSelected = selectedTreeNodeId === nodeKey;
                              const isEditing = editingNodeId === nodeKey && !isVirtualRoot;
                              const level = node._level || 0;

                              const depthBarColor = isVirtualRoot ? "bg-primary/70"
                                : level === 1 ? "bg-emerald-400"
                                : level === 2 ? "bg-amber-400"
                                : level >= 3 ? "bg-purple-400" : "bg-muted/60";

                              const depthBg = isVirtualRoot
                                ? "border-border bg-primary/10 font-medium hover:bg-primary/20"
                                : isSelected
                                  ? "border-border bg-primary/5"
                                  : level === 1 ? "border-border bg-background hover:bg-muted/60"
                                  : level >= 2 ? "border-border bg-muted/40 hover:bg-muted/50"
                                  : "border-border bg-background hover:bg-muted/60";

                              return (
                                <tr
                                  key={nodeKey}
                                  className={cn("group cursor-pointer border-b transition-colors", depthBg)}
                                  onClick={() => {
                                    if (editingNodeId && editingNodeId !== nodeKey) setEditingNodeId(null);
                                    setSelectedTreeNodeId(nodeKey);
                                    if (hasChildren) toggleEditTreeNode(nodeKey);
                                  }}
                                  onDoubleClick={() => {
                                    if (!isVirtualRoot) {
                                      setTreeEditTarget(node);
                                      setTreeEditForm({ quantity: node.quantity || "1", unit: node.unit || "", process_type: node.process_type || "", loss_rate: node.loss_rate || "0", spec: node.spec || "", material: node.material || "", remark: node.remark || "" });
                                      setTreeEditModalOpen(true);
                                    }
                                  }}
                                >
                                  {/* 레벨 + 좌측 색상바 + 토글 */}
                                  <td className="relative px-1 py-2" style={{ paddingLeft: `${level * 16 + 8}px` }}>
                                    <div className={cn("absolute left-0 top-0 h-full w-[3px]", depthBarColor)} />
                                    <div className="flex items-center gap-1">
                                      <span className="flex h-4 w-4 flex-shrink-0 items-center justify-center">
                                        {hasChildren ? (
                                          isExpanded ? <ChevronDown className={cn("h-3.5 w-3.5", isVirtualRoot ? "text-primary" : "text-muted-foreground/70")} />
                                          : <ChevronRight className={cn("h-3.5 w-3.5", isVirtualRoot ? "text-primary" : "text-muted-foreground/70")} />
                                        ) : (
                                          <span className="h-1 w-1 rounded-full bg-muted-foreground/40" />
                                        )}
                                      </span>
                                      <span className="text-[11px]">{level}</span>
                                    </div>
                                  </td>
                                  {/* 품번 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2">
                                    <span className={cn("font-mono text-[11px]", isVirtualRoot && "font-bold text-foreground")}>
                                      {node.item_number || "-"}
                                    </span>
                                  </td>
                                  {/* 품명 */}
                                  <td className={cn("overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2", isVirtualRoot && "font-bold")}>
                                    {node.item_name || "-"}
                                  </td>
                                  {/* 소요량 */}
                                  <td className="px-3 py-2 text-center">{isVirtualRoot ? (bomHeader?.base_qty || "1") : (node.quantity || "-")}</td>
                                  {/* 단위 */}
                                  <td className="px-3 py-2 text-center">{isVirtualRoot ? (categoryOptions["inventory_unit"]?.find((o) => o.code === bomHeader?.unit)?.label || bomHeader?.unit || "-") : (node.unit || "-")}</td>
                                  {/* 공정구분 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2">{isVirtualRoot ? "-" : (node.process_type || "-")}</td>
                                  {/* 규격 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2">{isVirtualRoot ? "-" : (node.spec || "-")}</td>
                                  {/* 비고 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2">{isVirtualRoot ? "-" : (node.remark || "-")}</td>
                                  {/* 작성자 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2 text-muted-foreground">{isVirtualRoot ? "-" : (userMap[node.writer] || node.writer || "-")}</td>
                                  {/* 수정일시 */}
                                  <td className="overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2 text-muted-foreground">
                                    {isVirtualRoot ? "-" : (node.updated_date ? new Date(node.updated_date).toLocaleString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }) : "-")}
                                  </td>
                                  {/* 관리품목 */}
                                  <td className="px-2 py-2" style={{ minWidth: "150px" }}>
                                    {isVirtualRoot ? <span className="text-center block">-</span> : (
                                      node.item_type ? (
                                        <div className="flex flex-row items-center gap-1 flex-nowrap">{node.item_type.split(",").map((t: string, i: number) => {
                                          const trimmed = t.trim();
                                          if (!trimmed) return null;
                                          return <span key={i} className="shrink-0 rounded px-1 py-0.5 text-[9px] font-medium bg-muted text-muted-foreground">{trimmed}</span>;
                                        })}</div>
                                      ) : "-"
                                    )}
                                  </td>
                                </tr>
                              );
                            })}
                          </tbody>
                        </table>
                      )}
                    </TabsContent>

                    {/* 버전 탭 */}
                    <TabsContent value="version" className="flex-1 overflow-auto m-0">
                      <div className="p-3">
                        <div className="flex items-center justify-between mb-3">
                          <span className="text-xs text-muted-foreground">
                            {versions.length}개 버전
                          </span>
                          <Button size="sm" onClick={() => setShowNewVersionDialog(true)}>
                            <Plus className="w-3.5 h-3.5 mr-1" />
                            새 버전
                          </Button>
                        </div>
                        {versionLoading ? (
                          <div className="flex items-center justify-center py-8">
                            <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />
                          </div>
                        ) : versions.length === 0 ? (
                          <div className="flex flex-col items-center justify-center py-12 text-muted-foreground gap-2">
                            <Inbox className="w-8 h-8 text-muted-foreground/40" />
                            <p className="text-xs">버전 데이터가 없어요</p>
                          </div>
                        ) : (
                          <div className="space-y-2">
                            {versions.map((ver) => {
                              const isActive = ver.id === currentVersionId;
                              const statusInfo = VERSION_STATUS_MAP[ver.status] || { label: ver.status, className: "bg-muted text-muted-foreground" };
                              return (
                                <div
                                  key={ver.id}
                                  className={cn(
                                    "flex items-center justify-between p-3 rounded-lg border transition-colors",
                                    isActive ? "border-primary/30 bg-primary/5" : "hover:bg-muted/50"
                                  )}
                                >
                                  <div className="flex items-center gap-3">
                                    <div className="flex flex-col gap-0.5">
                                      <div className="flex items-center gap-2">
                                        <span className="text-sm font-semibold">{ver.version_name}</span>
                                        <span className={cn("text-[10px] font-medium px-1.5 py-0.5 rounded-full", statusInfo.className)}>
                                          {statusInfo.label}
                                        </span>
                                        {isActive && (
                                          <Badge variant="outline" className="text-[10px] h-4 border-primary/30 text-primary">
                                            현재
                                          </Badge>
                                        )}
                                      </div>
                                      <span className="text-[11px] text-muted-foreground">
                                        {ver.detail_count || 0}개 구성품 · {ver.created_date ? new Date(ver.created_date).toLocaleDateString("ko-KR") : "-"}
                                      </span>
                                    </div>
                                  </div>
                                  <div className="flex items-center gap-1">
                                    {!isActive && (
                                      <Button size="sm" variant="ghost" className="h-7 text-xs" onClick={() => handleLoadVersion(ver.id)}>
                                        <RotateCcw className="w-3 h-3 mr-1" />
                                        불러오기
                                      </Button>
                                    )}
                                    {ver.status !== "active" && (
                                      <Button size="sm" variant="ghost" className="h-7 text-xs" onClick={() => handleActivateVersion(ver.id)}>
                                        <CheckCircle2 className="w-3 h-3 mr-1" />
                                        확정
                                      </Button>
                                    )}
                                    {ver.status !== "active" && (
                                      <Button
                                        size="sm"
                                        variant="ghost"
                                        className="h-7 text-xs text-destructive hover:text-destructive"
                                        onClick={() => handleDeleteVersion(ver.id)}
                                      >
                                        <Trash2 className="w-3 h-3" />
                                      </Button>
                                    )}
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    </TabsContent>

                    {/* 이력 탭 */}
                    <TabsContent value="history" className="flex-1 overflow-auto m-0">
                      <div className="p-3">
                        <div className="flex items-center justify-between mb-3">
                          <span className="text-xs text-muted-foreground">
                            {historyList.length}건 이력
                          </span>
                        </div>
                        {historyLoading ? (
                          <div className="flex items-center justify-center py-8">
                            <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />
                          </div>
                        ) : historyList.length === 0 ? (
                          <div className="flex flex-col items-center justify-center py-12 text-muted-foreground gap-2">
                            <Inbox className="w-8 h-8 text-muted-foreground/40" />
                            <p className="text-xs">이력 데이터가 없어요</p>
                          </div>
                        ) : (
                          <div className="space-y-2">
                            {historyList.map((entry) => {
                              const typeStyle = CHANGE_TYPE_STYLE[entry.change_type] || "bg-muted text-muted-foreground ring-border";
                              return (
                                <div key={entry.id} className="p-3 rounded-lg border hover:bg-muted/50 transition-colors">
                                  <div className="flex items-center justify-between mb-1">
                                    <div className="flex items-center gap-2">
                                      <span className={cn("inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset", typeStyle)}>
                                        {entry.change_type}
                                      </span>
                                      {(entry.revision || entry.version) && (
                                        <span className="text-[10px] text-muted-foreground">
                                          {entry.revision && `차수 ${entry.revision}`}{entry.revision && entry.version && " / "}{entry.version && `v${entry.version}`}
                                        </span>
                                      )}
                                    </div>
                                    <span className="text-[10px] text-muted-foreground">
                                      {entry.changed_date ? new Date(entry.changed_date).toLocaleString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }) : "-"}
                                    </span>
                                  </div>
                                  <p className="text-xs mt-1">{entry.change_description || "-"}</p>
                                  {entry.changed_by && (
                                    <span className="text-[10px] text-muted-foreground/60 mt-1 block">
                                      {entry.changed_by}
                                    </span>
                                  )}
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    </TabsContent>
                  </Tabs>
                </div>
              </div>
            )}
          </ResizablePanel>
        </ResizablePanelGroup>
      </div>

      {/* ─── BOM 등록/수정 모달 ─────────────────── */}
      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
        <DialogContent className="max-w-[95vw] sm:max-w-[1100px] max-h-[85vh] overflow-y-auto" onPointerDownOutside={(e) => { if (showItemSearchModal) e.preventDefault(); }}>
          <DialogHeader>
            <DialogTitle>{isEditMode ? "BOM 수정" : "BOM 등록"}</DialogTitle>
            <DialogDescription>{isEditMode ? "BOM 정보를 수정해요" : "새로운 BOM을 등록해요"}</DialogDescription>
          </DialogHeader>
        <div className="space-y-6">
          {/* 기본정보 */}
          <div className="grid grid-cols-2 gap-4">
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">
                품목 <span className="text-destructive">*</span>
              </Label>
              <div className="flex gap-2">
                <Input
                  className="h-9 flex-1"
                  value={masterForm.item_code ? `${masterForm.item_code} - ${masterForm.item_name}` : ""}
                  placeholder="품목을 검색해주세요"
                  readOnly
                  onClick={() => {
                    setItemSearchTarget("master");
                    setItemSearchKeyword("");
                    setShowItemSearchModal(true);
                    searchItems("");
                  }}
                />
                <Button
                  size="sm"
                  variant="outline"
                  className="h-9 px-3"
                  onClick={() => {
                    setItemSearchTarget("master");
                    setItemSearchKeyword("");
                    setShowItemSearchModal(true);
                    searchItems("");
                  }}
                >
                  <Search className="w-3.5 h-3.5" />
                </Button>
              </div>
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">
                BOM 유형 <span className="text-destructive">*</span>
              </Label>
              <Select value={masterForm.bom_type || ""} onValueChange={(v) => setMasterForm((prev) => ({ ...prev, bom_type: v }))}>
                <SelectTrigger className="h-9"><SelectValue placeholder="선택" /></SelectTrigger>
                <SelectContent>
                  {BOM_TYPE_OPTIONS.map((o) => (
                    <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">기준수량</Label>
              <Input
                className="h-9"
                type="number"
                value={masterForm.base_qty}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, base_qty: e.target.value }))}
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">단위</Label>
              <Input
                className="h-9"
                value={masterForm.unit}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, unit: e.target.value }))}
                placeholder="EA"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">
                버전 <span className="text-destructive">*</span>
              </Label>
              <Input
                className="h-9"
                value={masterForm.version}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, version: e.target.value }))}
                placeholder="1.0"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">상태</Label>
              <Select value={masterForm.status || "draft"} onValueChange={(v) => setMasterForm((prev) => ({ ...prev, status: v }))}>
                <SelectTrigger className="h-9"><SelectValue placeholder="선택" /></SelectTrigger>
                <SelectContent>
                  <SelectItem value="draft">초안</SelectItem>
                  <SelectItem value="active">활성</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">
                유효 시작일 <span className="text-destructive">*</span>
              </Label>
              <Input
                type="date"
                className="h-9"
                value={masterForm.effective_date || ""}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, effective_date: e.target.value }))}
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">유효 종료일</Label>
              <Input
                type="date"
                className="h-9"
                value={masterForm.expiry_date || ""}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, expiry_date: e.target.value }))}
              />
            </div>
            <div className="flex flex-col gap-1.5 col-span-2">
              <Label className="text-xs font-medium">메모</Label>
              <Textarea
                className="min-h-[60px] resize-none"
                value={masterForm.remark}
                onChange={(e) => setMasterForm((prev) => ({ ...prev, remark: e.target.value }))}
                placeholder="메모를 입력해주세요"
              />
            </div>
          </div>

          {/* BOM 구성품목 */}
          <div>
            <div className="flex items-center justify-between mb-2">
              <h4 className="text-sm font-semibold">BOM 구성품목</h4>
              <Button
                size="sm"
                variant="outline"
                onClick={() => {
                  setItemSearchTarget("detail");
                  setItemSearchKeyword("");
                  setShowItemSearchModal(true);
                  searchItems("");
                }}
              >
                <Plus className="w-3.5 h-3.5 mr-1" />
                품목 추가
              </Button>
            </div>
            <div className="border rounded-lg overflow-hidden">
              <Table>
                <TableHeader className="sticky top-0 z-10">
                  <TableRow className="bg-muted hover:bg-muted">
                    <TableHead className="w-[40px] text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">#</TableHead>
                    <TableHead className="w-[120px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품목코드</TableHead>
                    <TableHead className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">품명</TableHead>
                    <TableHead className="w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">수량</TableHead>
                    <TableHead className="w-[70px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">단위</TableHead>
                    <TableHead className="w-[100px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">공정유형</TableHead>
                    <TableHead className="w-[80px] text-[11px] font-bold uppercase tracking-wide text-muted-foreground">손실율(%)</TableHead>
                    <TableHead className="w-[40px]" />
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {modalDetailRows.length === 0 ? (
                    <TableRow>
                      <TableCell colSpan={8} className="text-center py-8 text-muted-foreground text-[13px]">
                        구성품목을 추가해주세요
                      </TableCell>
                    </TableRow>
                  ) : (
                    modalDetailRows.map((row, idx) => (
                      <TableRow key={idx}>
                        <TableCell className="text-center text-muted-foreground text-[13px]">{idx + 1}</TableCell>
                        <TableCell className="font-mono text-[13px]">{row.item_number}</TableCell>
                        <TableCell className="text-[13px]">{row.item_name}</TableCell>
                        <TableCell>
                          <Input
                            className="h-7 w-[70px] text-xs"
                            type="number"
                            value={row.quantity}
                            onChange={(e) => {
                              const newRows = [...modalDetailRows];
                              newRows[idx].quantity = e.target.value;
                              setModalDetailRows(newRows);
                            }}
                          />
                        </TableCell>
                        <TableCell className="text-[13px]">{row.unit}</TableCell>
                        <TableCell>
                          <Input
                            className="h-7 w-[90px] text-xs"
                            value={row.process_type}
                            onChange={(e) => {
                              const newRows = [...modalDetailRows];
                              newRows[idx].process_type = e.target.value;
                              setModalDetailRows(newRows);
                            }}
                            placeholder="공정"
                          />
                        </TableCell>
                        <TableCell>
                          <Input
                            className="h-7 w-[70px] text-xs"
                            type="number"
                            value={row.loss_rate}
                            onChange={(e) => {
                              const newRows = [...modalDetailRows];
                              newRows[idx].loss_rate = e.target.value;
                              setModalDetailRows(newRows);
                            }}
                          />
                        </TableCell>
                        <TableCell>
                          <Button
                            size="sm"
                            variant="ghost"
                            className="h-7 w-7 p-0 text-destructive hover:text-destructive"
                            onClick={() => setModalDetailRows((prev) => prev.filter((_, i) => i !== idx))}
                          >
                            <X className="w-3.5 h-3.5" />
                          </Button>
                        </TableCell>
                      </TableRow>
                    ))
                  )}
                </TableBody>
              </Table>
            </div>
          </div>
        </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setIsModalOpen(false)} disabled={saving}>
              취소
            </Button>
            <Button onClick={handleSaveBom} disabled={saving}>
              {saving && <Loader2 className="w-3.5 h-3.5 animate-spin mr-1.5" />}
              {isEditMode ? "수정" : "등록"}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ─── 품목 검색 모달 ──────────────────────── */}
      <Dialog open={showItemSearchModal} onOpenChange={setShowItemSearchModal}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle>품목 검색</DialogTitle>
            <DialogDescription>품목코드 또는 품명으로 검색해주세요</DialogDescription>
          </DialogHeader>
          <div className="flex gap-2 mb-3">
            <Input
              className="h-9 flex-1"
              placeholder="품목코드 또는 품명 입력"
              value={itemSearchKeyword}
              onChange={(e) => setItemSearchKeyword(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && searchItems()}
            />
            <Button size="sm" className="h-9" onClick={() => searchItems()} disabled={itemSearchLoading}>
              {itemSearchLoading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Search className="w-3.5 h-3.5" />}
            </Button>
          </div>
          <div className="max-h-[300px] overflow-auto border rounded-lg">
            {itemSearchResults.length === 0 ? (
              <div className="flex flex-col items-center justify-center py-8 text-muted-foreground gap-1">
                <Search className="w-6 h-6 text-muted-foreground/40" />
                <p className="text-xs">검색어를 입력해주세요</p>
              </div>
            ) : (
              itemSearchResults.map((item) => {
                const badge = getItemTypeBadge(item.division);
                return (
                  <div
                    key={item.id}
                    className="flex items-center gap-3 px-3 py-2 border-b last:border-b-0 hover:bg-accent cursor-pointer transition-colors"
                    onClick={() => selectItem(item)}
                  >
                    <span className={cn("text-[10px] font-semibold px-1.5 py-0.5 rounded shrink-0", badge.className)}>
                      {badge.label}
                    </span>
                    <span className="font-mono text-[11px] text-muted-foreground">{item.item_number}</span>
                    <span className="text-xs">{item.item_name}</span>
                    <span className="ml-auto text-[11px] text-muted-foreground">{item.inventory_unit || ""}</span>
                  </div>
                );
              })
            )}
          </div>
        </DialogContent>
      </Dialog>

      {/* ─── 품목 수정 모달 (더블클릭) ─────────────────── */}
      <Dialog open={treeEditModalOpen} onOpenChange={setTreeEditModalOpen}>
        <DialogContent className="max-w-md">
          <DialogHeader>
            <DialogTitle>품목 수정</DialogTitle>
            <DialogDescription>선택한 품목의 BOM 구성 정보를 수정합니다</DialogDescription>
          </DialogHeader>
          {treeEditTarget && (
            <div className="space-y-4">
              <div className="grid grid-cols-2 gap-4">
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">품목코드</Label>
                  <Input className="h-9 bg-muted/50" value={treeEditTarget.item_number || ""} readOnly />
                </div>
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">품목명</Label>
                  <Input className="h-9 bg-muted/50" value={treeEditTarget.item_name || ""} readOnly />
                </div>
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">구성수량 <span className="text-destructive">*</span></Label>
                  <Input className="h-9" type="number" value={treeEditForm.quantity || ""} onChange={(e) => setTreeEditForm((p) => ({ ...p, quantity: e.target.value }))} />
                </div>
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">단위</Label>
                  <Input className="h-9 bg-muted/50" value={treeEditForm.unit || "-"} readOnly />
                </div>
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">공정</Label>
                  <Select value={treeEditForm.process_type || ""} onValueChange={(v) => setTreeEditForm((p) => ({ ...p, process_type: v }))}>
                    <SelectTrigger className="h-9"><SelectValue placeholder="공정 선택" /></SelectTrigger>
                    <SelectContent>
                      {processOptions.map((o) => (
                        <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">로스율 (%)</Label>
                  <Input className="h-9" type="number" value={treeEditForm.loss_rate || ""} onChange={(e) => setTreeEditForm((p) => ({ ...p, loss_rate: e.target.value }))} />
                </div>
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">규격</Label>
                  <Input className="h-9 bg-muted/50" value={treeEditForm.spec || "-"} readOnly />
                </div>
                <div className="space-y-1">
                  <Label className="text-xs font-semibold text-muted-foreground">재질</Label>
                  <Input className="h-9 bg-muted/50" value={treeEditForm.material || "-"} readOnly />
                </div>
              </div>
              <div className="space-y-1">
                <Label className="text-xs font-semibold text-muted-foreground">메모</Label>
                <textarea className="flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm" value={treeEditForm.remark || ""} onChange={(e) => setTreeEditForm((p) => ({ ...p, remark: e.target.value }))} placeholder="비고 사항을 입력하세요" />
              </div>
            </div>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setTreeEditModalOpen(false)}>취소</Button>
            <Button onClick={() => {
              if (treeEditTarget) {
                const nodeKey = treeEditTarget.id || treeEditTarget._tempId || "";
                setEditingTree(prev => updateTreeNode(prev, nodeKey, node => ({
                  ...node,
                  quantity: treeEditForm.quantity || "1",
                  process_type: treeEditForm.process_type || "",
                  loss_rate: treeEditForm.loss_rate || "0",
                  remark: treeEditForm.remark || "",
                  _isModified: !node._isNew ? true : node._isModified,
                })));
                setTreeHasChanges(true);
              }
              setTreeEditModalOpen(false);
            }}>저장</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ─── 트리 품목 검색 모달 ─────────────────── */}
      <Dialog open={treeItemSearchOpen} onOpenChange={setTreeItemSearchOpen}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle>품목 검색 (트리 추가)</DialogTitle>
            <DialogDescription>추가할 품목을 검색해주세요</DialogDescription>
          </DialogHeader>
          <div className="flex gap-2 mb-3">
            <Input
              className="h-9 flex-1"
              placeholder="품목코드 또는 품명 입력"
              value={itemSearchKeyword}
              onChange={(e) => setItemSearchKeyword(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && searchItems()}
            />
            <Button size="sm" className="h-9" onClick={() => searchItems()} disabled={itemSearchLoading}>
              {itemSearchLoading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Search className="w-3.5 h-3.5" />}
            </Button>
          </div>
          <div className="max-h-[300px] overflow-auto border rounded-lg">
            {itemSearchResults.length === 0 ? (
              <div className="flex flex-col items-center justify-center py-8 text-muted-foreground gap-1">
                <Search className="w-6 h-6 text-muted-foreground/40" />
                <p className="text-xs">검색어를 입력해주세요</p>
              </div>
            ) : (
              itemSearchResults.filter((item) => {
                // 자기 자신 제외
                if (bomHeader && (item.id === bomHeader.item_id || item.item_number === bomHeader.item_code)) return false;
                // 조상 경로 순환 참조 제외
                if (addTargetParentId) {
                  const collectAncestorIds = (nodes: TreeNode[], targetId: string, path: string[]): string[] | null => {
                    for (const n of nodes) {
                      const nk = n.id || n._tempId || "";
                      if (nk === targetId) return [...path, n.child_item_id || ""];
                      if (n.children.length > 0) {
                        const r = collectAncestorIds(n.children, targetId, [...path, n.child_item_id || ""]);
                        if (r) return r;
                      }
                    }
                    return null;
                  };
                  const ancestors = collectAncestorIds(editingTree, addTargetParentId, []);
                  if (ancestors && ancestors.includes(item.id)) return false;
                }
                return true;
              }).map((item) => {
                const badge = getItemTypeBadge(item.division);
                return (
                  <div
                    key={item.id}
                    className="flex items-center gap-3 px-3 py-2 border-b last:border-b-0 hover:bg-accent cursor-pointer transition-colors"
                    onClick={() => handleTreeItemSelect(item)}
                  >
                    <span className={cn("text-[10px] font-semibold px-1.5 py-0.5 rounded shrink-0", badge.className)}>
                      {badge.label}
                    </span>
                    <span className="font-mono text-[11px] text-muted-foreground">{item.item_number}</span>
                    <span className="text-xs">{item.item_name}</span>
                    <span className="ml-auto text-[11px] text-muted-foreground">{item.inventory_unit || ""}</span>
                  </div>
                );
              })
            )}
          </div>
        </DialogContent>
      </Dialog>

      {/* ─── 새 버전 생성 다이얼로그 ────────────── */}
      <Dialog open={showNewVersionDialog} onOpenChange={setShowNewVersionDialog}>
        <DialogContent className="max-w-sm">
          <DialogHeader>
            <DialogTitle>새 버전 생성</DialogTitle>
            <DialogDescription>현재 버전의 데이터를 복사하여 새 버전을 만들어요</DialogDescription>
          </DialogHeader>
          <div className="flex flex-col gap-1.5">
            <Label className="text-xs font-medium">버전명 (비워두면 자동 채번)</Label>
            <Input
              className="h-9"
              value={newVersionName}
              onChange={(e) => setNewVersionName(e.target.value)}
              placeholder="예: 2.0"
            />
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setShowNewVersionDialog(false)}>
              취소
            </Button>
            <Button onClick={handleCreateVersion}>
              <Plus className="w-3.5 h-3.5 mr-1" />
              생성
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ─── 이력 추가 다이얼로그 ───────────────── */}
      <Dialog open={showHistoryDialog} onOpenChange={setShowHistoryDialog}>
        <DialogContent className="max-w-sm">
          <DialogHeader>
            <DialogTitle>이력 추가</DialogTitle>
            <DialogDescription>BOM 변경 이력을 기록해요</DialogDescription>
          </DialogHeader>
          <div className="space-y-3">
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">
                변경 유형 <span className="text-destructive">*</span>
              </Label>
              <Select value={newHistory.change_type} onValueChange={(v) => setNewHistory((prev) => ({ ...prev, change_type: v }))}>
                <SelectTrigger className="h-9"><SelectValue placeholder="변경 유형 선택" /></SelectTrigger>
                <SelectContent>
                  {CHANGE_TYPE_OPTIONS.map((t) => (
                    <SelectItem key={t} value={t}>{t}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className="flex flex-col gap-1.5">
              <Label className="text-xs font-medium">변경 내용</Label>
              <Textarea
                className="min-h-[80px] resize-none"
                value={newHistory.change_description}
                onChange={(e) => setNewHistory((prev) => ({ ...prev, change_description: e.target.value }))}
                placeholder="변경 내용을 입력해주세요"
              />
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setShowHistoryDialog(false)}>
              취소
            </Button>
            <Button onClick={handleAddHistory}>
              <Plus className="w-3.5 h-3.5 mr-1" />
              추가
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ─── 엑셀 업로드 다이얼로그 ────────────── */}
      <Dialog open={showExcelUpload} onOpenChange={setShowExcelUpload}>
        <DialogContent className="max-w-md">
          <DialogHeader>
            <DialogTitle>BOM 엑셀 업로드</DialogTitle>
            <DialogDescription>
              {selectedBomId
                ? "선택한 BOM에 새 버전을 엑셀로 생성해요"
                : "엑셀 파일로 새 BOM을 생성해요"}
            </DialogDescription>
          </DialogHeader>
          <div className="space-y-3">
            <div className="border-2 border-dashed rounded-lg p-6 text-center">
              <input
                type="file"
                accept=".xlsx,.xls"
                className="hidden"
                id="excel-file-input"
                onChange={(e) => setExcelFile(e.target.files?.[0] || null)}
              />
              <label htmlFor="excel-file-input" className="cursor-pointer">
                <Upload className="w-8 h-8 mx-auto mb-2 text-muted-foreground/40" />
                <p className="text-sm text-muted-foreground mb-1">
                  {excelFile ? excelFile.name : "클릭하여 파일을 선택해주세요"}
                </p>
                <p className="text-[10px] text-muted-foreground/60">.xlsx, .xls 형식</p>
              </label>
            </div>
            <div className="bg-muted/50 rounded-lg p-3 text-xs text-muted-foreground space-y-1">
              <p className="font-medium text-foreground">엑셀 형식 안내</p>
              <p>레벨 0: BOM 마스터 (최상위 품목)</p>
              <p>레벨 1: 직접 자품목</p>
              <p>레벨 2+: 자품목의 자품목</p>
              <p>필수 컬럼: level, item_number, quantity</p>
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => { setShowExcelUpload(false); setExcelFile(null); }}>
              취소
            </Button>
            <Button onClick={handleExcelUpload} disabled={!excelFile || excelUploading}>
              {excelUploading && <Loader2 className="w-3.5 h-3.5 animate-spin mr-1.5" />}
              업로드
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {ConfirmDialogComponent}

      <TableSettingsModal
        open={ts.open}
        onOpenChange={ts.setOpen}
        tableName={ts.tableName}
        settingsId={ts.settingsId}
        defaultVisibleKeys={ts.defaultVisibleKeys}
        onSave={ts.applySettings}
      />
    </div>
  );
}
