"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 { Checkbox } from "@/components/ui/checkbox";
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, DialogDescription, DialogFooter } from "@/components/ui/dialog";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import {
  Plus, Trash2, Save, Loader2, Pencil, Inbox, Settings2, Search, ClipboardList,
  ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, FileSpreadsheet, Copy,
  ChevronUp, ChevronDown, ChevronsUpDown,
} from "lucide-react";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth";
import { useTableSettings } from "@/hooks/useTableSettings";
import { TableSettingsModal } from "@/components/common/TableSettingsModal";
import { toast } from "sonner";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { SmartExcelUploadModal } from "@/components/common/SmartExcelUpload";
import type { SmartExcelUploadConfig, ParsedSheetData } from "@/components/common/SmartExcelUpload";
import { getInspectionGrouped, type InspectionGroupApi } from "@/lib/api/itemInspection";

const TABLE_NAME = "item_inspection_info";
const ITEM_TABLE = "item_info";
const INSPECTION_TABLE = "inspection_standard";

const GRID_COLUMNS = [
  { key: "item_code", label: "품목코드" },
  { key: "item_name", label: "품명" },
  { key: "size", label: "규격" },
  { key: "inspection_type", label: "검사유형" },
  { key: "is_active", label: "사용여부" },
];


type InspectionRow = {
  id: string;
  inspection_standard_id: string;
  inspection_detail: string;
  inspection_method: string;
  apply_process: string;
  classification: string;
  acceptance_criteria: string;
  is_required: boolean;
  judgment_criteria?: string;       // 판단기준 라벨 (수치(범위)/텍스트입력/O·X/선택형)
  selection_options?: string;       // 선택형일 때 옵션 (콤마 구분)
  unit?: string;                    // 검사 단위
};

