"use client";

import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
import {
  GripVertical,
  Plus,
  X,
  Search,
  ChevronRight,
  ChevronDown,
  Package,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { entityJoinApi } from "@/lib/api/entityJoin";
import { apiClient } from "@/lib/api/client";

// ─── 타입 정의 ───

interface BomItemNode {
  tempId: string;
  id?: string;
  parent_detail_id: string | null;
  seq_no: number;
  level: number;
  children: BomItemNode[];
  _isNew?: boolean;
  _isDeleted?: boolean;
  data: Record<string, any>;
}

interface BomColumnConfig {
  key: string;
  title: string;
  width?: string;
  visible?: boolean;
  editable?: boolean;
  isSourceDisplay?: boolean;
  inputType?: string;
}

interface ItemInfo {
  id: string;
  item_number: string;
  item_name: string;
  type: string;
  unit: string;
  division: string;
}

interface BomItemEditorProps {
  component?: any;
  formData?: Record<string, any>;
  companyCode?: string;
  isDesignMode?: boolean;
  selectedRowsData?: any[];
  onChange?: (flatData: any[]) => void;
  bomId?: string;
}

// 임시 ID 생성
let tempIdCounter = 0;
const generateTempId = () => `temp_${Date.now()}_${++tempIdCounter}`;

// ─── 품목 검색 모달 ───

interface ItemSearchModalProps {
  open: boolean;
  onClose: () => void;
  onSelect: (items: ItemInfo[]) => void;
  companyCode?: string;
  existingItemIds?: Set<string>;
}

function ItemSearchModal({
  open,
  onClose,
  onSelect,
  companyCode,
  existingItemIds,
}: ItemSearchModalProps) {
  const [searchText, setSearchText] = useState("");
  const [items, setItems] = useState<ItemInfo[]>([]);
  const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(false);

  const searchItems = useCallback(
    async (query: string) => {
      setLoading(true);
      try {
        const result = await entityJoinApi.getTableDataWithJoins("item_info", {
          page: 1,
          size: 50,
          search: query
            ? { item_number: query, item_name: query }
            : undefined,
          enableEntityJoin: true,
          companyCodeOverride: companyCode,
        });
        setItems((result.data || []) as ItemInfo[]);
      } catch (error) {
        console.error("[BomItemEditor] 품목 검색 실패:", error);
      } finally {
        setLoading(false);
      }
    },
    [companyCode],
  );

  useEffect(() => {
    if (open) {
      setSearchText("");
      setSelectedItems(new Set());
      searchItems("");
    }
  }, [open, searchItems]);

  const handleSearch = () => {
    searchItems(searchText);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") {
      e.preventDefault();
      handleSearch();
    }
  };

  return (
    <Dialog open={open} onOpenChange={onClose}>
      <DialogContent className="max-w-[95vw] sm:max-w-[600px]">
        <DialogHeader>
          <DialogTitle className="text-base sm:text-lg">품목 검색</DialogTitle>
          <DialogDescription className="text-xs sm:text-sm">
            하위 품목으로 추가할 품목을 선택하세요.
          </DialogDescription>
        </DialogHeader>

        <div className="flex gap-2">
          <Input
            value={searchText}
            onChange={(e) => setSearchText(e.target.value)}
            onKeyDown={handleKeyDown}
            placeholder="품목코드 또는 품목명"
            className="h-8 text-xs sm:h-10 sm:text-sm"
          />
          <Button
            onClick={handleSearch}
            size="sm"
            className="h-8 sm:h-10"
          >
            <Search className="mr-1 h-4 w-4" />
            검색
          </Button>
        </div>

        <div className="max-h-[300px] overflow-y-auto rounded-md border">
          {loading ? (
            <div className="flex items-center justify-center py-8">
              <span className="text-muted-foreground text-sm">검색 중...</span>
            </div>
          ) : items.length === 0 ? (
            <div className="flex items-center justify-center py-8">
              <span className="text-muted-foreground text-sm">
                검색 결과가 없습니다.
              </span>
            </div>
          ) : (
            <table className="w-full text-xs sm:text-sm">
              <thead className="bg-muted sticky top-0 z-10">
                <tr>
                  <th className="w-8 px-2 py-2 text-center">
                    <Checkbox
                      checked={selectedItems.size > 0 && selectedItems.size === items.length}
                      onCheckedChange={(checked) => {
                        if (checked) setSelectedItems(new Set(items.map((i) => i.id)));
                        else setSelectedItems(new Set());
                      }}
                    />
                  </th>
                  <th className="px-3 py-2 text-left font-medium">품목코드</th>
                  <th className="px-3 py-2 text-left font-medium">품목명</th>
                  <th className="px-3 py-2 text-left font-medium">구분</th>
                  <th className="px-3 py-2 text-left font-medium">단위</th>
                </tr>
              </thead>
              <tbody>
                {items.map((item) => {
                  const alreadyAdded = existingItemIds?.has(item.id) || false;
                  return (
                    <tr
                      key={item.id}
                      onClick={() => {
                        if (alreadyAdded) return;
                        setSelectedItems((prev) => {
                          const next = new Set(prev);
                          if (next.has(item.id)) next.delete(item.id);
                          else next.add(item.id);
                          return next;
                        });
                      }}
                      className={cn(
                        "border-t transition-colors",
                        alreadyAdded
                          ? "cursor-not-allowed opacity-40"
                          : "cursor-pointer",
                        !alreadyAdded && selectedItems.has(item.id) ? "bg-primary/10" : !alreadyAdded ? "hover:bg-accent" : "",
                      )}
                    >
                      <td className="px-2 py-2 text-center" onClick={(e) => e.stopPropagation()}>
                        <Checkbox
                          checked={selectedItems.has(item.id)}
                          disabled={alreadyAdded}
                          onCheckedChange={(checked) => {
                            if (alreadyAdded) return;
                            setSelectedItems((prev) => {
                              const next = new Set(prev);
                              if (checked) next.add(item.id);
                              else next.delete(item.id);
                              return next;
                            });
                          }}
                        />
                      </td>
                      <td className="px-3 py-2 font-mono">
                        {item.item_number}
                        {alreadyAdded && <span className="text-muted-foreground ml-1 text-[10px]">(추가됨)</span>}
                      </td>
                      <td className="px-3 py-2">{item.item_name}</td>
                      <td className="px-3 py-2">{item.type}</td>
                      <td className="px-3 py-2">{item.unit}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>

        {selectedItems.size > 0 && (
          <DialogFooter className="gap-2 sm:gap-0">
            <span className="text-muted-foreground text-xs sm:text-sm">
              {selectedItems.size}개 선택됨
            </span>
            <Button
              onClick={() => {
                const selected = items.filter((i) => selectedItems.has(i.id));
                onSelect(selected);
                onClose();
              }}
              className="h-8 text-xs sm:h-10 sm:text-sm"
            >
              <Plus className="mr-1 h-4 w-4" />
              추가
            </Button>
          </DialogFooter>
        )}
      </DialogContent>
    </Dialog>
  );
}

// ─── 트리 노드 행 렌더링 (config.columns 기반 동적 셀) ───

interface TreeNodeRowProps {
  node: BomItemNode;
  depth: number;
  expanded: boolean;
  hasChildren: boolean;
  columns: BomColumnConfig[];
  categoryOptionsMap: Record<string, { value: string; label: string }[]>;
  mainTableName?: string;
  onToggle: () => void;
  onFieldChange: (tempId: string, field: string, value: string) => void;
  onDelete: (tempId: string) => void;
  onAddChild: (parentTempId: string) => void;
  onDragStart: (e: React.DragEvent, tempId: string) => void;
  onDragOver: (e: React.DragEvent, tempId: string) => void;
  onDrop: (e: React.DragEvent, tempId: string) => void;
  isDragOver?: boolean;
}

function TreeNodeRow({
  node,
  depth,
  expanded,
  hasChildren,
  columns,
  categoryOptionsMap,
  mainTableName,
  onToggle,
  onFieldChange,
  onDelete,
  onAddChild,
  onDragStart,
  onDragOver,
  onDrop,
  isDragOver,
}: TreeNodeRowProps) {
  const indentPx = depth * 32;
  const visibleColumns = columns.filter((c) => c.visible !== false);

  const renderCell = (col: BomColumnConfig) => {
    const value = node.data[col.key] ?? "";

    // 소스 표시 컬럼 (읽기 전용)
    if (col.isSourceDisplay) {
      return (
        <span className="truncate text-xs" title={String(value)}>
          {value || "-"}
        </span>
      );
    }

    // 카테고리 타입: API에서 로드한 옵션으로 Select 렌더링
    if (col.inputType === "category") {
      const categoryRef = mainTableName ? `${mainTableName}.${col.key}` : "";
      const options = categoryOptionsMap[categoryRef] || [];
      return (
        <Select
          value={String(value || "")}
          onValueChange={(val) => onFieldChange(node.tempId, col.key, val)}
        >
          <SelectTrigger className="h-7 w-full min-w-[70px] text-xs">
            <SelectValue placeholder={col.title} />
          </SelectTrigger>
          <SelectContent>
            {options.map((opt) => (
              <SelectItem key={opt.value} value={opt.value}>
                {opt.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }

    // 편집 불가능 컬럼
    if (col.editable === false) {
      return (
        <span className="text-muted-foreground truncate text-xs">
          {value || "-"}
        </span>
      );
    }

    // 숫자 입력
    if (col.inputType === "number" || col.inputType === "decimal") {
      return (
        <Input
          type="number"
          value={String(value)}
          onChange={(e) => onFieldChange(node.tempId, col.key, e.target.value)}
          className="h-7 w-full min-w-[50px] text-center text-xs"
          placeholder={col.title}
        />
      );
    }

    // 기본 텍스트 입력
    return (
      <Input
        value={String(value)}
        onChange={(e) => onFieldChange(node.tempId, col.key, e.target.value)}
        className="h-7 w-full min-w-[50px] text-xs"
        placeholder={col.title}
      />
    );
  };

  return (
    <div
      className={cn(
        "group flex items-center gap-2 rounded-md border px-2 py-1.5",
        "transition-colors hover:bg-accent/30",
        depth > 0 && "ml-2 border-l-2 border-l-primary/20",
        isDragOver && "border-primary bg-primary/5 border-dashed",
      )}
      style={{ marginLeft: `${indentPx}px` }}
      draggable
      onDragStart={(e) => onDragStart(e, node.tempId)}
      onDragOver={(e) => onDragOver(e, node.tempId)}
      onDrop={(e) => onDrop(e, node.tempId)}
    >
      <GripVertical className="text-muted-foreground h-4 w-4 shrink-0 cursor-grab" />

      <button
        onClick={onToggle}
        className={cn(
          "flex h-5 w-5 shrink-0 items-center justify-center rounded",
          hasChildren
            ? "hover:bg-accent cursor-pointer"
            : "cursor-default opacity-0",
        )}
      >
        {hasChildren &&
          (expanded ? (
            <ChevronDown className="h-3.5 w-3.5" />
          ) : (
            <ChevronRight className="h-3.5 w-3.5" />
          ))}
      </button>

      <span className="text-muted-foreground w-6 shrink-0 text-center text-xs font-medium">
        {node.seq_no}
      </span>

      {node.level > 0 && (
        <span className="bg-primary/10 text-primary shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold">
          L{node.level}
        </span>
      )}

      {/* config.columns 기반 동적 셀 렌더링 */}
      {visibleColumns.map((col) => (
        <div
          key={col.key}
          className={cn(
            "shrink-0",
            col.isSourceDisplay ? "min-w-[60px] flex-1" : "min-w-[50px]",
          )}
          style={{ width: col.width && col.width !== "auto" ? col.width : undefined }}
        >
          {renderCell(col)}
        </div>
      ))}

      <Button
        variant="ghost"
        size="icon"
        className="h-7 w-7 shrink-0"
        onClick={() => onAddChild(node.tempId)}
        title="하위 품목 추가"
      >
        <Plus className="h-3.5 w-3.5" />
      </Button>

      <Button
        variant="ghost"
        size="icon"
        className="text-destructive hover:bg-destructive/10 h-7 w-7 shrink-0"
        onClick={() => onDelete(node.tempId)}
        title="삭제"
      >
        <X className="h-3.5 w-3.5" />
      </Button>
    </div>
  );
}

// ─── 메인 컴포넌트 ───

export function BomItemEditorComponent({
  component,
  formData,
  companyCode,
  isDesignMode = false,
  selectedRowsData,
  onChange,
  bomId: propBomId,
}: BomItemEditorProps) {
  const [treeData, setTreeData] = useState<BomItemNode[]>([]);
  const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
  const [loading, setLoading] = useState(false);
  const [itemSearchOpen, setItemSearchOpen] = useState(false);
  const [addTargetParentId, setAddTargetParentId] = useState<string | null>(null);
  const [categoryOptionsMap, setCategoryOptionsMap] = useState<Record<string, { value: string; label: string }[]>>({});

  // 설정값 추출
  const cfg = useMemo(() => component?.componentConfig || {}, [component]);
  const mainTableName = cfg.mainTableName || "bom_detail";
  const parentKeyColumn = (cfg.parentKeyColumn && cfg.parentKeyColumn !== "id") ? cfg.parentKeyColumn : "parent_detail_id";
  const columns: BomColumnConfig[] = useMemo(() => cfg.columns || [], [cfg.columns]);
  const visibleColumns = useMemo(() => columns.filter((c) => c.visible !== false), [columns]);
  const fkColumn = cfg.foreignKeyColumn || "bom_id";

  // BOM ID 결정
  const bomId = useMemo(() => {
    if (propBomId) return propBomId;
    if (formData?.id) return formData.id as string;
    if (selectedRowsData?.[0]?.id) return selectedRowsData[0].id as string;
    return null;
  }, [propBomId, formData, selectedRowsData]);

  // BOM 전용 API로 현재 current_version_id 조회
  const fetchCurrentVersionId = useCallback(async (id: string): Promise<string | null> => {
    try {
      const res = await apiClient.get(`/bom/${id}/versions`);
      if (res.data?.success) {
        // bom.current_version_id를 직접 반환 (불러오기와 사용확정 구분)
        if (res.data.currentVersionId) return res.data.currentVersionId;
        // fallback: active 상태 버전
        const activeVersion = res.data.data?.find((v: any) => v.status === "active");
        if (activeVersion) return activeVersion.id;
      }
    } catch (e) {
      console.error("[BomItemEditor] current_version_id 조회 실패:", e);
    }
    return null;
  }, []);

  // formData에서 가져오는 versionId (fallback용)
  const propsVersionId = (formData?.current_version_id as string)
    || (selectedRowsData?.[0]?.current_version_id as string)
    || null;

  // ─── 카테고리 옵션 로드 (리피터 방식) ───

  useEffect(() => {
    const loadCategoryOptions = async () => {
      const categoryColumns = visibleColumns.filter((col) => col.inputType === "category");
      if (categoryColumns.length === 0) return;

      for (const col of categoryColumns) {
        const categoryRef = `${mainTableName}.${col.key}`;

        const alreadyLoaded = await new Promise<boolean>((resolve) => {
          setCategoryOptionsMap((prev) => {
            resolve(!!prev[categoryRef]);
            return prev;
          });
        });
        if (alreadyLoaded) continue;

        try {
          const response = await apiClient.get(`/table-categories/${mainTableName}/${col.key}/values?includeInactive=true`);
          if (response.data?.success && response.data.data) {
            const options = response.data.data.map((item: any) => ({
              value: item.valueCode || item.value_code,
              label: item.valueLabel || item.value_label || item.displayValue || item.display_value || item.label,
            }));
            setCategoryOptionsMap((prev) => ({ ...prev, [categoryRef]: options }));
          }
        } catch (error) {
          console.error(`카테고리 옵션 로드 실패 (${categoryRef}):`, error);
        }
      }
    };

    if (!isDesignMode) {
      loadCategoryOptions();
    }
  }, [visibleColumns, mainTableName, isDesignMode]);

  // ─── 데이터 로드 ───

  const sourceFk = cfg.dataSource?.foreignKey || "child_item_id";
  const sourceTable = cfg.dataSource?.sourceTable || "item_info";

  const loadBomDetails = useCallback(
    async (id: string) => {
      if (!id) return;
      setLoading(true);
      try {
        // isSourceDisplay 컬럼을 추가 조인 컬럼으로 요청
        const displayCols = columns.filter((c) => c.isSourceDisplay);
        const additionalJoinColumns = displayCols.map((col) => ({
          sourceTable,
          sourceColumn: sourceFk,
          joinAlias: `${sourceFk}_${col.key}`,
          referenceTable: sourceTable,
        }));

        // 서버에서 최신 current_version_id 조회 (항상 최신 보장)
        const freshVersionId = await fetchCurrentVersionId(id);
        const effectiveVersionId = freshVersionId || propsVersionId;

        const searchFilter: Record<string, any> = { [fkColumn]: id };
        if (effectiveVersionId) {
          searchFilter.version_id = effectiveVersionId;
        }

        // autoFilter 비활성화: BOM 전용 API로 company_code 관리
        const res = await apiClient.get(`/table-management/tables/${mainTableName}/data-with-joins`, {
          params: {
            page: 1,
            size: 500,
            search: JSON.stringify(searchFilter),
            sortBy: "seq_no",
            sortOrder: "asc",
            enableEntityJoin: true,
            additionalJoinColumns: additionalJoinColumns.length > 0 ? JSON.stringify(additionalJoinColumns) : undefined,
            autoFilter: JSON.stringify({ enabled: false }),
          },
        });

        const rawData = res.data?.data?.data || res.data?.data || [];
        const rows = (Array.isArray(rawData) ? rawData : []).map((row: Record<string, any>) => {
          const mapped = { ...row };
          for (const key of Object.keys(row)) {
            if (key.startsWith(`${sourceFk}_`)) {
              const shortKey = key.replace(`${sourceFk}_`, "");
              if (!mapped[shortKey]) mapped[shortKey] = row[key];
            }
          }
          return mapped;
        });

        const tree = buildTree(rows);
        setTreeData(tree);

        const firstLevelIds = new Set<string>(
          tree.map((n) => n.tempId || n.id || ""),
        );
        setExpandedNodes(firstLevelIds);
      } catch (error) {
        console.error("[BomItemEditor] 데이터 로드 실패:", error);
      } finally {
        setLoading(false);
      }
    },
    [mainTableName, fkColumn, sourceFk, sourceTable, columns, fetchCurrentVersionId, propsVersionId],
  );

  // formData.current_version_id가 변경될 때도 재로드 (버전 전환 시 반영)
  const formVersionRef = useRef<string | null>(null);
  useEffect(() => {
    if (!bomId || isDesignMode) return;
    const currentFormVersion = formData?.current_version_id as string || null;
    // bomId가 바뀌거나, formData의 current_version_id가 바뀌면 재로드
    if (formVersionRef.current !== currentFormVersion || !formVersionRef.current) {
      formVersionRef.current = currentFormVersion;
      loadBomDetails(bomId);
    }
  }, [bomId, isDesignMode, loadBomDetails, formData?.current_version_id]);

  // ─── 트리 빌드 (동적 데이터) ───

  const buildTree = (flatData: any[]): BomItemNode[] => {
    const nodeMap = new Map<string, BomItemNode>();
    const roots: BomItemNode[] = [];

    flatData.forEach((item) => {
      const tempId = item.id || generateTempId();
      nodeMap.set(item.id || tempId, {
        tempId,
        id: item.id,
        parent_detail_id: item[parentKeyColumn] || null,
        seq_no: Number(item.seq_no) || 0,
        level: Number(item.level) || 0,
        children: [],
        data: { ...item },
      });
    });

    flatData.forEach((item) => {
      const nodeId = item.id || "";
      const node = nodeMap.get(nodeId);
      if (!node) return;

      const parentId = item[parentKeyColumn];
      if (parentId && nodeMap.has(parentId)) {
        nodeMap.get(parentId)!.children.push(node);
      } else {
        roots.push(node);
      }
    });

    const sortChildren = (nodes: BomItemNode[]) => {
      nodes.sort((a, b) => a.seq_no - b.seq_no);
      nodes.forEach((n) => sortChildren(n.children));
    };
    sortChildren(roots);

    return roots;
  };

  // ─── 트리 -> 평면 변환 (onChange 콜백용) ───

  const flattenTree = useCallback((nodes: BomItemNode[]): any[] => {
    const result: any[] = [];
    const traverse = (
      items: BomItemNode[],
      parentId: string | null,
      level: number,
    ) => {
      items.forEach((node, idx) => {
        result.push({
          ...node.data,
          id: node.id,
          tempId: node.tempId,
          [parentKeyColumn]: parentId,
          [fkColumn]: bomId,
          seq_no: String(idx + 1),
          level: String(level),
          _isNew: node._isNew,
          _targetTable: mainTableName,
          _fkColumn: fkColumn,
          _deferSave: true,
        });
        if (node.children.length > 0) {
          traverse(node.children, node.id || node.tempId, level + 1);
        }
      });
    };
    traverse(nodes, null, 0);
    return result;
  }, [parentKeyColumn, mainTableName, fkColumn, bomId]);

  // 트리 변경 시 부모에게 알림
  const notifyChange = useCallback(
    (newTree: BomItemNode[]) => {
      setTreeData(newTree);
      onChange?.(flattenTree(newTree));
    },
    [onChange, flattenTree],
  );

  // ─── DB 저장 (INSERT/UPDATE/DELETE 일괄) ───

  const [saving, setSaving] = useState(false);
  const [hasChanges, setHasChanges] = useState(false);

  const originalDataRef = React.useRef<Set<string>>(new Set());
  useEffect(() => {
    if (treeData.length > 0 && originalDataRef.current.size === 0) {
      const collectIds = (nodes: BomItemNode[]) => {
        nodes.forEach((n) => {
          if (n.id) originalDataRef.current.add(n.id);
          collectIds(n.children);
        });
      };
      collectIds(treeData);
    }
  }, [treeData]);

  const markChanged = useCallback(() => setHasChanges(true), []);
  const originalNotifyChange = notifyChange;
  const notifyChangeWithDirty = useCallback(
    (newTree: BomItemNode[]) => {
      originalNotifyChange(newTree);
      markChanged();
    },
    [originalNotifyChange, markChanged],
  );

  const handleSaveAllRef = React.useRef<(() => Promise<void>) | null>(null);

  // EditModal 저장 시 beforeFormSave 이벤트로 디테일 데이터도 함께 저장
  useEffect(() => {
    if (isDesignMode || !bomId) return;
    const handler = (e: Event) => {
      const detail = (e as CustomEvent).detail;
      if (handleSaveAllRef.current) {
        const savePromise = handleSaveAllRef.current();
        if (detail?.pendingPromises) {
          detail.pendingPromises.push(savePromise);
        }
      }
    };
    window.addEventListener("beforeFormSave", handler);
    return () => window.removeEventListener("beforeFormSave", handler);
  }, [isDesignMode, bomId]);

  const handleSaveAll = useCallback(async () => {
    if (!bomId) return;
    setSaving(true);
    try {
      // version_id 확보: 없으면 서버에서 자동 초기화
      let saveVersionId = await fetchCurrentVersionId(bomId) || propsVersionId;
      if (!saveVersionId) {
        try {
          const initRes = await apiClient.post(`/bom/${bomId}/initialize-version`);
          if (initRes.data?.success && initRes.data.data?.versionId) {
            saveVersionId = initRes.data.data.versionId;
          }
        } catch (e) {
          console.error("[BomItemEditor] 버전 초기화 실패:", e);
        }
      }

      const collectAll = (nodes: BomItemNode[], parentRealId: string | null, level: number): any[] => {
        const result: any[] = [];
        nodes.forEach((node, idx) => {
          result.push({
            node,
            parentRealId,
            level,
            seqNo: idx + 1,
          });
          if (node.children.length > 0) {
            result.push(...collectAll(node.children, node.id || node.tempId, level + 1));
          }
        });
        return result;
      };

      const allNodes = collectAll(treeData, null, 0);
      const tempToReal: Record<string, string> = {};
      let savedCount = 0;

      for (const { node, parentRealId, level, seqNo } of allNodes) {
        const realParentId = parentRealId
          ? tempToReal[parentRealId] || parentRealId
          : null;

        if (node._isNew) {
          const raw: Record<string, any> = {
            ...node.data,
            [fkColumn]: bomId,
            [parentKeyColumn]: realParentId,
            seq_no: String(seqNo),
            level: String(level),
            company_code: companyCode || undefined,
            version_id: saveVersionId || undefined,
          };
          // bom_detail에 유효한 필드만 남기기 (item_info 조인 필드 제거)
          const payload: Record<string, any> = {};
          const validKeys = new Set([
            fkColumn, parentKeyColumn, "seq_no", "level", "child_item_id",
            "quantity", "unit", "loss_rate", "remark", "process_type",
            "base_qty", "revision", "version_id", "company_code", "writer",
          ]);
          Object.keys(raw).forEach((k) => {
            if (validKeys.has(k)) payload[k] = raw[k];
          });

          const resp = await apiClient.post(
            `/table-management/tables/${mainTableName}/add`,
            payload,
          );
          const newId = resp.data?.data?.id;
          if (newId) tempToReal[node.tempId] = newId;
          savedCount++;
        } else if (node.id) {
          const updatedData: Record<string, any> = {
            id: node.id,
            [fkColumn]: bomId,
            [parentKeyColumn]: realParentId,
            seq_no: String(seqNo),
            level: String(level),
          };
          ["quantity", "unit", "loss_rate", "remark", "process_type", "base_qty", "revision", "child_item_id", "version_id", "company_code"].forEach((k) => {
            if (node.data[k] !== undefined) updatedData[k] = node.data[k];
          });

          await apiClient.put(
            `/table-management/tables/${mainTableName}/edit`,
            { originalData: { id: node.id }, updatedData },
          );
          savedCount++;
        }
      }

      const currentIds = new Set(allNodes.filter((a) => a.node.id).map((a) => a.node.id));
      for (const oldId of originalDataRef.current) {
        if (!currentIds.has(oldId)) {
          await apiClient.delete(
            `/table-management/tables/${mainTableName}/delete`,
            { data: [{ id: oldId }] },
          );
          savedCount++;
        }
      }

      originalDataRef.current = new Set(allNodes.filter((a) => a.node.id || tempToReal[a.node.tempId]).map((a) => a.node.id || tempToReal[a.node.tempId]));
      setHasChanges(false);
      if (bomId) loadBomDetails(bomId);
      window.dispatchEvent(new CustomEvent("refreshTable"));
      console.log(`[BomItemEditor] ${savedCount}건 저장 완료`);
    } catch (error) {
      console.error("[BomItemEditor] 저장 실패:", error);
      alert("저장 중 오류가 발생했습니다.");
    } finally {
      setSaving(false);
    }
  }, [bomId, treeData, fkColumn, parentKeyColumn, mainTableName, companyCode, sourceFk, loadBomDetails, fetchCurrentVersionId, propsVersionId]);

  useEffect(() => {
    handleSaveAllRef.current = handleSaveAll;
  }, [handleSaveAll]);

  // ─── 노드 조작 함수들 ───

  // 트리에서 특정 노드 찾기 (재귀)
  const findAndUpdate = (
    nodes: BomItemNode[],
    targetTempId: string,
    updater: (node: BomItemNode) => BomItemNode | null,
  ): BomItemNode[] => {
    const result: BomItemNode[] = [];
    for (const node of nodes) {
      if (node.tempId === targetTempId) {
        const updated = updater(node);
        if (updated) result.push(updated);
      } else {
        result.push({
          ...node,
          children: findAndUpdate(node.children, targetTempId, updater),
        });
      }
    }
    return result;
  };

  // 필드 변경 (data Record 내부 업데이트)
  const handleFieldChange = useCallback(
    (tempId: string, field: string, value: string) => {
      const newTree = findAndUpdate(treeData, tempId, (node) => ({
        ...node,
        data: { ...node.data, [field]: value },
      }));
      notifyChangeWithDirty(newTree);
    },
    [treeData, notifyChangeWithDirty],
  );

  // 노드 삭제
  const handleDelete = useCallback(
    (tempId: string) => {
      const newTree = findAndUpdate(treeData, tempId, () => null);
      notifyChangeWithDirty(newTree);
    },
    [treeData, notifyChangeWithDirty],
  );

  // 하위 품목 추가 시작 (모달 열기)
  const handleAddChild = useCallback((parentTempId: string) => {
    setAddTargetParentId(parentTempId);
    setItemSearchOpen(true);
  }, []);

  // 같은 레벨(형제) 품목 ID 목록 (동일 레벨 중복 방지, 하위 레벨은 허용)
  const existingItemIds = useMemo(() => {
    const ids = new Set<string>();
    const fkField = cfg.dataSource?.foreignKey || "child_item_id";

    if (addTargetParentId === null) {
      // 루트 레벨 추가: 루트 노드의 형제들만 체크
      for (const n of treeData) {
        const fk = n.data[fkField];
        if (fk) ids.add(fk);
      }
    } else {
      // 하위 추가: 해당 부모의 직속 자식들만 체크
      const findParent = (nodes: BomItemNode[]): BomItemNode | null => {
        for (const n of nodes) {
          if (n.tempId === addTargetParentId) return n;
          const found = findParent(n.children);
          if (found) return found;
        }
        return null;
      };
      const parent = findParent(treeData);
      if (parent) {
        for (const child of parent.children) {
          const fk = child.data[fkField];
          if (fk) ids.add(fk);
        }
      }
    }

    return ids;
  }, [treeData, cfg, addTargetParentId]);

  // 루트 품목 추가 시작
  const handleAddRoot = useCallback(() => {
    setAddTargetParentId(null);
    setItemSearchOpen(true);
  }, []);

  // 품목 선택 후 추가 (다중 선택 지원)
  const handleItemSelect = useCallback(
    (selectedItemsList: ItemInfo[]) => {
      let newTree = [...treeData];

      for (const item of selectedItemsList) {
        const sourceData: Record<string, any> = {};
        const sourceTable = cfg.dataSource?.sourceTable;
        if (sourceTable) {
          const sourceFk = cfg.dataSource?.foreignKey || "child_item_id";
          sourceData[sourceFk] = item.id;
          Object.keys(item).forEach((key) => {
            sourceData[`_display_${key}`] = (item as any)[key];
            sourceData[key] = (item as any)[key];
          });
        }

        const newNode: BomItemNode = {
          tempId: generateTempId(),
          parent_detail_id: null,
          seq_no: 0,
          level: 0,
          children: [],
          _isNew: true,
          data: {
            ...sourceData,
            quantity: "1",
            loss_rate: "0",
            remark: "",
          },
        };

        if (addTargetParentId === null) {
          newNode.seq_no = newTree.length + 1;
          newNode.level = 0;
          newTree = [...newTree, newNode];
        } else {
          newTree = findAndUpdate(newTree, addTargetParentId, (parent) => {
            newNode.parent_detail_id = parent.id || parent.tempId;
            newNode.seq_no = parent.children.length + 1;
            newNode.level = parent.level + 1;
            return {
              ...parent,
              children: [...parent.children, newNode],
            };
          });
        }
      }

      if (addTargetParentId !== null) {
        setExpandedNodes((prev) => new Set([...prev, addTargetParentId]));
      }

      notifyChangeWithDirty(newTree);
    },
    [addTargetParentId, treeData, notifyChangeWithDirty, cfg],
  );

  // 펼침/접기 토글
  const toggleExpand = useCallback((tempId: string) => {
    setExpandedNodes((prev) => {
      const next = new Set(prev);
      if (next.has(tempId)) next.delete(tempId);
      else next.add(tempId);
      return next;
    });
  }, []);

  // ─── 드래그 재정렬 ───
  const [dragId, setDragId] = useState<string | null>(null);
  const [dragOverId, setDragOverId] = useState<string | null>(null);

  // 트리에서 노드를 제거하고 반환
  const removeNode = (nodes: BomItemNode[], tempId: string): { tree: BomItemNode[]; removed: BomItemNode | null } => {
    const result: BomItemNode[] = [];
    let removed: BomItemNode | null = null;
    for (const node of nodes) {
      if (node.tempId === tempId) {
        removed = node;
      } else {
        const childResult = removeNode(node.children, tempId);
        if (childResult.removed) removed = childResult.removed;
        result.push({ ...node, children: childResult.tree });
      }
    }
    return { tree: result, removed };
  };

  // 노드가 대상의 자손인지 확인 (자기 자신의 하위로 드래그 방지)
  const isDescendant = (nodes: BomItemNode[], parentId: string, childId: string): boolean => {
    const find = (list: BomItemNode[]): BomItemNode | null => {
      for (const n of list) {
        if (n.tempId === parentId) return n;
        const found = find(n.children);
        if (found) return found;
      }
      return null;
    };
    const parent = find(nodes);
    if (!parent) return false;
    const check = (children: BomItemNode[]): boolean => {
      for (const c of children) {
        if (c.tempId === childId) return true;
        if (check(c.children)) return true;
      }
      return false;
    };
    return check(parent.children);
  };

  const handleDragStart = useCallback((e: React.DragEvent, tempId: string) => {
    setDragId(tempId);
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/plain", tempId);
  }, []);

  const handleDragOver = useCallback((e: React.DragEvent, tempId: string) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    setDragOverId(tempId);
  }, []);

  const handleDrop = useCallback((e: React.DragEvent, targetTempId: string) => {
    e.preventDefault();
    setDragOverId(null);
    if (!dragId || dragId === targetTempId) return;

    // 자기 자신의 하위로 드래그 방지
    if (isDescendant(treeData, dragId, targetTempId)) return;

    const { tree: treeWithout, removed } = removeNode(treeData, dragId);
    if (!removed) return;

    // 대상 노드 바로 뒤에 같은 레벨로 삽입
    const insertAfter = (nodes: BomItemNode[], afterId: string, node: BomItemNode): { result: BomItemNode[]; inserted: boolean } => {
      const result: BomItemNode[] = [];
      let inserted = false;
      for (const n of nodes) {
        result.push(n);
        if (n.tempId === afterId) {
          result.push({ ...node, level: n.level, parent_detail_id: n.parent_detail_id });
          inserted = true;
        } else if (!inserted) {
          const childResult = insertAfter(n.children, afterId, node);
          if (childResult.inserted) {
            result[result.length - 1] = { ...n, children: childResult.result };
            inserted = true;
          }
        }
      }
      return { result, inserted };
    };

    const { result, inserted } = insertAfter(treeWithout, targetTempId, removed);
    if (inserted) {
      const reindex = (nodes: BomItemNode[], depth = 0): BomItemNode[] =>
        nodes.map((n, i) => ({ ...n, seq_no: i + 1, level: depth, children: reindex(n.children, depth + 1) }));
      notifyChangeWithDirty(reindex(result));
    }

    setDragId(null);
  }, [dragId, treeData, notifyChangeWithDirty]);

  // ─── 재귀 렌더링 ───

  const renderNodes = (nodes: BomItemNode[], depth: number) => {
    return nodes.map((node) => {
      const isExpanded = expandedNodes.has(node.tempId);
      return (
        <React.Fragment key={node.tempId}>
          <TreeNodeRow
            node={node}
            depth={depth}
            expanded={isExpanded}
            hasChildren={node.children.length > 0}
            columns={visibleColumns}
            categoryOptionsMap={categoryOptionsMap}
            mainTableName={mainTableName}
            onToggle={() => toggleExpand(node.tempId)}
            onFieldChange={handleFieldChange}
            onDelete={handleDelete}
            onAddChild={handleAddChild}
            onDragStart={handleDragStart}
            onDragOver={handleDragOver}
            onDrop={handleDrop}
            isDragOver={dragOverId === node.tempId}
          />
          {isExpanded &&
            node.children.length > 0 &&
            renderNodes(node.children, depth + 1)}
        </React.Fragment>
      );
    });
  };

  // ─── 디자인 모드 미리보기 ───

  if (isDesignMode) {
    const cfg = component?.componentConfig || {};
    const hasConfig =
      cfg.mainTableName || cfg.dataSource?.sourceTable || (cfg.columns && cfg.columns.length > 0);

    if (!hasConfig) {
      return (
        <div className="rounded-md border border-dashed p-6 text-center">
          <Package className="text-muted-foreground mx-auto mb-2 h-8 w-8" />
          <p className="text-muted-foreground text-sm font-medium">
            BOM 하위 품목 편집기
          </p>
          <p className="text-muted-foreground text-xs">
            설정 패널에서 테이블과 컬럼을 지정하세요
          </p>
        </div>
      );
    }

    const visibleColumns = (cfg.columns || []).filter((c: any) => c.visible !== false);

    const DUMMY_DATA: Record<string, string[]> = {
      item_name: ["본체 조립", "프레임", "커버", "전장 조립", "PCB 보드"],
      item_number: ["ASM-001", "PRT-010", "PRT-011", "ASM-002", "PRT-020"],
      specification: ["100×50", "200mm", "ABS", "50×30", "4-Layer"],
      material: ["AL6061", "SUS304", "ABS", "FR-4", "구리"],
      stock_unit: ["EA", "EA", "EA", "EA", "EA"],
      quantity: ["1", "2", "1", "1", "3"],
      loss_rate: ["0", "5", "3", "0", "2"],
      unit: ["EA", "EA", "EA", "EA", "EA"],
      remark: ["", "외주", "", "", ""],
      seq_no: ["1", "2", "3", "4", "5"],
    };
    const DUMMY_DEPTHS = [0, 1, 1, 0, 1];

    const getDummyValue = (col: any, rowIdx: number): string => {
      const vals = DUMMY_DATA[col.key];
      if (vals) return vals[rowIdx % vals.length];
      return "";
    };

    return (
      <div className="space-y-2">
        <div className="flex items-center justify-between">
          <h4 className="text-sm font-semibold">하위 품목 구성</h4>
          <Button size="sm" className="h-7 text-xs" disabled>
            <Plus className="mr-1 h-3 w-3" />
            품목추가
          </Button>
        </div>

        {/* 설정 요약 뱃지 */}
        <div className="flex flex-wrap gap-1">
          {cfg.mainTableName && (
            <span className="rounded bg-amber-100 px-1.5 py-0.5 text-[10px] text-orange-700">
              저장: {cfg.mainTableName}
            </span>
          )}
          {cfg.dataSource?.sourceTable && (
            <span className="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary">
              소스: {cfg.dataSource.sourceTable}
            </span>
          )}
          {cfg.parentKeyColumn && (
            <span className="rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] text-emerald-700">
              트리: {cfg.parentKeyColumn}
            </span>
          )}
        </div>

        {/* 테이블 형태 미리보기 - config.columns 순서 그대로 */}
        <div className="overflow-hidden rounded-md border">
          {visibleColumns.length === 0 ? (
            <div className="flex flex-col items-center justify-center py-6">
              <Package className="text-muted-foreground mb-1.5 h-6 w-6" />
              <p className="text-muted-foreground text-xs">
                컬럼 탭에서 표시할 컬럼을 선택하세요
              </p>
            </div>
          ) : (
            <table className="w-full text-[10px]">
              <thead className="bg-muted/60">
                <tr>
                  <th className="w-6 px-1 py-1.5 text-center font-medium" />
                  <th className="w-5 px-0.5 py-1.5 text-center font-medium">#</th>
                  {visibleColumns.map((col: any) => (
                    <th
                      key={col.key}
                      className={cn(
                        "px-2 py-1.5 text-left font-medium",
                        col.isSourceDisplay && "text-primary",
                      )}
                      style={{ width: col.width && col.width !== "auto" ? col.width : undefined }}
                    >
                      {col.title}
                    </th>
                  ))}
                  <th className="w-14 px-1 py-1.5 text-center font-medium">액션</th>
                </tr>
              </thead>
              <tbody>
                {DUMMY_DEPTHS.map((depth, rowIdx) => (
                  <tr
                    key={rowIdx}
                    className={cn(
                      "border-t transition-colors",
                      rowIdx === 0 && "bg-accent/20",
                    )}
                  >
                    <td className="px-1 py-1 text-center">
                      <div className="flex items-center justify-center gap-0.5" style={{ paddingLeft: `${depth * 10}px` }}>
                        {depth === 0 ? (
                          <ChevronDown className="h-3 w-3 opacity-50" />
                        ) : (
                          <span className="text-primary/40 text-[10px]">└</span>
                        )}
                      </div>
                    </td>
                    <td className="text-muted-foreground px-0.5 py-1 text-center">
                      {rowIdx + 1}
                    </td>
                    {visibleColumns.map((col: any) => (
                      <td key={col.key} className="px-1.5 py-0.5">
                        {col.isSourceDisplay ? (
                          <span className="truncate text-primary">
                            {getDummyValue(col, rowIdx) || col.title}
                          </span>
                        ) : col.editable !== false ? (
                          <div className="h-5 rounded border bg-background px-1.5 text-[10px] leading-5">
                            {getDummyValue(col, rowIdx)}
                          </div>
                        ) : (
                          <span className="text-muted-foreground">
                            {getDummyValue(col, rowIdx)}
                          </span>
                        )}
                      </td>
                    ))}
                    <td className="px-1 py-1 text-center">
                      <div className="flex items-center justify-center gap-0.5">
                        <div className="text-muted-foreground flex h-5 w-5 items-center justify-center rounded opacity-40">
                          <Plus className="h-3 w-3" />
                        </div>
                        <div className="text-muted-foreground flex h-5 w-5 items-center justify-center rounded opacity-40">
                          <X className="h-3 w-3" />
                        </div>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    );
  }

  // ─── 메인 렌더링 ───

  return (
    <div className="space-y-3">
      {/* 헤더 */}
      <div className="flex items-center justify-between">
        <h4 className="text-sm font-semibold">
          하위 품목 구성
          {hasChanges && <span className="ml-1.5 text-[10px] text-amber-500">(미저장)</span>}
        </h4>
        <div className="flex gap-1.5">
          <Button
            onClick={handleAddRoot}
            variant="outline"
            size="sm"
            className="h-7 text-xs"
          >
            <Plus className="mr-1 h-3 w-3" />
            품목추가
          </Button>
          <Button
            onClick={handleSaveAll}
            disabled={saving || !hasChanges}
            size="sm"
            className="h-7 text-xs"
          >
            {saving ? "저장중..." : "저장"}
          </Button>
        </div>
      </div>

      {/* 트리 목록 */}
      <div className="max-h-[400px] space-y-1 overflow-y-auto">
        {loading ? (
          <div className="flex items-center justify-center py-8">
            <span className="text-muted-foreground text-sm">로딩 중...</span>
          </div>
        ) : treeData.length === 0 ? (
          <div className="flex flex-col items-center justify-center rounded-md border border-dashed py-8">
            <Package className="text-muted-foreground mb-2 h-8 w-8" />
            <p className="text-muted-foreground text-sm">
              하위 품목이 없습니다.
            </p>
            <p className="text-muted-foreground text-xs">
              &quot;품목추가&quot; 버튼을 눌러 추가하세요.
            </p>
          </div>
        ) : (
          renderNodes(treeData, 0)
        )}
      </div>

      {/* 품목 검색 모달 */}
      <ItemSearchModal
        open={itemSearchOpen}
        onClose={() => setItemSearchOpen(false)}
        onSelect={handleItemSelect}
        companyCode={companyCode}
        existingItemIds={existingItemIds}
      />
    </div>
  );
}

export default BomItemEditorComponent;