export default function ItemInspectionInfoPage() {
  const { user } = useAuth();
  const { confirm, ConfirmDialogComponent } = useConfirmDialog();
  const ts = useTableSettings("c16-item-inspection", TABLE_NAME, GRID_COLUMNS);

  const [data, setData] = useState<any[]>([]);
  const [serverGroups, setServerGroups] = useState<InspectionGroupApi[]>([]);
  const [loading, setLoading] = useState(false);
  const [totalCount, setTotalCount] = useState(0);
  const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
  const [checkedIds, setCheckedIds] = useState<string[]>([]);

  // 좌측 품목 패널 서버 페이징
  const [page, setPage] = useState(1);
  const [pageSize] = useState(20);
  const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));

  // 우측 패널: 선택된 품목
  const [selectedItemCode, setSelectedItemCode] = useState<string | null>(null);
  const [selectedTypeTab, setSelectedTypeTab] = useState<string>("");

  // 모달
  const [modalOpen, setModalOpen] = useState(false);
  const [editMode, setEditMode] = useState(false);
  const [form, setForm] = useState<Record<string, any>>({});
  const [saving, setSaving] = useState(false);

  // FK 옵션
  const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string; size: string }[]>([]);
  const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; judgment_criteria: string; selection_options: string; unit: string; types: string[] }[]>([]);
  const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);

  // 검사유형 목록 (검사기준 카테고리 기반)
  const INSPECTION_TYPES = useMemo(() => {
    return inspTypeCatOptions.map((cat) => ({
      key: cat.code,
      label: cat.label,
      matchLabels: [cat.code, cat.label],
    }));
  }, [inspTypeCatOptions]);
  const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
  const [judgmentCatOptions, setJudgmentCatOptions] = useState<{ code: string; label: string }[]>([]);
  const [inspUnitCatOptions, setInspUnitCatOptions] = useState<{ code: string; label: string }[]>([]);
  const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);

  // 품목 카테고리 코드→라벨 (type, inventory_unit)
  const [itemCatMap, setItemCatMap] = useState<Record<string, Record<string, string>>>({});
  const itemCatMapRef = React.useRef(itemCatMap);
  itemCatMapRef.current = itemCatMap;

  // 검사유형별 검사항목 rows (모달용)
  const [inspectionRows, setInspectionRows] = useState<Record<string, InspectionRow[]>>({});
  const [collapsedTypes, setCollapsedTypes] = useState<Record<string, boolean>>({});

  // 복사 모달: 편집 가능한 기준 데이터 상태 (등록/수정 폼과 평행 구조)
  const [copyForm, setCopyForm] = useState<Record<string, any>>({});
  const [copyInspectionRows, setCopyInspectionRows] = useState<Record<string, InspectionRow[]>>({});
  const [copyCollapsedTypes, setCopyCollapsedTypes] = useState<Record<string, boolean>>({});

  // 기본 라우팅 공정 목록 (적용공정 Select용)
  const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);

  // 엑셀 업로드 모달
  const [excelUploadOpen, setExcelUploadOpen] = useState(false);

  // 품목 선택 모달
  const [itemModalOpen, setItemModalOpen] = useState(false);
  const [itemSearchKeyword, setItemSearchKeyword] = useState("");
  const [filteredItems, setFilteredItems] = useState<typeof itemOptions>([]);
  const [itemSearchLoading, setItemSearchLoading] = useState(false);
  const [itemPage, setItemPage] = useState(1);
  const [itemTotal, setItemTotal] = useState(0);
  const itemPageSize = 20;
  const itemTotalPages = Math.max(1, Math.ceil(itemTotal / itemPageSize));

  /* ═══════════════════ FK 옵션 로드 ═══════════════════ */
  useEffect(() => {
    const loadOptions = async () => {
      try {
        const [itemRes, inspRes, userRes] = await Promise.all([
          apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, { page: 1, size: 0, autoFilter: true }),
          apiClient.post(`/table-management/tables/${INSPECTION_TABLE}/data`, { page: 1, size: 0, autoFilter: true }),
          apiClient.post(`/table-management/tables/user_info/data`, { page: 1, size: 0, autoFilter: true }),
        ]);
        const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || [];
        setItemOptions(items.map((r: any) => ({
          code: r.item_number || r.item_code || "",
          name: r.item_name || "",
          item_type: r.type || r.item_type || "",
          unit: r.inventory_unit || "",
          size: r.size || "",
        })));

        const insps = inspRes.data?.data?.data || inspRes.data?.data?.rows || [];
        setInspOptions(insps.map((r: any) => ({
          code: r.id,
          label: r.inspection_criteria || r.inspection_standard || r.id,
          detail: r.inspection_item || r.inspection_criteria || "",
          method: r.inspection_method || "",
          judgment_criteria: r.judgment_criteria || "",
          selection_options: r.selection_options || "",
          unit: r.unit || "",
          types: r.inspection_type ? r.inspection_type.split(",").filter(Boolean) : [],
        })));

        // 품목 카테고리 (type, unit)
        const catMap: Record<string, Record<string, string>> = {};
        for (const col of ["type", "unit", "inventory_unit"]) {
          try {
            const catRes = await apiClient.get(`/table-categories/item_info/${col}/values`);
            if (catRes.data?.success && catRes.data.data?.length) {
              catMap[col] = {};
              const fl = (arr: any[]) => { for (const v of arr) { catMap[col][v.valueCode] = v.valueLabel; if (v.children?.length) fl(v.children); } };
              fl(catRes.data.data);
            }
          } catch { /* skip */ }
        }
        setItemCatMap(catMap);

        // 검사유형 카테고리
        try {
          const catRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_type/values`);
          const flatCats: { code: string; label: string }[] = [];
          const flatten = (arr: any[]) => { for (const v of arr) { flatCats.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flatten(v.children); } };
          if (catRes.data?.data?.length) flatten(catRes.data.data);
          setInspTypeCatOptions(flatCats);
        } catch { /* skip */ }

        // 검사방법 카테고리
        try {
          const methodRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_method/values`);
          const flatMethods: { code: string; label: string }[] = [];
          const flattenM = (arr: any[]) => { for (const v of arr) { flatMethods.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenM(v.children); } };
          if (methodRes.data?.data?.length) flattenM(methodRes.data.data);
          setInspMethodCatOptions(flatMethods);
        } catch { /* skip */ }

        // 판단기준 카테고리
        try {
          const jcRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/judgment_criteria/values`);
          const flatJc: { code: string; label: string }[] = [];
          const flattenJc = (arr: any[]) => { for (const v of arr) { flatJc.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenJc(v.children); } };
          if (jcRes.data?.data?.length) flattenJc(jcRes.data.data);
          setJudgmentCatOptions(flatJc);
        } catch { /* skip */ }

        // 검사 단위 카테고리
        try {
          const unitRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/unit/values`);
          const flatUnit: { code: string; label: string }[] = [];
          const flattenU = (arr: any[]) => { for (const v of arr) { flatUnit.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenU(v.children); } };
          if (unitRes.data?.data?.length) flattenU(unitRes.data.data);
          setInspUnitCatOptions(flatUnit);
        } catch { /* skip */ }

        const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
        setUserOptions(users.map((u: any) => ({
          code: u.user_id || u.id,
          label: `${u.user_name || u.name || u.user_id}${u.dept_name ? ` (${u.dept_name})` : ""}`,
        })));
      } catch { /* skip */ }
    };
    loadOptions();
  }, []);

  // 품목별 기본 라우팅 공정 로드
  const loadProcessOptions = async (itemCode: string) => {
    try {
      const res = await apiClient.get(`/work-instruction/__lookup__/routing-versions/${encodeURIComponent(itemCode)}`);
      if (res.data?.success && res.data.data?.length > 0) {
        const defaultVer = res.data.data.find((v: any) => v.is_default) || res.data.data[0];
        const procs = (defaultVer.processes || []).map((p: any) => ({
          code: p.process_code,
          name: p.process_name || p.process_code,
        }));
        setProcessOptions(procs);
      } else {
        setProcessOptions([]);
      }
    } catch { setProcessOptions([]); }
  };

  /* ═══════════════════ 품목 선택 모달 ═══════════════════ */
  const searchItemServer = async (page?: number) => {
    const p = page ?? itemPage;
    setItemSearchLoading(true);
    try {
      const filters: any[] = [];
      if (itemSearchKeyword.trim()) {
        filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword.trim() });
      }
      const res = await apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, {
        page: p, size: itemPageSize,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      const resData = res.data?.data;
      const rows = resData?.data || resData?.rows || [];
      const cm = itemCatMapRef.current;
      setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: cm["type"]?.[r.type] || r.type || "", unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "", size: r.size || "" })));
      setItemTotal(resData?.total || resData?.totalCount || rows.length);
    } catch { /* skip */ } finally { setItemSearchLoading(false); }
  };
  const openItemModal = () => { setItemSearchKeyword(""); setItemPage(1); setItemModalOpen(true); searchItemServer(1); };
  const handleItemSearch = () => { setItemPage(1); searchItemServer(1); };
  const selectItem = (item: typeof itemOptions[0]) => {
    if (groupedData.some(g => g.item_code === item.code)) {
      toast.error(`"${item.name}" 은(는) 이미 등록된 품목입니다.`);
      return;
    }
    setForm(p => ({ ...p, item_code: item.code, item_name: item.name }));
    setItemModalOpen(false);
    loadProcessOptions(item.code);
  };

  /* ═══════════════════ 복사 모달 (기준 품목 검사정보 → 다른 품목들) ═══════════════════ */
  const [copyModalOpen, setCopyModalOpen] = useState(false);
  const [copySearchKeyword, setCopySearchKeyword] = useState("");
  const [copyFilteredItems, setCopyFilteredItems] = useState<typeof itemOptions>([]);
  const [copySearchLoading, setCopySearchLoading] = useState(false);
  const [copyPage, setCopyPage] = useState(1);
  const [copyTotal, setCopyTotal] = useState(0);
  const [copyCheckedIds, setCopyCheckedIds] = useState<string[]>([]);
  const [copying, setCopying] = useState(false);
  const [copyProgress, setCopyProgress] = useState({ current: 0, total: 0 });
  const copyPageSize = 20;
  const copyTotalPages = Math.max(1, Math.ceil(copyTotal / copyPageSize));

  const searchCopyTargets = async (page?: number) => {
    const p = page ?? copyPage;
    setCopySearchLoading(true);
    try {
      const filters: any[] = [];
      if (copySearchKeyword.trim()) {
        filters.push({ columnName: "item_name", operator: "contains", value: copySearchKeyword.trim() });
      }
      const res = await apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, {
        page: p, size: copyPageSize,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      const resData = res.data?.data;
      const rows = resData?.data || resData?.rows || [];
      const cm = itemCatMapRef.current;
      const list = rows
        .filter((r: any) => r.item_number !== selectedItemCode)
        .map((r: any) => ({
          code: r.item_number,
          name: r.item_name,
          item_type: cm["type"]?.[r.type] || r.type || "",
          unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "",
          size: r.size || "",
        }));
      setCopyFilteredItems(list);
      setCopyTotal(resData?.total || resData?.totalCount || rows.length);
    } catch { /* skip */ } finally { setCopySearchLoading(false); }
  };
  const openCopyModal = async () => {
    if (!selectedItemCode) { toast.error("복사 기준 품목을 먼저 선택해주세요"); return; }
    const srcGroup = groupedData.find(g => g.item_code === selectedItemCode);
    if (!srcGroup || srcGroup.rows.length === 0) { toast.error("선택한 품목에 복사할 검사정보가 없어요"); return; }
    setCopySearchKeyword(""); setCopyPage(1); setCopyCheckedIds([]);

    // 기준 품목 데이터를 편집용 상태로 복제 (openEdit과 동일한 변환 로직)
    const baseRow = srcGroup.rows[0];
    try {
      const res = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
        page: 1, size: 0,
        dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: selectedItemCode }] },
        autoFilter: true,
      });
      const allRows = res.data?.data?.data || res.data?.data?.rows || [];
      const rowMap: Record<string, InspectionRow[]> = {};
      const typeFlags: Record<string, boolean> = {};
      for (const r of allRows) {
        const inspType = r.inspection_type || "";
        const matched = INSPECTION_TYPES.find(t =>
          t.matchLabels.some(ml => inspType.includes(ml)) ||
          inspTypeCatOptions.some(cat => inspType.includes(cat.code) && t.matchLabels.some(ml => cat.label.includes(ml)))
        );
        const typeKey = matched?.key || "";
        if (!typeKey) continue;
        typeFlags[typeKey] = true;
        if (!rowMap[typeKey]) rowMap[typeKey] = [];
        const mCode = r.inspection_method || "";
        const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
        const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
        const jcCode = inspOpt?.judgment_criteria || "";
        const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
        const unitCode = inspOpt?.unit || "";
        const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
        rowMap[typeKey].push({
          id: crypto.randomUUID(), // 복사본은 새 id 부여 (원본과 분리)
          inspection_standard_id: r.inspection_standard_id || "",
          inspection_detail: r.inspection_item_name || r.inspection_standard || "",
          inspection_method: mLabel,
          apply_process: "",
          acceptance_criteria: r.pass_criteria || "",
          is_required: r.is_required === "true" || r.is_required === true,
          judgment_criteria: jcLabel,
          selection_options: inspOpt?.selection_options || "",
          unit: unitLabel,
        });
      }
      setCopyInspectionRows(rowMap);
      setCopyForm({ ...baseRow, ...typeFlags });
      setCopyCollapsedTypes({});
    } catch {
      setCopyInspectionRows({});
      setCopyForm({ ...baseRow });
      setCopyCollapsedTypes({});
    }

    setCopyModalOpen(true);
    searchCopyTargets(1);
  };
  const handleCopySearch = () => { setCopyPage(1); searchCopyTargets(1); };
  const toggleCopyChecked = (code: string) => {
    setCopyCheckedIds(prev => prev.includes(code) ? prev.filter(c => c !== code) : [...prev, code]);
  };
  const handleCopy = async () => {
    if (!selectedItemCode) { toast.error("복사 기준 품목이 없어요"); return; }
    if (copyCheckedIds.length === 0) { toast.error("붙여넣을 품목을 선택해주세요"); return; }

    // 편집된 rows를 평탄화 (선택된 검사유형의 rows만)
    const enabledTypes = INSPECTION_TYPES.filter(t => !!copyForm[t.key]);
    const flatRows: Array<{ row: InspectionRow; typeLabel: string }> = [];
    for (const t of enabledTypes) {
      const rows = copyInspectionRows[t.key] || [];
      for (const r of rows) flatRows.push({ row: r, typeLabel: t.label });
    }
    if (flatRows.length === 0) { toast.error("복사할 검사항목이 없어요"); return; }

    const ok = await confirm(
      `선택한 ${copyCheckedIds.length}개 품목에 편집된 검사정보(${flatRows.length}개 행)를 복사할까요?`,
      { description: "대상 품목의 기존 검사정보는 삭제 후 교체됩니다.", variant: "info", confirmText: "복사" }
    );
    if (!ok) return;
    setCopying(true);
    setCopyProgress({ current: 0, total: copyCheckedIds.length });
    await new Promise(resolve => setTimeout(resolve, 50));
    try {
      for (let i = 0; i < copyCheckedIds.length; i++) {
        const targetCode = copyCheckedIds[i];
        const target = copyFilteredItems.find(o => o.code === targetCode) || itemOptions.find(o => o.code === targetCode);
        const targetName = target?.name || "";
        const existRes = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
          page: 1, size: 0,
          dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: targetCode }] },
          autoFilter: true,
        });
        const existing = existRes.data?.data?.data || existRes.data?.data?.rows || [];
        if (existing.length > 0) {
          await apiClient.delete(`/table-management/tables/${TABLE_NAME}/delete`, { data: existing.map((r: any) => ({ id: r.id })) });
        }
        for (const { row: r, typeLabel } of flatRows) {
          await apiClient.post(`/table-management/tables/${TABLE_NAME}/add`, {
            id: crypto.randomUUID(),
            item_code: targetCode,
            item_name: targetName,
            inspection_type: typeLabel,
            inspection_standard_id: r.inspection_standard_id || "",
            inspection_item_name: r.inspection_detail || "",
            inspection_method: r.inspection_method || "",
            pass_criteria: r.acceptance_criteria || "",
            is_required: r.is_required ? "true" : "false",
            is_active: copyForm.is_active || "사용",
            manager: copyForm.manager || "",
          });
        }
        setCopyProgress({ current: i + 1, total: copyCheckedIds.length });
      }
      await new Promise(resolve => setTimeout(resolve, 500));
      toast.success(`${copyCheckedIds.length}개 품목에 복사했어요`);
      setCopyModalOpen(false);
      fetchData();
    } catch { toast.error("복사에 실패했어요"); }
    finally {
      setCopying(false);
      setCopyProgress({ current: 0, total: 0 });
    }
  };

  /* ═══════════════════ 데이터 조회 (서버 페이징) ═══════════════════ */
  const fetchData = useCallback(async () => {
    setLoading(true);
    try {
      const search = searchFilters
        .map((f) => (f.value ?? "").toString().trim())
        .filter(Boolean)
        .join(" ");
      const res = await getInspectionGrouped({ page, size: pageSize, search });
      if (res.success) {
        setServerGroups(res.groups || []);
        setTotalCount(res.total || 0);
        const flat: any[] = [];
        for (const g of res.groups || []) {
          for (const r of g.rows || []) flat.push(r);
        }
        setData(flat);
      } else {
        toast.error("품목검사정보 조회에 실패했어요");
      }
    } catch {
      toast.error("품목검사정보 조회에 실패했어요");
    } finally {
      setLoading(false);
    }
  }, [searchFilters, page, pageSize]);

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

  // 검색 필터 변경 시 페이지 1로 리셋
  useEffect(() => { setPage(1); }, [searchFilters]);

  // 서버에서 받은 그룹을 화면용 형태로 변환 (size, types 보강)
  const groupedData = useMemo(() => {
    const itemSizeMap: Record<string, string> = {};
    for (const it of itemOptions) itemSizeMap[it.code] = it.size || "";
    return serverGroups.map((g) => {
      const types: string[] = [];
      for (const r of g.rows || []) {
        if (r.inspection_type && !types.includes(r.inspection_type)) types.push(r.inspection_type);
      }
      return {
        item_code: g.item_code,
        item_name: g.item_name,
        size: itemSizeMap[g.item_code] || "",
        is_active: g.is_active || "",
        types,
        rows: g.rows || [],
      };
    });
  }, [serverGroups, itemOptions]);

  // 좌측 품목 목록 정렬 (컬럼 헤더 클릭 → asc → desc → 해제 순환)
  const [sortConfig, setSortConfig] = useState<{ key: string; direction: "asc" | "desc" } | null>(null);
  const handleSort = (key: string) => {
    setSortConfig(prev => {
      if (!prev || prev.key !== key) return { key, direction: "asc" };
      if (prev.direction === "asc") return { key, direction: "desc" };
      return null;
    });
  };
  const sortedGroupedData = useMemo(() => {
    if (!sortConfig) return groupedData;
    const { key, direction } = sortConfig;
    const mul = direction === "asc" ? 1 : -1;
    const getVal = (g: any): string => {
      if (key === "inspection_type") {
        const code = g.types[0] || "";
        return inspTypeCatOptions.find(o => o.code === code)?.label || code;
      }
      if (key === "is_active") {
        return (g.is_active === "사용" || g.is_active === "true") ? "사용" : "미사용";
      }
      return String(g[key] ?? "");
    };
    return [...groupedData].sort((a, b) => {
      return getVal(a).localeCompare(getVal(b), "ko", { numeric: true }) * mul;
    });
  }, [groupedData, sortConfig, inspTypeCatOptions]);

  // 선택된 품목의 그룹 데이터
  const selectedGroup = useMemo(() => {
    if (!selectedItemCode) return null;
    return groupedData.find(g => g.item_code === selectedItemCode) || null;
  }, [selectedItemCode, groupedData]);

  // 선택된 탭의 검사항목 행
  const selectedTabRows = useMemo(() => {
    if (!selectedGroup || !selectedTypeTab) return [];
    const filtered = selectedGroup.rows.filter((r: any) => r.inspection_type === selectedTypeTab && r.inspection_standard_id);
    return [...filtered].sort((a: any, b: any) => {
      const av = parseInt(String(a.sort_order || "9999"), 10);
      const bv = parseInt(String(b.sort_order || "9999"), 10);
      if (av === bv) return String(a.id).localeCompare(String(b.id));
      return av - bv;
    });
  }, [selectedGroup, selectedTypeTab]);

  // 검사기준 ID → 라벨
  const resolveInspLabel = useCallback((id: string) => {
    return inspOptions.find(o => o.code === id)?.label || id || "-";
  }, [inspOptions]);

  // 검사방법 코드 → 라벨
  const resolveMethodLabel = useCallback((code: string) => {
    return inspMethodCatOptions.find(o => o.code === code)?.label || code || "-";
  }, [inspMethodCatOptions]);

  /* ═══════════════════ CRUD ═══════════════════ */
  const openCreate = () => { setForm({}); setEditMode(false); setInspectionRows({}); setCollapsedTypes({}); setModalOpen(true); };

  const openEdit = async (itemCode?: string) => {
    const code = itemCode || selectedItemCode;
    if (!code) { toast.error("수정할 항목을 선택해주세요"); return; }
    const group = groupedData.find(g => g.item_code === code);
    if (!group) return;
    loadProcessOptions(code);
    const row = group.rows[0];
    setForm({ ...row });
    setEditMode(true);
    setCollapsedTypes({});

    try {
      const res = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
        page: 1, size: 0,
        dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: code }] },
        autoFilter: true,
      });
      const allRows = res.data?.data?.data || res.data?.data?.rows || [];
      // sort_order 기준 오름차순 정렬 (varchar이므로 숫자 파싱 후 비교)
      allRows.sort((a: any, b: any) => {
        const av = parseInt(String(a.sort_order || "9999"), 10);
        const bv = parseInt(String(b.sort_order || "9999"), 10);
        if (av === bv) return String(a.id).localeCompare(String(b.id));
        return av - bv;
      });
      const rowMap: Record<string, InspectionRow[]> = {};
      const typeFlags: Record<string, boolean> = {};

      for (const r of allRows) {
        const inspType = r.inspection_type || "";
        const matched = INSPECTION_TYPES.find(t =>
          t.matchLabels.some(ml => inspType.includes(ml)) ||
          inspTypeCatOptions.some(cat => inspType.includes(cat.code) && t.matchLabels.some(ml => cat.label.includes(ml)))
        );
        const typeKey = matched?.key || "";
        if (!typeKey) continue;
        typeFlags[typeKey] = true;
        if (!rowMap[typeKey]) rowMap[typeKey] = [];
        const mCode = r.inspection_method || "";
        const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
        // 판단기준/선택옵션/단위 resolve
        const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
        const jcCode = inspOpt?.judgment_criteria || "";
        const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
        const unitCode = inspOpt?.unit || "";
        const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
        rowMap[typeKey].push({
          id: r.id,
          inspection_standard_id: r.inspection_standard_id || "",
          inspection_detail: r.inspection_item_name || r.inspection_standard || "",
          inspection_method: mLabel,
          apply_process: r.apply_process || "",
          classification: r.classification || "",
          acceptance_criteria: r.pass_criteria || "",
          is_required: r.is_required === "true" || r.is_required === true,
          judgment_criteria: jcLabel,
          selection_options: inspOpt?.selection_options || "",
          unit: unitLabel,
        });
      }
      setInspectionRows(rowMap);
      setForm(p => ({ ...p, ...typeFlags }));
    } catch { setInspectionRows({}); }
    setModalOpen(true);
  };

  /* ═══════════════════ 검사항목 행 관리 (모달) ═══════════════════ */
  const addInspRow = (typeKey: string) => {
    setInspectionRows(prev => ({
      ...prev,
      [typeKey]: [...(prev[typeKey] || []), { id: crypto.randomUUID(), inspection_standard_id: "", inspection_detail: "", inspection_method: "", apply_process: "", classification: "", acceptance_criteria: "", is_required: false }],
    }));
  };
  const removeInspRow = (typeKey: string, rowId: string) => {
    setInspectionRows(prev => ({ ...prev, [typeKey]: (prev[typeKey] || []).filter(r => r.id !== rowId) }));
  };
  const updateInspRow = (typeKey: string, rowId: string, field: string, value: any) => {
    setInspectionRows(prev => ({
      ...prev,
      [typeKey]: (prev[typeKey] || []).map(r => {
        if (r.id !== rowId) return r;
        if (field === "inspection_standard_id") {
          const opt = inspOptions.find(o => o.code === value);
          const methodCode = opt?.method || "";
          const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
          // 판단기준 라벨 resolve
          const jcCode = opt?.judgment_criteria || "";
          const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
          // 단위 라벨 resolve
          const unitCode = opt?.unit || "";
          const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
          return {
            ...r,
            inspection_standard_id: value,
            inspection_detail: opt?.detail || "",
            inspection_method: methodLabel,
            judgment_criteria: jcLabel,
            selection_options: opt?.selection_options || "",
            unit: unitLabel,
            acceptance_criteria: "", // 판단기준 변경 시 초기화
          };
        }
        return { ...r, [field]: value };
      }),
    }));
  };
  const getFilteredInspOptions = (typeKey: string) => {
    const typeDef = INSPECTION_TYPES.find(t => t.key === typeKey);
    if (!typeDef) return inspOptions;
    const matchCodes = inspTypeCatOptions.filter(cat => typeDef.matchLabels.some(ml => cat.label.includes(ml))).map(cat => cat.code);
    if (matchCodes.length === 0) return inspOptions;
    return inspOptions.filter(opt => opt.types.some(t => matchCodes.includes(t)));
  };
  const toggleCollapse = (typeKey: string) => { setCollapsedTypes(prev => ({ ...prev, [typeKey]: !prev[typeKey] })); };

  /* ═══════════════════ 복사 모달용 검사항목 행 관리 (등록 폼과 평행) ═══════════════════ */
  const addCopyInspRow = (typeKey: string) => {
    setCopyInspectionRows(prev => ({
      ...prev,
      [typeKey]: [...(prev[typeKey] || []), { id: crypto.randomUUID(), inspection_standard_id: "", inspection_detail: "", inspection_method: "", apply_process: "", acceptance_criteria: "", is_required: false }],
    }));
  };
  const removeCopyInspRow = (typeKey: string, rowId: string) => {
    setCopyInspectionRows(prev => ({ ...prev, [typeKey]: (prev[typeKey] || []).filter(r => r.id !== rowId) }));
  };
  const updateCopyInspRow = (typeKey: string, rowId: string, field: string, value: any) => {
    setCopyInspectionRows(prev => ({
      ...prev,
      [typeKey]: (prev[typeKey] || []).map(r => {
        if (r.id !== rowId) return r;
        if (field === "inspection_standard_id") {
          const opt = inspOptions.find(o => o.code === value);
          const methodCode = opt?.method || "";
          const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
          const jcCode = opt?.judgment_criteria || "";
          const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
          const unitCode = opt?.unit || "";
          const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
          return {
            ...r,
            inspection_standard_id: value,
            inspection_detail: opt?.detail || "",
            inspection_method: methodLabel,
            judgment_criteria: jcLabel,
            selection_options: opt?.selection_options || "",
            unit: unitLabel,
            acceptance_criteria: "",
          };
        }
        return { ...r, [field]: value };
      }),
    }));
  };
  const toggleCopyCollapse = (typeKey: string) => { setCopyCollapsedTypes(prev => ({ ...prev, [typeKey]: !prev[typeKey] })); };

  const handleSave = async () => {
    if (!form.item_code) { toast.error("품목코드는 필수예요"); return; }
    setSaving(true);
    try {
      if (editMode) {
        const existRes = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
          page: 1, size: 0,
          dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: form.item_code }] },
          autoFilter: true,
        });
        const existing = existRes.data?.data?.data || existRes.data?.data?.rows || [];
        if (existing.length > 0) {
          await apiClient.delete(`/table-management/tables/${TABLE_NAME}/delete`, { data: existing.map((r: any) => ({ id: r.id })) });
        }
      }
      const enabledTypes = INSPECTION_TYPES.filter(t => !!form[t.key]);
      const rows: any[] = [];
      let globalOrder = 0;
      for (const t of enabledTypes) {
        const typeRows = inspectionRows[t.key] || [];
        if (typeRows.length === 0) {
          globalOrder += 1;
          rows.push({ id: crypto.randomUUID(), item_code: form.item_code, item_name: form.item_name, inspection_type: t.label, is_active: form.is_active || "사용", manager_id: form.manager_id || "", memo: form.remarks || "", sort_order: String(globalOrder).padStart(4, "0") });
        } else {
          for (const r of typeRows) {
            globalOrder += 1;
            rows.push({
              id: crypto.randomUUID(), item_code: form.item_code, item_name: form.item_name, inspection_type: t.label,
              inspection_standard_id: r.inspection_standard_id || "", inspection_item_name: r.inspection_detail || "",
              inspection_method: r.inspection_method || "", pass_criteria: r.acceptance_criteria || "",
              apply_process: r.apply_process || "", classification: r.classification || "",
              is_required: r.is_required ? "true" : "false", is_active: form.is_active || "사용",
              manager_id: form.manager_id || "", memo: form.remarks || "",
              sort_order: String(globalOrder).padStart(4, "0"),
            });
          }
        }
      }
      for (const row of rows) { await apiClient.post(`/table-management/tables/${TABLE_NAME}/add`, row); }
      toast.success(editMode ? "수정했어요" : "등록했어요");
      setModalOpen(false);
      fetchData();
    } catch { toast.error("저장에 실패했어요"); }
    finally { setSaving(false); }
  };

  const handleDelete = async () => {
    if (!selectedItemCode) { toast.error("삭제할 품목을 선택해주세요"); return; }
    const group = groupedData.find(g => g.item_code === selectedItemCode);
    if (!group) return;
    const ok = await confirm(`${selectedItemCode} 검사정보 삭제`, { description: "선택한 품목의 검사정보를 모두 삭제할까요?", variant: "destructive", confirmText: "삭제" });
    if (!ok) return;
    try {
      await apiClient.delete(`/table-management/tables/${TABLE_NAME}/delete`, { data: group.rows.map((r: any) => ({ id: r.id })) });
      toast.success("삭제했어요");
      setSelectedItemCode(null);
      fetchData();
    } catch { toast.error("삭제에 실패했어요"); }
  };

  /* ═══════════════════ 엑셀 업로드 (다건 품목 모드) ═══════════════════ */
  const [excelItemProcessMappings, setExcelItemProcessMappings] = useState<import("@/components/common/SmartExcelUpload").ItemProcessMapping[]>([]);
  const [excelLoading, setExcelLoading] = useState(false);
  const [excelLoadProgress, setExcelLoadProgress] = useState({ loaded: 0, total: 0 });

  const openExcelUpload = async () => {
    setExcelUploadOpen(true);

    // 캐시 히트: 이미 로드된 데이터 있으면 재사용
    if (excelItemProcessMappings.length > 0) return;

    setExcelLoading(true);
    setExcelLoadProgress({ loaded: 0, total: 0 });

    try {
      // 1. 전체 품목 조회
      const itemRes = await apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, {
        page: 1, size: 99999, autoFilter: true,
      });
      const items = itemRes.data?.data?.data || itemRes.data?.data?.rows || [];
      setExcelLoadProgress({ loaded: items.length / 2, total: items.length });

      // 2. 벌크 라우팅 조회 (1회 API 호출)
      const itemCodes = items.map((item: any) => item.item_number || item.item_code || "").filter(Boolean);
      let processMap: Record<string, { code: string; name: string }[]> = {};
      try {
        const bulkRes = await apiClient.post(`/work-instruction/routing-versions-bulk`, { itemCodes });
        if (bulkRes.data?.success) {
          processMap = bulkRes.data.data || {};
        }
      } catch { /* 벌크 API 실패 시 빈 공정으로 진행 */ }

      // 3. 매핑 구성
      const mappings: import("@/components/common/SmartExcelUpload").ItemProcessMapping[] = items.map((item: any) => {
        const code = item.item_number || item.item_code || "";
        return {
          itemCode: code,
          itemName: item.item_name || "",
          processes: processMap[code] || [],
        };
      });

      setExcelLoadProgress({ loaded: items.length, total: items.length });
      setExcelItemProcessMappings(mappings);
      toast.success(`${mappings.length}개 품목 로드 완료`);
    } catch {
      toast.error("품목 정보 로드에 실패했습니다");
    } finally {
      setExcelLoading(false);
    }
  };

  // 엑셀 Config 생성 (다건 품목 모드)
  const excelUploadConfig = useMemo((): SmartExcelUploadConfig => {
    const itemCount = excelItemProcessMappings.length || 9999;
    const makeColumns = () => [
      { key: "item_name", label: "품목명", required: true, type: "dropdown" as const, dropdown: { source: "custom" as const, values: [] }, width: 22 },
      { key: "item_code", label: "품목코드", type: "text" as const, readOnly: true, customFormula: `IFERROR(INDEX('_품목목록'!$A$1:$A$${itemCount},MATCH({col:item_name},'_품목목록'!$B$1:$B$${itemCount},0)),"")`, width: 16 },
      { key: "inspection_standard", label: "검사기준", required: true, type: "dropdown" as const, dropdown: { source: "custom" as const, values: [] }, width: 22 },
      { key: "inspection_detail", label: "검사기준 상세", type: "text" as const, readOnly: true, autoFill: { lookupColumn: "inspection_standard", referenceColumn: "detail" }, width: 20 },
      { key: "inspection_method", label: "검사방법", type: "text" as const, readOnly: true, autoFill: { lookupColumn: "inspection_standard", referenceColumn: "method" }, width: 14 },
      { key: "apply_process", label: "적용공정", type: "dropdown" as const, dropdown: { source: "indirect" as const, indirectKeyColumn: "item_code" }, width: 14 },
      { key: "judgment_criteria", label: "판단기준", type: "text" as const, readOnly: true, autoFill: { lookupColumn: "inspection_standard", referenceColumn: "judgment_criteria" }, width: 14 },
      { key: "standard_value", label: "기준값", type: "number" as const, enableWhen: { column: "judgment_criteria", equals: "수치(범위)" }, width: 12 },
      { key: "tolerance", label: "오차", type: "number" as const, enableWhen: { column: "judgment_criteria", equals: "수치(범위)" }, width: 10 },
      { key: "unit", label: "단위", type: "text" as const, readOnly: true, autoFill: { lookupColumn: "inspection_standard", referenceColumn: "unit" }, width: 10 },
      { key: "acceptance_criteria", label: "합격기준", type: "dropdown" as const, dropdown: { source: "indirect" as const, indirectKeyColumn: "inspection_standard", indirectPrefix: "ACC_" }, width: 18 },
      { key: "is_required", label: "필수", type: "dropdown" as const, dropdown: { source: "custom" as const, values: ["Y", "N"] }, width: 8 },
    ];

    return {
      templateName: "품목검사정보",
      sheets: INSPECTION_TYPES.map(t => ({
        name: t.label,
        typeKey: t.label,
        columns: makeColumns(),
      })),
      referenceSheet: {
        name: "검사기준정보",
        columns: [
          { key: "label", label: "검사기준명" },
          { key: "detail", label: "검사기준 상세" },
          { key: "method", label: "검사방법" },
          { key: "judgment_criteria", label: "판단기준" },
          { key: "selection_options", label: "선택옵션" },
          { key: "unit", label: "단위" },
          { key: "types", label: "검사유형" },
        ],
      },
      conditionalRules: [
        { when: { column: "judgment_criteria", equals: "수치(범위)" }, require: ["standard_value"], ignore: ["acceptance_criteria"] },
        { when: { column: "judgment_criteria", equals: "O/X" }, require: ["acceptance_criteria"], ignore: ["standard_value", "tolerance"] },
        { when: { column: "judgment_criteria", equals: "선택형" }, require: ["acceptance_criteria"], ignore: ["standard_value", "tolerance"] },
        { when: { column: "judgment_criteria", equals: "텍스트입력" }, require: ["acceptance_criteria"], ignore: ["standard_value", "tolerance"] },
      ],
      indirectOptions: {
        conditionColumn: "judgment_criteria",
        optionsByCondition: { "O/X": ["O", "X"] },
        selectionOptionsColumn: "selection_options",
      },
    };
  }, [excelItemProcessMappings]);

  // 참조 데이터 구성
  const excelReferenceData = useMemo(() => {
    return inspOptions.map(opt => {
      const methodLabel = inspMethodCatOptions.find(o => o.code === opt.method)?.label || opt.method;
      const jcLabel = judgmentCatOptions.find(c => c.code === opt.judgment_criteria)?.label || opt.judgment_criteria;
      const unitLabel = inspUnitCatOptions.find(c => c.code === opt.unit)?.label || opt.unit;
      const typeLabels = opt.types.map(t => inspTypeCatOptions.find(c => c.code === t)?.label || t).join(",");
      return { label: opt.label, detail: opt.detail, method: methodLabel, judgment_criteria: jcLabel, selection_options: opt.selection_options, unit: unitLabel, types: typeLabels };
    });
  }, [inspOptions, inspMethodCatOptions, judgmentCatOptions, inspUnitCatOptions, inspTypeCatOptions]);

  // 시트별 드롭다운 옵션
  const excelDropdownOptions = useMemo(() => {
    const opts: Record<string, string[]> = {};
    for (const t of INSPECTION_TYPES) {
      const matchCodes = inspTypeCatOptions.filter(cat => t.matchLabels.some(ml => cat.label.includes(ml))).map(cat => cat.code);
      const filtered = matchCodes.length > 0
        ? inspOptions.filter(opt => opt.types.some(tp => matchCodes.includes(tp)))
        : inspOptions;
      opts[`${t.label}:inspection_standard`] = filtered.map(o => o.label);
    }
    opts["is_required"] = ["Y", "N"];
    // 품목명 드롭다운
    opts["item_name"] = excelItemProcessMappings.map(m => m.itemName);
    return opts;
  }, [inspOptions, inspTypeCatOptions, excelItemProcessMappings]);

  // 라벨→코드 매핑
  const excelLabelToCodeMap = useMemo(() => {
    const map: Record<string, Record<string, string>> = {};
    map["inspection_standard"] = {};
    for (const opt of inspOptions) map["inspection_standard"][opt.label] = opt.code;
    // 품목명→품목코드
    map["item_name"] = {};
    for (const m of excelItemProcessMappings) map["item_name"][m.itemName] = m.itemCode;
    // 적용공정 이름→코드 (전체 품목 공정에서)
    map["apply_process"] = {};
    for (const m of excelItemProcessMappings) {
      for (const p of m.processes) map["apply_process"][p.name] = p.code;
    }
    return map;
  }, [inspOptions, excelItemProcessMappings]);

  // 엑셀 업로드 저장 (다건)
  const handleExcelUpload = async (data: ParsedSheetData[]) => {
    // 품목코드별로 그룹핑
    const itemCodeSet = new Set<string>();
    const rows: any[] = [];

    for (const sheet of data) {
      for (const row of sheet.rows) {
        // 품목코드: 수식 결과 또는 품목명으로 역매핑
        let itemCode = row.item_code || "";
        const itemName = row.item_name || "";
        if (!itemCode && itemName) {
          const mapping = excelItemProcessMappings.find(m => m.itemName === itemName);
          if (mapping) itemCode = mapping.itemCode;
        }
        if (!itemCode) continue;
        itemCodeSet.add(itemCode);

        const inspLabel = row.inspection_standard || "";
        const inspId = excelLabelToCodeMap["inspection_standard"]?.[inspLabel] || inspLabel;
        const inspOpt = inspOptions.find(o => o.code === inspId);
        const itemMapping = excelItemProcessMappings.find(m => m.itemCode === itemCode);

        let passCriteria = "";
        const jcLabel = inspOpt ? (judgmentCatOptions.find(c => c.code === inspOpt.judgment_criteria)?.label || inspOpt.judgment_criteria) : "";
        if (jcLabel === "수치(범위)") {
          passCriteria = `${row.standard_value || ""}|${row.tolerance || ""}`;
        } else {
          passCriteria = row.acceptance_criteria || "";
        }

        // 적용공정 검증: 해당 품목의 유효 공정인지 확인 (품목 변경 후 공정 미초기화 대응)
        let applyProcess = row.apply_process || "";
        if (applyProcess && itemMapping) {
          const validProcess = itemMapping.processes.find(p => p.code === applyProcess || p.name === applyProcess);
          if (!validProcess) {
            applyProcess = ""; // 유효하지 않은 공정은 비움
          }
        }

        rows.push({
          id: crypto.randomUUID(),
          item_code: itemCode,
          item_name: itemMapping?.itemName || itemCode,
          inspection_type: sheet.typeKey || sheet.sheetName,
          inspection_standard_id: inspId,
          inspection_item_name: inspOpt?.detail || row.inspection_detail || "",
          inspection_method: inspOpt?.method || "",
          apply_process: applyProcess,
          pass_criteria: passCriteria,
          is_required: row.is_required === "Y" ? "true" : "false",
          is_active: "사용",
        });
      }
    }

    // 해당 품목들의 기존 데이터 삭제 후 재등록
    for (const itemCode of itemCodeSet) {
      const existRes = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
        page: 1, size: 9999,
        dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: itemCode }] },
        autoFilter: true,
      });
      const existing = existRes.data?.data?.data || existRes.data?.data?.rows || [];
      if (existing.length > 0) {
        await apiClient.delete(`/table-management/tables/${TABLE_NAME}/delete`, { data: existing.map((r: any) => ({ id: r.id })) });
      }
    }

    for (const row of rows) {
      await apiClient.post(`/table-management/tables/${TABLE_NAME}/add`, row);
    }
    fetchData();
  };

  /* ═══════════════════ JSX ═══════════════════ */
  return (
    <div className="flex h-full flex-col">
      {ConfirmDialogComponent}

      {/* 검색 필터 */}
      <div className="shrink-0 px-3 pt-3 pb-2">
        <DynamicSearchFilter
          tableName={TABLE_NAME}
          filterId="c16-item-inspection"
          onFilterChange={setSearchFilters}
          externalFilterConfig={ts.filterConfig}
          dataCount={totalCount}
        />
      </div>

      {/* 좌우 분할 패널 */}
      <div className="flex-1 min-h-0 px-3 pb-3">
        <ResizablePanelGroup direction="horizontal" className="h-full rounded-lg border">
          {/* ═══════ 좌측: 품목 목록 ═══════ */}
          <ResizablePanel defaultSize={50} minSize={30}>
            <div className="flex h-full flex-col">
              <div className="shrink-0 flex items-center justify-between px-4 py-3 border-b bg-muted/30">
                <div className="flex items-center gap-2">
                  <ClipboardList className="h-4 w-4 text-muted-foreground" />
                  <span className="text-sm font-semibold">품목 목록</span>
                  <Badge variant="secondary" className="text-[10px]">{groupedData.length}건</Badge>
                </div>
                <div className="flex items-center gap-1.5">
                  <Button size="sm" className="h-7 text-xs" onClick={openCreate}><Plus className="w-3.5 h-3.5 mr-1" />등록</Button>
                  <Button size="sm" variant="outline" className="h-7 text-xs" onClick={openExcelUpload}>
                    <FileSpreadsheet className="w-3.5 h-3.5 mr-1" />엑셀 업로드
                  </Button>
                  <Button size="sm" variant="destructive" className="h-7 text-xs" onClick={handleDelete}><Trash2 className="w-3.5 h-3.5 mr-1" />삭제</Button>
                  <Button variant="ghost" size="sm" className="h-7 w-7 p-0" onClick={() => ts.setOpen(true)}><Settings2 className="h-3.5 w-3.5" /></Button>
                </div>
              </div>
              <div className="flex-1 min-h-0 overflow-auto">
                {loading ? (
                  <div className="flex items-center justify-center py-16"><Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /></div>
                ) : groupedData.length === 0 ? (
                  <div className="flex flex-col items-center justify-center py-16 text-muted-foreground">
                    <Inbox className="h-10 w-10 mb-2 opacity-30" />
                    <p className="text-sm">등록된 품목검사정보가 없어요</p>
                  </div>
                ) : (
                  <Table>
                    <TableHeader className="sticky top-0 z-10">
                      <TableRow className="bg-muted hover:bg-muted">
                        {ts.visibleColumns.map((col) => (
                          <TableHead
                            key={col.key}
                            className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground cursor-pointer select-none hover:bg-muted/70"
                            style={ts.thStyle(col.key)}
                            onClick={() => handleSort(col.key)}
                          >
                            <div className="flex items-center gap-1">
                              <span>{col.label}</span>
                              {sortConfig?.key === col.key ? (
                                sortConfig.direction === "asc"
                                  ? <ChevronUp className="h-3 w-3" />
                                  : <ChevronDown className="h-3 w-3" />
                              ) : (
                                <ChevronsUpDown className="h-3 w-3 opacity-30" />
                              )}
                            </div>
                          </TableHead>
                        ))}
                      </TableRow>
                    </TableHeader>
                    <TableBody>
                      {sortedGroupedData.map((group) => (
                        <TableRow
                          key={group.item_code}
                          className={cn("cursor-pointer", selectedItemCode === group.item_code ? "bg-primary/10 border-l-2 border-l-primary" : "hover:bg-muted/50")}
                          onClick={() => {
                            setSelectedItemCode(group.item_code);
                            setSelectedTypeTab(group.types[0] || "");
                          }}
                        >
                          {ts.visibleColumns.map((col) => {
                            switch (col.key) {
                              case "item_code": return <TableCell key={col.key} className="text-sm font-medium text-primary">{group.item_code}</TableCell>;
                              case "item_name": return <TableCell key={col.key} className="text-sm">{group.item_name}</TableCell>;
                              case "size": return <TableCell key={col.key} className="text-sm">{group.size}</TableCell>;
                              case "inspection_type": return (
                                <TableCell key={col.key}>
                                  <div className="flex items-center gap-1 flex-nowrap overflow-hidden">
                                    {group.types.map((t: string) => {
                                      const label = inspTypeCatOptions.find((o) => o.code === t)?.label || t;
                                      return <Badge key={t} variant="secondary" className="text-[10px] whitespace-nowrap">{label}</Badge>;
                                    })}
                                  </div>
                                </TableCell>
                              );
                              case "is_active": return (
                                <TableCell key={col.key}>
                                  <Badge variant={group.is_active === "사용" || group.is_active === "true" ? "default" : "secondary"} className="text-[10px]">
                                    {group.is_active === "사용" || group.is_active === "true" ? "사용" : "미사용"}
                                  </Badge>
                                </TableCell>
                              );
                              default: return <TableCell key={col.key}>{(group as any)[col.key] ?? ""}</TableCell>;
                            }
                          })}
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                )}
              </div>
              <div className="shrink-0 flex items-center justify-between gap-2 px-4 py-2 border-t bg-muted/20">
                <div className="text-xs text-muted-foreground">
                  전체 {totalCount.toLocaleString()}건 (품목 기준)
                </div>
                <div className="flex items-center gap-1">
                  <Button variant="ghost" size="sm" className="h-7 w-7 p-0" disabled={page <= 1 || loading} onClick={() => setPage(1)}>
                    <ChevronsLeft className="h-3.5 w-3.5" />
                  </Button>
                  <Button variant="ghost" size="sm" className="h-7 w-7 p-0" disabled={page <= 1 || loading} onClick={() => setPage((p) => Math.max(1, p - 1))}>
                    <ChevronLeft className="h-3.5 w-3.5" />
                  </Button>
                  <span className="text-xs tabular-nums text-muted-foreground px-2">
                    {page} / {totalPages}
                  </span>
                  <Button variant="ghost" size="sm" className="h-7 w-7 p-0" disabled={page >= totalPages || loading} onClick={() => setPage((p) => Math.min(totalPages, p + 1))}>
                    <ChevronRight className="h-3.5 w-3.5" />
                  </Button>
                  <Button variant="ghost" size="sm" className="h-7 w-7 p-0" disabled={page >= totalPages || loading} onClick={() => setPage(totalPages)}>
                    <ChevronsRight className="h-3.5 w-3.5" />
                  </Button>
                </div>
              </div>
            </div>
          </ResizablePanel>

          <ResizableHandle withHandle />

          {/* ═══════ 우측: 검사유형별 검사항목 ═══════ */}
          <ResizablePanel defaultSize={50} minSize={30}>
            <div className="flex h-full flex-col">
              <div className="shrink-0 flex items-center justify-between px-4 py-3 border-b bg-muted/30">
                <div className="flex items-center gap-2">
                  <span className="text-sm font-semibold">검사유형별 검사항목</span>
                </div>
                {selectedGroup && (
                  <div className="flex items-center gap-2">
                    <Button size="sm" variant="outline" className="h-7 text-xs" onClick={() => openEdit()}><Pencil className="w-3.5 h-3.5 mr-1" />수정</Button>
                    <Button size="sm" variant="outline" className="h-7 text-xs" onClick={openCopyModal}><Copy className="w-3.5 h-3.5 mr-1" />복사</Button>
                    <Button size="sm" variant="destructive" className="h-7 text-xs" onClick={handleDelete}><Trash2 className="w-3.5 h-3.5 mr-1" />삭제</Button>
                  </div>
                )}
              </div>

              {!selectedGroup ? (
                <div className="flex-1 flex items-center justify-center text-muted-foreground">
                  <div className="text-center space-y-2">
                    <ClipboardList className="h-8 w-8 mx-auto opacity-30" />
                    <p className="text-sm">좌측에서 품목을 선택해주세요</p>
                  </div>
                </div>
              ) : (
                <div className="flex-1 min-h-0 flex flex-col">
                  {/* 검사유형 탭 */}
                  <div className="shrink-0 flex items-center gap-2 px-4 py-3">
                    {selectedGroup.types.map((type: string) => {
                      const count = selectedGroup.rows.filter((r: any) => r.inspection_type === type && r.inspection_standard_id).length;
                      return (
                        <button
                          key={type}
                          type="button"
                          onClick={() => setSelectedTypeTab(type)}
                          className={cn(
                            "flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors border",
                            selectedTypeTab === type
                              ? "bg-primary text-primary-foreground border-primary"
                              : "bg-muted/50 text-muted-foreground border-border hover:bg-muted"
                          )}
                        >
                          {inspTypeCatOptions.find((o) => o.code === type)?.label || type}
                          <span className={cn(
                            "inline-flex items-center justify-center h-4 min-w-[16px] rounded-full text-[10px] font-bold px-1",
                            selectedTypeTab === type ? "bg-primary-foreground/20 text-primary-foreground" : "bg-muted-foreground/20 text-muted-foreground"
                          )}>
                            {count}
                          </span>
                        </button>
                      );
                    })}
                  </div>

                  {/* 검사항목 상세 테이블 */}
                  <div className="flex-1 min-h-0 overflow-auto px-4 pb-4">
                    {selectedTypeTab && (
                      <div className="space-y-2">
                        <div className="flex items-center gap-2">
                          <Badge variant="default" className="text-xs">{selectedTypeTab}</Badge>
                          <span className="text-sm font-medium">검사항목 상세</span>
                        </div>
                        <div className="border rounded-lg overflow-hidden">
                          <Table>
                            <TableHeader>
                              <TableRow className="bg-muted/50 hover:bg-muted/50">
                                <TableHead className="text-[10px] font-bold h-8">검사항목</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">검사기준</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">검사방법</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">적용공정</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">구분</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">판단기준</TableHead>
                                <TableHead className="text-[10px] font-bold h-8">합격기준</TableHead>
                                <TableHead className="text-[10px] font-bold h-8 w-[50px]">필수</TableHead>
                                <TableHead className="text-[10px] font-bold h-8 w-[70px]">단위</TableHead>
                              </TableRow>
                            </TableHeader>
                            <TableBody>
                              {selectedTabRows.length === 0 ? (
                                <TableRow>
                                  <TableCell colSpan={9} className="text-center py-8 text-xs text-muted-foreground">등록된 검사항목이 없어요</TableCell>
                                </TableRow>
                              ) : selectedTabRows.map((row: any) => (
                                <TableRow key={row.id}>
                                  <TableCell className="text-xs py-2">{row.inspection_item_name || "-"}</TableCell>
                                  <TableCell className="text-xs py-2">{resolveInspLabel(row.inspection_standard_id)}</TableCell>
                                  <TableCell className="text-xs py-2">{resolveMethodLabel(row.inspection_method)}</TableCell>
                                  <TableCell className="text-xs py-2">{(() => {
                                    const code = row.apply_process;
                                    if (!code) return "-";
                                    // excelItemProcessMappings에서 공정명 찾기
                                    for (const m of excelItemProcessMappings) {
                                      const proc = m.processes.find(p => p.code === code);
                                      if (proc) return proc.name;
                                    }
                                    // processOptions (모달용)에서 찾기
                                    const proc = processOptions.find(p => p.code === code);
                                    return proc?.name || code;
                                  })()}</TableCell>
                                  <TableCell className="text-xs py-2">{row.classification || "-"}</TableCell>
                                  <TableCell className="text-xs py-2">
                                    {(() => {
                                      const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
                                      const jcCode = insp?.judgment_criteria || "";
                                      const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
                                      return jcLabel ? <Badge variant="outline" className="text-[10px]">{jcLabel}</Badge> : "-";
                                    })()}
                                  </TableCell>
                                  <TableCell className="text-xs py-2 font-mono">{(() => {
                                    const pc = row.pass_criteria;
                                    if (!pc) return "-";
                                    if (pc.includes("|")) {
                                      const [s, t] = pc.split("|");
                                      if (!t || !t.trim()) return s || "-";
                                      return `${s} ± ${t}`;
                                    }
                                    return pc;
                                  })()}</TableCell>
                                  <TableCell className="text-xs py-2 text-center">
                                    {row.is_required === "true" || row.is_required === true ? (
                                      <Badge variant="destructive" className="text-[9px]">필수</Badge>
                                    ) : "-"}
                                  </TableCell>
                                  <TableCell className="text-xs py-2">
                                    {(() => {
                                      const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
                                      const unitCode = insp?.unit || "";
                                      const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
                                      return unitLabel || "-";
                                    })()}
                                  </TableCell>
                                </TableRow>
                              ))}
                            </TableBody>
                          </Table>
                        </div>
                      </div>
                    )}
                  </div>
                </div>
              )}
            </div>
          </ResizablePanel>
        </ResizablePanelGroup>
      </div>

      {/* ═══════════════════ 등록/수정 모달 ═══════════════════ */}
      <Dialog open={modalOpen} onOpenChange={(open) => { if (!open) setItemModalOpen(false); setModalOpen(open); }}>
        <DialogContent className={cn("max-w-[95vw] max-h-[85vh] flex flex-col overflow-hidden", itemModalOpen ? "sm:max-w-3xl" : "sm:max-w-[1400px]")}>
          {itemModalOpen ? (
            <>
              <DialogHeader>
                <DialogTitle>품목 선택</DialogTitle>
                <DialogDescription>품목코드 또는 품목명으로 검색</DialogDescription>
              </DialogHeader>
              <div className="flex gap-2 shrink-0">
                <Input className="h-9 flex-1" placeholder="품목코드 또는 품목명" value={itemSearchKeyword} onChange={(e) => setItemSearchKeyword(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleItemSearch(); }} />
                <Button size="sm" className="h-9" onClick={handleItemSearch} disabled={itemSearchLoading}>
                  {itemSearchLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <><Search className="w-4 h-4 mr-1" />검색</>}
                </Button>
              </div>
              <div className="flex-1 border rounded-lg overflow-auto">
                <Table>
                  <TableHeader className="sticky top-0 z-10">
                    <TableRow className="bg-muted hover:bg-muted">
                      <TableHead className="text-[11px] font-bold w-[140px]">품목코드</TableHead>
                      <TableHead className="text-[11px] font-bold">품목명</TableHead>
                      <TableHead className="text-[11px] font-bold w-[120px]">규격</TableHead>
                      <TableHead className="text-[11px] font-bold w-[100px]">품목유형</TableHead>
                      <TableHead className="text-[11px] font-bold w-[80px]">단위</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {filteredItems.length === 0 ? (
                      <TableRow><TableCell colSpan={5} className="text-center py-8 text-muted-foreground text-sm">{itemSearchLoading ? "검색 중..." : "검색 결과가 없어요"}</TableCell></TableRow>
                    ) : filteredItems.map((item) => (
                      <TableRow key={item.code} className="cursor-pointer hover:bg-primary/5" onClick={() => selectItem(item)}>
                        <TableCell className="text-sm font-mono">{item.code}</TableCell>
                        <TableCell className="text-sm">{item.name}</TableCell>
                        <TableCell className="text-sm truncate max-w-[120px]" title={item.size}>{item.size}</TableCell>
                        <TableCell className="text-sm">{item.item_type}</TableCell>
                        <TableCell className="text-sm">{item.unit}</TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </div>
              {/* 페이지네이션 (EDataTable 스타일) */}
              <div className="shrink-0 flex items-center justify-between border-t px-2 py-2 text-xs text-muted-foreground">
                <span>전체 <span className="font-medium text-foreground">{itemTotal.toLocaleString()}</span>건</span>
                <div className="flex items-center gap-0.5">
                  <button onClick={() => { setItemPage(1); searchItemServer(1); }} disabled={itemPage <= 1}
                    className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronsLeft className="h-3.5 w-3.5" /></button>
                  <button onClick={() => { const p = itemPage - 1; setItemPage(p); searchItemServer(p); }} disabled={itemPage <= 1}
                    className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronLeft className="h-3.5 w-3.5" /></button>
                  {Array.from({ length: Math.min(5, itemTotalPages) }, (_, i) => {
                    const start = Math.max(1, Math.min(itemPage - 2, itemTotalPages - 4));
                    const p = start + i;
                    if (p > itemTotalPages) return null;
                    return (
                      <button key={p} onClick={() => { setItemPage(p); searchItemServer(p); }}
                        className={cn("h-7 w-7 flex items-center justify-center rounded text-xs",
                          p === itemPage ? "bg-primary text-primary-foreground font-medium" : "hover:bg-muted")}>{p}</button>
                    );
                  })}
                  <button onClick={() => { const p = itemPage + 1; setItemPage(p); searchItemServer(p); }} disabled={itemPage >= itemTotalPages}
                    className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronRight className="h-3.5 w-3.5" /></button>
                  <button onClick={() => { setItemPage(itemTotalPages); searchItemServer(itemTotalPages); }} disabled={itemPage >= itemTotalPages}
                    className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronsRight className="h-3.5 w-3.5" /></button>
                </div>
              </div>
              <DialogFooter className="shrink-0"><Button variant="outline" onClick={() => setItemModalOpen(false)}>취소</Button></DialogFooter>
            </>
          ) : (
            <>
              <DialogHeader className="shrink-0">
                <DialogTitle>{editMode ? "품목검사정보 수정" : "품목검사정보 등록"}</DialogTitle>
                <DialogDescription className="sr-only">품목검사정보를 등록합니다</DialogDescription>
              </DialogHeader>
              <div className="flex-1 overflow-y-auto space-y-4 pr-1">
                {/* 품목 정보 */}
                <div className="space-y-3">
                  <h4 className="text-sm font-semibold">품목 정보</h4>
                  <div className="flex items-end gap-2">
                    <div className="space-y-1.5 flex-1">
                      <Label className="text-xs font-semibold text-muted-foreground">품목코드 <span className="text-destructive">*</span></Label>
                      <Input className="h-9 bg-muted" value={form.item_code || ""} readOnly placeholder="품목코드" />
                    </div>
                    <div className="space-y-1.5 flex-1">
                      <Label className="text-xs font-semibold text-muted-foreground">품목명</Label>
                      <Input className="h-9 bg-muted" value={form.item_name || ""} readOnly placeholder="품목명" />
                    </div>
                    <Button type="button" variant="outline" size="sm" className="h-9 px-3 shrink-0" onClick={openItemModal}>
                      <Search className="w-4 h-4 mr-1" />품목선택
                    </Button>
                  </div>
                  <div className="grid grid-cols-2 gap-3">
                    <div className="space-y-1.5">
                      <Label className="text-xs font-semibold text-muted-foreground">사용여부</Label>
                      <Select value={form.is_active === false || form.is_active === "N" ? "N" : "Y"} onValueChange={(v) => setForm(p => ({ ...p, is_active: v === "Y" ? "사용" : "미사용" }))}>
                        <SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          <SelectItem value="Y">사용</SelectItem>
                          <SelectItem value="N">미사용</SelectItem>
                        </SelectContent>
                      </Select>
                    </div>
                    <div className="space-y-1.5">
                      <Label className="text-xs font-semibold text-muted-foreground">관리자</Label>
                      <Select value={form.manager || ""} onValueChange={(v) => setForm(p => ({ ...p, manager: v }))}>
                        <SelectTrigger className="h-9"><SelectValue placeholder="관리자 선택" /></SelectTrigger>
                        <SelectContent>{userOptions.map(o => <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>)}</SelectContent>
                      </Select>
                    </div>
                  </div>
                </div>

                {/* 검사유형 선택 */}
                <div className="space-y-3">
                  <h4 className="text-sm font-semibold">검사유형 선택</h4>
                  <div className="flex flex-wrap gap-4">
                    {INSPECTION_TYPES.map(({ key, label }) => (
                      <div key={key} className="flex items-center gap-1.5">
                        <Checkbox checked={!!form[key]} onCheckedChange={(v) => setForm(p => ({ ...p, [key]: !!v }))} />
                        <Label className="text-sm cursor-pointer">{label}</Label>
                      </div>
                    ))}
                  </div>
                </div>

                {/* 검사유형별 검사항목 설정 */}
                {INSPECTION_TYPES.filter(t => !!form[t.key]).map(({ key, label }) => (
                  <div key={key} className="space-y-2">
                    <button type="button" className="w-full flex items-center gap-2 py-2 px-3 rounded-md border bg-muted/50 hover:bg-muted text-left" onClick={() => toggleCollapse(key)}>
                      <Badge variant="default" className="text-xs">{label}</Badge>
                      <span className="text-sm font-medium">검사항목 설정</span>
                      <span className="text-xs text-muted-foreground ml-auto">{(inspectionRows[key] || []).length}개</span>
                    </button>
                    {!collapsedTypes[key] && (
                      <div className="space-y-2 pl-1">
                        <div className="flex items-center justify-between">
                          <span className="text-xs font-semibold text-muted-foreground">검사항목 목록</span>
                          <Button type="button" size="sm" variant="outline" className="h-7 text-xs" onClick={() => addInspRow(key)}>
                            <Plus className="w-3 h-3 mr-1" />항목추가
                          </Button>
                        </div>
                        <div className="border rounded-lg overflow-hidden">
                          <Table>
                            <TableHeader>
                              <TableRow className="bg-muted/50 hover:bg-muted/50">
                                <TableHead className="text-[10px] font-bold w-[170px]">검사기준 선택</TableHead>
                                <TableHead className="text-[10px] font-bold w-[130px]">검사기준 상세</TableHead>
                                <TableHead className="text-[10px] font-bold w-[90px]">검사방법</TableHead>
                                <TableHead className="text-[10px] font-bold w-[90px]">적용공정</TableHead>
                                <TableHead className="text-[10px] font-bold w-[100px]">구분</TableHead>
                                <TableHead className="text-[10px] font-bold w-[80px]">판단기준</TableHead>
                                <TableHead className="text-[10px] font-bold w-[200px]">합격기준 (판단기준별)</TableHead>
                                <TableHead className="text-[10px] font-bold w-[40px]">필수</TableHead>
                                <TableHead className="text-[10px] font-bold w-[70px]">단위</TableHead>
                                <TableHead className="text-[10px] font-bold w-[36px]" />
                              </TableRow>
                            </TableHeader>
                            <TableBody>
                              {(!inspectionRows[key] || inspectionRows[key].length === 0) ? (
                                <TableRow><TableCell colSpan={10} className="text-center py-4 text-xs text-muted-foreground">항목추가 버튼으로 검사항목을 추가하세요</TableCell></TableRow>
                              ) : inspectionRows[key].map((row) => (
                                <TableRow key={row.id}>
                                  <TableCell className="p-1">
                                    <Select value={row.inspection_standard_id || ""} onValueChange={(v) => updateInspRow(key, row.id, "inspection_standard_id", v)}>
                                      <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="검사기준 선택" /></SelectTrigger>
                                      <SelectContent>{getFilteredInspOptions(key).map(o => <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>)}</SelectContent>
                                    </Select>
                                  </TableCell>
                                  <TableCell className="p-1"><Input className="h-8 text-xs bg-muted" value={row.inspection_detail} readOnly placeholder="자동" /></TableCell>
                                  <TableCell className="p-1"><Input className="h-8 text-xs bg-muted" value={row.inspection_method} readOnly placeholder="자동" /></TableCell>
                                  <TableCell className="p-1">
                                    {processOptions.length > 0 ? (
                                      <Select value={row.apply_process || ""} onValueChange={(v) => updateInspRow(key, row.id, "apply_process", v)}>
                                        <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="공정 선택" /></SelectTrigger>
                                        <SelectContent>
                                          {processOptions.map((p) => (
                                            <SelectItem key={p.code} value={p.code} className="text-xs">{p.name}</SelectItem>
                                          ))}
                                        </SelectContent>
                                      </Select>
                                    ) : (
                                      <Input className="h-8 text-xs" value={row.apply_process} onChange={(e) => updateInspRow(key, row.id, "apply_process", e.target.value)} placeholder="공정" />
                                    )}
                                  </TableCell>
                                  <TableCell className="p-1">
                                    <Input className="h-8 text-xs" value={row.classification || ""} onChange={(e) => updateInspRow(key, row.id, "classification", e.target.value)} placeholder="구분 입력" />
                                  </TableCell>
                                  <TableCell className="p-1 text-center">
                                    {row.judgment_criteria ? <Badge variant="outline" className="text-[10px]">{row.judgment_criteria}</Badge> : <span className="text-[10px] text-muted-foreground">-</span>}
                                  </TableCell>
                                  <TableCell className="p-1">
                                    {row.judgment_criteria === "선택형" && row.selection_options ? (
                                      <Select value={row.acceptance_criteria || ""} onValueChange={(v) => updateInspRow(key, row.id, "acceptance_criteria", v)} disabled={!row.inspection_standard_id}>
                                        <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="선택" /></SelectTrigger>
                                        <SelectContent>
                                          {row.selection_options.split(",").filter(Boolean).map((opt) => (
                                            <SelectItem key={opt} value={opt} className="text-xs">{opt}</SelectItem>
                                          ))}
                                        </SelectContent>
                                      </Select>
                                    ) : row.judgment_criteria === "O/X" ? (
                                      <Select value={row.acceptance_criteria || ""} onValueChange={(v) => updateInspRow(key, row.id, "acceptance_criteria", v)} disabled={!row.inspection_standard_id}>
                                        <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="선택" /></SelectTrigger>
                                        <SelectContent>
                                          <SelectItem value="O" className="text-xs">O (합격)</SelectItem>
                                          <SelectItem value="X" className="text-xs">X (불합격)</SelectItem>
                                        </SelectContent>
                                      </Select>
                                    ) : row.judgment_criteria === "수치(범위)" ? (
                                      <div className="flex items-center gap-1">
                                        <Input className="h-8 text-xs w-16" value={row.acceptance_criteria?.split("|")[0] || ""} onChange={(e) => {
                                          const parts = (row.acceptance_criteria || "||").split("|");
                                          parts[0] = e.target.value;
                                          updateInspRow(key, row.id, "acceptance_criteria", parts.join("|"));
                                        }} placeholder="기준값" disabled={!row.inspection_standard_id} />
                                        <span className="text-[10px] text-muted-foreground">±</span>
                                        <Input className="h-8 text-xs w-12" value={row.acceptance_criteria?.split("|")[1] || ""} onChange={(e) => {
                                          const parts = (row.acceptance_criteria || "||").split("|");
                                          parts[1] = e.target.value;
                                          updateInspRow(key, row.id, "acceptance_criteria", parts.join("|"));
                                        }} placeholder="오차" disabled={!row.inspection_standard_id} />
                                        {row.unit && <span className="text-[10px] text-muted-foreground shrink-0">{row.unit}</span>}
                                      </div>
                                    ) : (
                                      <Input className="h-8 text-xs" value={row.acceptance_criteria} onChange={(e) => updateInspRow(key, row.id, "acceptance_criteria", e.target.value)} placeholder="합격기준 텍스트 입력" disabled={!row.inspection_standard_id} />
                                    )}
                                  </TableCell>
                                  <TableCell className="p-1 text-center"><Checkbox checked={row.is_required} onCheckedChange={(v) => updateInspRow(key, row.id, "is_required", !!v)} /></TableCell>
                                  <TableCell className="p-1 text-xs text-muted-foreground">{row.unit || "-"}</TableCell>
                                  <TableCell className="p-1">
                                    <Button type="button" variant="destructive" size="sm" className="h-7 w-7 p-0" onClick={() => removeInspRow(key, row.id)}><Trash2 className="w-3.5 h-3.5" /></Button>
                                  </TableCell>
                                </TableRow>
                              ))}
                            </TableBody>
                          </Table>
                        </div>
                      </div>
                    )}
                  </div>
                ))}
              </div>
              <DialogFooter className="shrink-0">
                <Button variant="outline" onClick={() => setModalOpen(false)}>취소</Button>
                <Button onClick={handleSave} disabled={saving}>
                  {saving ? <Loader2 className="w-4 h-4 animate-spin mr-1" /> : <Save className="w-4 h-4 mr-1" />}저장
                </Button>
              </DialogFooter>
            </>
          )}
        </DialogContent>
      </Dialog>

      {/* ═══════════════════ 복사 모달 (2단 분할: 좌 대상 / 우 편집) ═══════════════════ */}
      <Dialog open={copyModalOpen} onOpenChange={(v) => { if (!copying) setCopyModalOpen(v); }}>
        <DialogContent
          className="max-w-[95vw] sm:max-w-[1400px] w-[95vw] max-h-[90vh] flex flex-col overflow-hidden"
          onPointerDownOutside={(e) => e.preventDefault()}
          onInteractOutside={(e) => e.preventDefault()}
          onEscapeKeyDown={(e) => { if (copying) e.preventDefault(); }}
        >
          <DialogHeader className="shrink-0">
            <DialogTitle>{copying ? "검사정보 복사 중..." : "검사정보 복사"}</DialogTitle>
            <DialogDescription>
              <span className="font-medium text-foreground">{selectedGroup?.item_name || "-"}</span>
              <span className="text-muted-foreground"> ({selectedItemCode})</span>
              <span>{copying ? " 의 검사정보를 복사하고 있습니다" : " 의 검사정보를 편집해서 선택한 품목들에 복사합니다. 기준 품목은 변경되지 않아요"}</span>
            </DialogDescription>
          </DialogHeader>
          {copying ? (
            <div className="flex-1 flex flex-col items-center justify-center gap-4 py-8 px-4">
              <div className="w-full max-w-sm space-y-3">
                <div className="flex items-center gap-2">
                  <Loader2 className="h-4 w-4 animate-spin text-blue-600" />
                  <span className="text-sm font-medium text-blue-700">복사 진행 중...</span>
                  <span className="text-xs text-blue-600 ml-auto">
                    {copyProgress.current.toLocaleString()} / {copyProgress.total.toLocaleString()}
                  </span>
                </div>
                <div className="w-full bg-blue-100 rounded-full h-2">
                  <div
                    className="bg-blue-600 h-2 rounded-full transition-all duration-300"
                    style={{ width: `${copyProgress.total > 0 ? Math.round((copyProgress.current / copyProgress.total) * 100) : 0}%` }}
                  />
                </div>
                <p className="text-xs text-muted-foreground text-center pt-2">
                  모달을 닫지 마세요. 완료 후 자동으로 닫혀요.
                </p>
              </div>
            </div>
          ) : (
            <div className="flex-1 grid grid-cols-[420px_1fr] gap-4 overflow-hidden">
              {/* 좌측: 복사 대상 품목 선택 */}
              <div className="flex flex-col overflow-hidden border rounded-lg">
                <div className="flex items-center justify-between border-b bg-muted/50 px-3 py-2">
                  <span className="text-xs font-semibold">복사 대상 품목 선택</span>
                  {copyCheckedIds.length > 0 && <span className="text-[10px] text-primary">선택 {copyCheckedIds.length}건</span>}
                </div>
                <div className="flex gap-2 px-2 pt-2">
                  <Input className="h-8 flex-1 text-xs" placeholder="품목코드 또는 품목명" value={copySearchKeyword}
                    onChange={(e) => setCopySearchKeyword(e.target.value)}
                    onKeyDown={(e) => { if (e.key === "Enter") handleCopySearch(); }} />
                  <Button size="sm" className="h-8 text-xs" onClick={handleCopySearch} disabled={copySearchLoading}>
                    {copySearchLoading ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Search className="w-3.5 h-3.5" />}
                  </Button>
                </div>
                <div className="flex-1 overflow-auto mt-2">
                  <Table>
                    <TableHeader className="sticky top-0 z-10 bg-background">
                      <TableRow className="bg-muted hover:bg-muted">
                        <TableHead className="w-[36px] text-center text-[10px]">
                          <Checkbox
                            checked={copyFilteredItems.length > 0 && copyFilteredItems.every(i => copyCheckedIds.includes(i.code))}
                            onCheckedChange={(v) => {
                              if (v) setCopyCheckedIds(prev => Array.from(new Set([...prev, ...copyFilteredItems.map(i => i.code)])));
                              else setCopyCheckedIds(prev => prev.filter(c => !copyFilteredItems.some(i => i.code === c)));
                            }}
                          />
                        </TableHead>
                        <TableHead className="text-[10px] font-bold w-[120px]">품목코드</TableHead>
                        <TableHead className="text-[10px] font-bold">품목명</TableHead>
                      </TableRow>
                    </TableHeader>
                    <TableBody>
                      {copyFilteredItems.length === 0 ? (
                        <TableRow><TableCell colSpan={3} className="text-center py-6 text-muted-foreground text-xs">
                          {copySearchLoading ? "검색 중..." : "검색 결과가 없어요"}
                        </TableCell></TableRow>
                      ) : copyFilteredItems.map((item) => (
                        <TableRow key={item.code} className="cursor-pointer hover:bg-primary/5"
                          onClick={() => toggleCopyChecked(item.code)}>
                          <TableCell className="text-center p-1" onClick={(e) => e.stopPropagation()}>
                            <Checkbox checked={copyCheckedIds.includes(item.code)} onCheckedChange={() => toggleCopyChecked(item.code)} />
                          </TableCell>
                          <TableCell className="text-xs font-mono p-1">{item.code}</TableCell>
                          <TableCell className="text-xs p-1 truncate">{item.name}</TableCell>
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                </div>
                <div className="border-t flex items-center justify-between px-2 py-1 text-[10px] text-muted-foreground">
                  <span>전체 <span className="font-medium text-foreground">{copyTotal.toLocaleString()}</span>건</span>
                  <div className="flex items-center gap-0.5">
                    <button onClick={() => { setCopyPage(1); searchCopyTargets(1); }} disabled={copyPage <= 1}
                      className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronsLeft className="h-3 w-3" /></button>
                    <button onClick={() => { const p = copyPage - 1; setCopyPage(p); searchCopyTargets(p); }} disabled={copyPage <= 1}
                      className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronLeft className="h-3 w-3" /></button>
                    <span className="text-[10px] mx-1">{copyPage}/{copyTotalPages}</span>
                    <button onClick={() => { const p = copyPage + 1; setCopyPage(p); searchCopyTargets(p); }} disabled={copyPage >= copyTotalPages}
                      className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronRight className="h-3 w-3" /></button>
                    <button onClick={() => { setCopyPage(copyTotalPages); searchCopyTargets(copyTotalPages); }} disabled={copyPage >= copyTotalPages}
                      className="h-6 w-6 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed"><ChevronsRight className="h-3 w-3" /></button>
                  </div>
                </div>
              </div>

              {/* 우측: 편집 폼 (등록/수정 폼과 동일 구조) */}
              <div className="flex flex-col overflow-hidden border rounded-lg">
                <div className="border-b bg-muted/50 px-3 py-2">
                  <span className="text-xs font-semibold">복사할 검사정보 편집 (기준: {selectedItemCode})</span>
                </div>
                <div className="flex-1 overflow-y-auto p-3 space-y-4">
                  <div className="grid grid-cols-2 gap-3">
                    <div className="space-y-1.5">
                      <Label className="text-xs font-semibold text-muted-foreground">사용여부</Label>
                      <Select value={copyForm.is_active === false || copyForm.is_active === "N" ? "N" : "Y"} onValueChange={(v) => setCopyForm(p => ({ ...p, is_active: v === "Y" ? "사용" : "미사용" }))}>
                        <SelectTrigger className="h-8 text-xs"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          <SelectItem value="Y">사용</SelectItem>
                          <SelectItem value="N">미사용</SelectItem>
                        </SelectContent>
                      </Select>
                    </div>
                    <div className="space-y-1.5">
                      <Label className="text-xs font-semibold text-muted-foreground">관리자</Label>
                      <Select value={copyForm.manager || ""} onValueChange={(v) => setCopyForm(p => ({ ...p, manager: v }))}>
                        <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="관리자 선택" /></SelectTrigger>
                        <SelectContent>{userOptions.map(o => <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>)}</SelectContent>
                      </Select>
                    </div>
                  </div>

                  <div className="space-y-2">
                    <h4 className="text-xs font-semibold">검사유형 선택</h4>
                    <div className="flex flex-wrap gap-3">
                      {INSPECTION_TYPES.map(({ key, label }) => (
                        <div key={key} className="flex items-center gap-1.5">
                          <Checkbox checked={!!copyForm[key]} onCheckedChange={(v) => setCopyForm(p => ({ ...p, [key]: !!v }))} />
                          <Label className="text-xs cursor-pointer">{label}</Label>
                        </div>
                      ))}
                    </div>
                  </div>

                  {INSPECTION_TYPES.filter(t => !!copyForm[t.key]).map(({ key, label }) => (
                    <div key={key} className="space-y-1.5">
                      <button type="button" className="w-full flex items-center gap-2 py-1.5 px-2 rounded-md border bg-muted/50 hover:bg-muted text-left" onClick={() => toggleCopyCollapse(key)}>
                        <Badge variant="default" className="text-[10px]">{label}</Badge>
                        <span className="text-xs font-medium">검사항목 설정</span>
                        <span className="text-[10px] text-muted-foreground ml-auto">{(copyInspectionRows[key] || []).length}개</span>
                      </button>
                      {!copyCollapsedTypes[key] && (
                        <div className="space-y-1.5 pl-1">
                          <div className="flex items-center justify-between">
                            <span className="text-[10px] font-semibold text-muted-foreground">검사항목 목록</span>
                            <Button type="button" size="sm" variant="outline" className="h-6 text-[10px]" onClick={() => addCopyInspRow(key)}>
                              <Plus className="w-3 h-3 mr-1" />항목추가
                            </Button>
                          </div>
                          <div className="border rounded-lg overflow-hidden">
                            <Table>
                              <TableHeader>
                                <TableRow className="bg-muted/50 hover:bg-muted/50">
                                  <TableHead className="text-[10px] font-bold w-[150px]">검사기준 선택</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[110px]">검사기준 상세</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[80px]">검사방법</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[80px]">적용공정</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[80px]">구분</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[70px]">판단기준</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[180px]">합격기준</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[36px]">필수</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[60px]">단위</TableHead>
                                  <TableHead className="text-[10px] font-bold w-[32px]" />
                                </TableRow>
                              </TableHeader>
                              <TableBody>
                                {(!copyInspectionRows[key] || copyInspectionRows[key].length === 0) ? (
                                  <TableRow><TableCell colSpan={10} className="text-center py-3 text-[10px] text-muted-foreground">항목추가 버튼으로 검사항목을 추가하세요</TableCell></TableRow>
                                ) : copyInspectionRows[key].map((row) => (
                                  <TableRow key={row.id}>
                                    <TableCell className="p-1">
                                      <Select value={row.inspection_standard_id || ""} onValueChange={(v) => updateCopyInspRow(key, row.id, "inspection_standard_id", v)}>
                                        <SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="검사기준" /></SelectTrigger>
                                        <SelectContent>{getFilteredInspOptions(key).map(o => <SelectItem key={o.code} value={o.code}>{o.label}</SelectItem>)}</SelectContent>
                                      </Select>
                                    </TableCell>
                                    <TableCell className="p-1"><Input className="h-7 text-[10px] bg-muted" value={row.inspection_detail} readOnly placeholder="자동" /></TableCell>
                                    <TableCell className="p-1"><Input className="h-7 text-[10px] bg-muted" value={row.inspection_method} readOnly placeholder="자동" /></TableCell>
                                    <TableCell className="p-1">
                                      {processOptions.length > 0 ? (
                                        <Select value={row.apply_process || ""} onValueChange={(v) => updateCopyInspRow(key, row.id, "apply_process", v)}>
                                          <SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="공정" /></SelectTrigger>
                                          <SelectContent>
                                            {processOptions.map((p) => (
                                              <SelectItem key={p.code} value={p.code} className="text-xs">{p.name}</SelectItem>
                                            ))}
                                          </SelectContent>
                                        </Select>
                                      ) : (
                                        <Input className="h-7 text-[10px]" value={row.apply_process} onChange={(e) => updateCopyInspRow(key, row.id, "apply_process", e.target.value)} placeholder="공정" />
                                      )}
                                    </TableCell>
                                    <TableCell className="p-1">
                                      <Input className="h-7 text-[10px]" value={row.classification || ""} onChange={(e) => updateCopyInspRow(key, row.id, "classification", e.target.value)} placeholder="구분" />
                                    </TableCell>
                                    <TableCell className="p-1 text-center">
                                      {row.judgment_criteria ? <Badge variant="outline" className="text-[9px]">{row.judgment_criteria}</Badge> : <span className="text-[9px] text-muted-foreground">-</span>}
                                    </TableCell>
                                    <TableCell className="p-1">
                                      {row.judgment_criteria === "선택형" && row.selection_options ? (
                                        <Select value={row.acceptance_criteria || ""} onValueChange={(v) => updateCopyInspRow(key, row.id, "acceptance_criteria", v)} disabled={!row.inspection_standard_id}>
                                          <SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="선택" /></SelectTrigger>
                                          <SelectContent>
                                            {row.selection_options.split(",").filter(Boolean).map((opt) => (
                                              <SelectItem key={opt} value={opt} className="text-xs">{opt}</SelectItem>
                                            ))}
                                          </SelectContent>
                                        </Select>
                                      ) : row.judgment_criteria === "O/X" ? (
                                        <Select value={row.acceptance_criteria || ""} onValueChange={(v) => updateCopyInspRow(key, row.id, "acceptance_criteria", v)} disabled={!row.inspection_standard_id}>
                                          <SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="선택" /></SelectTrigger>
                                          <SelectContent>
                                            <SelectItem value="O" className="text-xs">O (합격)</SelectItem>
                                            <SelectItem value="X" className="text-xs">X (불합격)</SelectItem>
                                          </SelectContent>
                                        </Select>
                                      ) : row.judgment_criteria === "수치(범위)" ? (
                                        <div className="flex items-center gap-1">
                                          <Input className="h-7 text-[10px] w-14" value={row.acceptance_criteria?.split("|")[0] || ""} onChange={(e) => {
                                            const parts = (row.acceptance_criteria || "||").split("|");
                                            parts[0] = e.target.value;
                                            updateCopyInspRow(key, row.id, "acceptance_criteria", parts.join("|"));
                                          }} placeholder="기준" disabled={!row.inspection_standard_id} />
                                          <span className="text-[9px] text-muted-foreground">±</span>
                                          <Input className="h-7 text-[10px] w-10" value={row.acceptance_criteria?.split("|")[1] || ""} onChange={(e) => {
                                            const parts = (row.acceptance_criteria || "||").split("|");
                                            parts[1] = e.target.value;
                                            updateCopyInspRow(key, row.id, "acceptance_criteria", parts.join("|"));
                                          }} placeholder="±" disabled={!row.inspection_standard_id} />
                                        </div>
                                      ) : (
                                        <Input className="h-7 text-[10px]" value={row.acceptance_criteria} onChange={(e) => updateCopyInspRow(key, row.id, "acceptance_criteria", e.target.value)} placeholder="합격기준" disabled={!row.inspection_standard_id} />
                                      )}
                                    </TableCell>
                                    <TableCell className="p-1 text-center"><Checkbox checked={row.is_required} onCheckedChange={(v) => updateCopyInspRow(key, row.id, "is_required", !!v)} /></TableCell>
                                    <TableCell className="p-1 text-[10px] text-muted-foreground">{row.unit || "-"}</TableCell>
                                    <TableCell className="p-1">
                                      <Button type="button" variant="destructive" size="sm" className="h-6 w-6 p-0" onClick={() => removeCopyInspRow(key, row.id)}><Trash2 className="w-3 h-3" /></Button>
                                    </TableCell>
                                  </TableRow>
                                ))}
                              </TableBody>
                            </Table>
                          </div>
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              </div>
            </div>
          )}
          <DialogFooter className="shrink-0">
            <Button variant="outline" onClick={() => setCopyModalOpen(false)} disabled={copying}>취소</Button>
            <Button onClick={handleCopy} disabled={copying || copyCheckedIds.length === 0}>
              {copying ? <Loader2 className="w-4 h-4 animate-spin mr-1" /> : <Copy className="w-4 h-4 mr-1" />}
              {copying
                ? `복사 중 (${copyProgress.current}/${copyProgress.total})`
                : copyCheckedIds.length > 0 ? `${copyCheckedIds.length}개 품목에 복사` : "복사"}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

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

      {/* ═══════ 엑셀 업로드 모달 ═══════ */}
      <SmartExcelUploadModal
        open={excelUploadOpen}
        onOpenChange={setExcelUploadOpen}
        config={excelUploadConfig}
        referenceData={excelReferenceData}
        dropdownOptions={excelDropdownOptions}
        itemProcessMappings={excelItemProcessMappings}
        labelToCodeMap={excelLabelToCodeMap}
        onUpload={handleExcelUpload}
        subtitle={`전체 ${excelItemProcessMappings.length}개 품목`}
        dataLoading={excelLoading}
        loadProgress={excelLoadProgress}
      />
    </div>
  );
}
