"use client";

import React, { useState, useRef, useEffect } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
import {
  Upload,
  FileSpreadsheet,
  AlertCircle,
  CheckCircle2,
  XCircle,
  ArrowRight,
  Zap,
  Copy,
  Loader2,
  Check,
  ChevronsUpDown,
} from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Checkbox } from "@/components/ui/checkbox";
import { importFromExcel, getExcelSheetNames } from "@/lib/utils/excelExport";
import { DynamicFormApi } from "@/lib/api/dynamicForm";
import { getTableSchema, TableColumn } from "@/lib/api/tableSchema";
import { cn } from "@/lib/utils";
import { findMappingByColumns, saveMappingTemplate } from "@/lib/api/excelMapping";
import { EditableSpreadsheet } from "./EditableSpreadsheet";
import { getCategoryValues } from "@/lib/api/tableCategoryValue";
import { getTableColumns } from "@/lib/api/tableManagement";

// 마스터-디테일 엑셀 업로드 설정 (버튼 설정에서 설정)
export interface MasterDetailExcelConfig {
  // 테이블 정보
  masterTable?: string;
  detailTable?: string;
  masterKeyColumn?: string;
  detailFkColumn?: string;
  // 채번
  numberingRuleId?: string;
  // 업로드 전 사용자가 선택할 마스터 테이블 필드
  masterSelectFields?: Array<{
    columnName: string;
    columnLabel: string;
    required: boolean;
    inputType: "entity" | "date" | "text" | "select";
    referenceTable?: string;
    referenceColumn?: string;
    displayColumn?: string;
  }>;
  // 엑셀에서 매핑할 디테일 테이블 필드
  detailExcelFields?: Array<{
    columnName: string;
    columnLabel: string;
    required: boolean;
  }>;
  masterDefaults?: Record<string, any>;
  detailDefaults?: Record<string, any>;
}

export interface ExcelUploadModalProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  tableName: string;
  uploadMode?: "insert" | "update" | "upsert";
  keyColumn?: string;
  onSuccess?: () => void;
  userId?: string;
  // 마스터-디테일 지원
  screenId?: number;
  isMasterDetail?: boolean;
  masterDetailRelation?: {
    masterTable: string;
    detailTable: string;
    masterKeyColumn: string;
    detailFkColumn: string;
    masterColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>;
    detailColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>;
  };
  // 마스터-디테일 엑셀 업로드 설정
  masterDetailExcelConfig?: MasterDetailExcelConfig;
  // 업로드 후 제어 실행 설정
  afterUploadFlows?: Array<{ flowId: string; order: number }>;
}

interface ColumnMapping {
  excelColumn: string;
  systemColumn: string | null;
  checkDuplicate?: boolean;
}

interface FlatCategoryValue {
  valueCode: string;
  valueLabel: string;
  depth: number;
  ancestors: string[];
}

function flattenCategoryValues(
  values: Array<{ valueCode: string; valueLabel: string; children?: any[] }>
): FlatCategoryValue[] {
  const result: FlatCategoryValue[] = [];
  const traverse = (items: any[], depth: number, ancestors: string[]) => {
    for (const item of items) {
      result.push({
        valueCode: item.valueCode,
        valueLabel: item.valueLabel,
        depth,
        ancestors,
      });
      if (item.children?.length > 0) {
        traverse(item.children, depth + 1, [...ancestors, item.valueLabel]);
      }
    }
  };
  traverse(values, 0, []);
  return result;
}

export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
  open,
  onOpenChange,
  tableName,
  uploadMode = "insert",
  keyColumn,
  onSuccess,
  userId = "guest",
  screenId,
  isMasterDetail = false,
  masterDetailRelation,
  masterDetailExcelConfig,
  // 업로드 후 제어 실행 설정
  afterUploadFlows,
}) => {
  const [currentStep, setCurrentStep] = useState(1);

  // 1단계: 파일 선택 & 미리보기
  const [file, setFile] = useState<File | null>(null);
  const [sheetNames, setSheetNames] = useState<string[]>([]);
  const [selectedSheet, setSelectedSheet] = useState<string>("");
  const [isDragOver, setIsDragOver] = useState(false);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [detectedRange, setDetectedRange] = useState<string>("");
  const [allData, setAllData] = useState<Record<string, any>[]>([]);
  const [displayData, setDisplayData] = useState<Record<string, any>[]>([]);

  // 2단계: 컬럼 매핑 + 매핑 템플릿 자동 적용
  const [isAutoMappingLoaded, setIsAutoMappingLoaded] = useState(false);
  const [excelColumns, setExcelColumns] = useState<string[]>([]);
  const [systemColumns, setSystemColumns] = useState<TableColumn[]>([]);
  const [columnMappings, setColumnMappings] = useState<ColumnMapping[]>([]);
  
  // 중복 처리 방법 (전역 설정)
  const [duplicateAction, setDuplicateAction] = useState<"overwrite" | "skip">("skip");

  // 검증 화면에서 DB 중복 처리 방법 (null이면 미선택 = 업로드 차단)
  const [dbDuplicateAction, setDbDuplicateAction] = useState<"overwrite" | "skip" | null>(null);

  // 엑셀 데이터 사전 검증 결과
  const [isDataValidating, setIsDataValidating] = useState(false);
  const [validationResult, setValidationResult] = useState<import("@/lib/api/tableManagement").ExcelValidationResult | null>(null);

  // 카테고리 검증 관련
  const [showCategoryValidation, setShowCategoryValidation] = useState(false);
  const [isCategoryValidating, setIsCategoryValidating] = useState(false);
  // { [columnName]: { invalidValue: string, replacement: string | null, validOptions: {code: string, label: string}[], rowIndices: number[] }[] }
  const [categoryMismatches, setCategoryMismatches] = useState<
    Record<string, Array<{
      invalidValue: string;
      replacement: string | null;
      validOptions: Array<{ code: string; label: string; depth: number; ancestors: string[] }>;
      rowIndices: number[];
    }>>
  >({});

  // 3단계: 확인
  const [isUploading, setIsUploading] = useState(false);

  // 🆕 마스터-디테일 모드: 마스터 필드 입력값
  const [masterFieldValues, setMasterFieldValues] = useState<Record<string, any>>({});
  const [entitySearchData, setEntitySearchData] = useState<Record<string, any[]>>({});
  const [entitySearchLoading, setEntitySearchLoading] = useState<Record<string, boolean>>({});
  const [entityDisplayColumns, setEntityDisplayColumns] = useState<Record<string, string>>({});

  // 🆕 엔티티 참조 데이터 로드
  useEffect(() => {
    console.log("🔍 엔티티 데이터 로드 체크:", {
      masterSelectFields: masterDetailExcelConfig?.masterSelectFields,
      open,
      isMasterDetail,
    });

    if (!masterDetailExcelConfig?.masterSelectFields) return;

    const loadEntityData = async () => {
      const { apiClient } = await import("@/lib/api/client");
      const { DynamicFormApi } = await import("@/lib/api/dynamicForm");

      for (const field of masterDetailExcelConfig.masterSelectFields!) {
        console.log("🔍 필드 처리:", field);
        
        if (field.inputType === "entity") {
          setEntitySearchLoading((prev) => ({ ...prev, [field.columnName]: true }));
          try {
            let refTable = field.referenceTable;
            console.log("🔍 초기 refTable:", refTable);

            let displayCol = field.displayColumn;

            // referenceTable 또는 displayColumn이 없으면 DB에서 동적으로 조회
            if ((!refTable || !displayCol) && masterDetailExcelConfig.masterTable) {
              console.log("🔍 DB에서 referenceTable/displayColumn 조회 시도:", masterDetailExcelConfig.masterTable);
              const colResponse = await apiClient.get(
                `/table-management/tables/${masterDetailExcelConfig.masterTable}/columns`
              );
              console.log("🔍 컬럼 조회 응답:", colResponse.data);
              
              if (colResponse.data?.success && colResponse.data?.data?.columns) {
                const colInfo = colResponse.data.data.columns.find(
                  (c: any) => (c.columnName || c.column_name) === field.columnName
                );
                console.log("🔍 찾은 컬럼 정보:", colInfo);
                if (colInfo) {
                  if (!refTable) {
                    refTable = colInfo.referenceTable || colInfo.reference_table;
                    console.log("🔍 DB에서 가져온 refTable:", refTable);
                  }
                  if (!displayCol) {
                    displayCol = colInfo.displayColumn || colInfo.display_column;
                    console.log("🔍 DB에서 가져온 displayColumn:", displayCol);
                  }
                }
              }
            }

            // displayColumn 저장 (Select 렌더링 시 사용)
            if (displayCol) {
              setEntityDisplayColumns((prev) => ({ ...prev, [field.columnName]: displayCol }));
            }

            if (refTable) {
              console.log("🔍 엔티티 데이터 조회:", refTable);
              const response = await DynamicFormApi.getTableData(refTable, {
                page: 1,
                pageSize: 1000,
              });
              console.log("🔍 엔티티 데이터 응답:", response);
              // getTableData는 { success, data: [...] } 형식으로 반환
              const rows = response.data?.rows || response.data;
              if (response.success && rows && Array.isArray(rows)) {
                setEntitySearchData((prev) => ({
                  ...prev,
                  [field.columnName]: rows,
                }));
                console.log("✅ 엔티티 데이터 로드 성공:", field.columnName, rows.length, "개");
              }
            } else {
              console.warn("❌ 엔티티 필드의 referenceTable을 찾을 수 없음:", field.columnName);
            }
          } catch (error) {
            console.error("❌ 엔티티 데이터 로드 실패:", field.columnName, error);
          } finally {
            setEntitySearchLoading((prev) => ({ ...prev, [field.columnName]: false }));
          }
        }
      }
    };

    if (open && isMasterDetail && masterDetailExcelConfig?.masterSelectFields?.length > 0) {
      loadEntityData();
    }
  }, [open, isMasterDetail, masterDetailExcelConfig]);

  // 마스터-디테일 모드에서 마스터 필드 입력 여부 확인
  const isSimpleMasterDetailMode = isMasterDetail && masterDetailExcelConfig;
  const hasMasterSelectFields = isSimpleMasterDetailMode && 
    (masterDetailExcelConfig?.masterSelectFields?.length ?? 0) > 0;

  // 마스터 필드가 모두 입력되었는지 확인
  const isMasterFieldsValid = () => {
    if (!hasMasterSelectFields) return true;
    return masterDetailExcelConfig!.masterSelectFields!.every((field) => {
      if (!field.required) return true;
      const value = masterFieldValues[field.columnName];
      return value !== undefined && value !== null && value !== "";
    });
  };

  // 파일 선택 핸들러
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedFile = e.target.files?.[0];
    if (!selectedFile) return;
    await processFile(selectedFile);
  };

  // 파일 처리 공통 함수 (파일 선택 및 드래그 앤 드롭에서 공유)
  const processFile = async (selectedFile: File) => {
    const fileExtension = selectedFile.name.split(".").pop()?.toLowerCase();
    if (!["xlsx", "xls", "csv"].includes(fileExtension || "")) {
      toast.error("엑셀 파일만 업로드 가능합니다. (.xlsx, .xls, .csv)");
      return;
    }

    setFile(selectedFile);

    try {
      const sheets = await getExcelSheetNames(selectedFile);
      setSheetNames(sheets);
      setSelectedSheet(sheets[0] || "");

      const data = await importFromExcel(selectedFile, sheets[0]);
      setAllData(data);
      setDisplayData(data);

      if (data.length > 0) {
        const columns = Object.keys(data[0]);
        const lastCol = String.fromCharCode(64 + columns.length);
        setDetectedRange(`A1:${lastCol}${data.length + 1}`);
        setExcelColumns(columns);
      }

      toast.success(`파일이 선택되었습니다: ${selectedFile.name}`);
    } catch (error) {
      console.error("파일 읽기 오류:", error);
      toast.error("파일을 읽는 중 오류가 발생했습니다.");
      setFile(null);
    }
  };

  // 드래그 앤 드롭 핸들러
  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(true);
  };

  const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(false);
  };

  const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(false);

    const droppedFile = e.dataTransfer.files?.[0];
    if (droppedFile) {
      await processFile(droppedFile);
    }
  };

  // 시트 변경 핸들러
  const handleSheetChange = async (sheetName: string) => {
    setSelectedSheet(sheetName);
    if (!file) return;

    try {
      const data = await importFromExcel(file, sheetName);
      setAllData(data);
      setDisplayData(data);

      if (data.length > 0) {
        const columns = Object.keys(data[0]);
        const lastCol = String.fromCharCode(64 + columns.length);
        setDetectedRange(`A1:${lastCol}${data.length + 1}`);
        setExcelColumns(columns);
      }
    } catch (error) {
      console.error("시트 읽기 오류:", error);
      toast.error("시트를 읽는 중 오류가 발생했습니다.");
    }
  };

  // 테이블 스키마 가져오기 (2단계 진입 시)
  useEffect(() => {
    if (currentStep === 2 && tableName) {
      loadTableSchema();
    }
  }, [currentStep, tableName]);

  // 테이블 생성 시 자동 생성되는 시스템 컬럼 (매핑에서 제외)
  const AUTO_GENERATED_COLUMNS = [
    "id",
    "created_date",
    "updated_date",
    "writer",
    "company_code",
  ];

  const loadTableSchema = async () => {
    try {
      console.log("🔍 테이블 스키마 로드 시작:", { tableName, isMasterDetail, isSimpleMasterDetailMode });

      let allColumns: TableColumn[] = [];

      // 🆕 마스터-디테일 간단 모드: 디테일 테이블 컬럼만 로드 (마스터 필드는 UI에서 선택)
      if (isSimpleMasterDetailMode && masterDetailRelation) {
        const { detailTable, detailFkColumn } = masterDetailRelation;
        
        console.log("📊 마스터-디테일 간단 모드 스키마 로드 (디테일만):", { detailTable });

        // 디테일 테이블 스키마만 로드 (마스터 정보는 UI에서 선택)
        const detailResponse = await getTableSchema(detailTable);
        if (detailResponse.success && detailResponse.data) {
          // 설정된 detailExcelFields가 있으면 해당 필드만, 없으면 전체
          const configuredFields = masterDetailExcelConfig?.detailExcelFields;
          
          const detailCols = detailResponse.data.columns
            .filter((col) => {
              // 자동 생성 컬럼, FK 컬럼 제외
              if (AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase())) return false;
              if (col.name === detailFkColumn) return false;
              
              // 설정된 필드가 있으면 해당 필드만
              if (configuredFields && configuredFields.length > 0) {
                return configuredFields.some((f) => f.columnName === col.name);
              }
              return true;
            })
            .map((col) => {
              // 설정에서 라벨 찾기
              const configField = configuredFields?.find((f) => f.columnName === col.name);
              return {
                ...col,
                label: configField?.columnLabel || col.label || col.name,
                originalName: col.name,
                sourceTable: detailTable,
              };
            });
          allColumns = detailCols;
        }

        console.log("✅ 마스터-디테일 간단 모드 컬럼 로드 완료:", allColumns.length);
      }
      // 🆕 마스터-디테일 기존 모드: 두 테이블의 컬럼 합치기
      else if (isMasterDetail && masterDetailRelation) {
        const { masterTable, detailTable, detailFkColumn } = masterDetailRelation;
        
        console.log("📊 마스터-디테일 스키마 로드:", { masterTable, detailTable });

        // 마스터 테이블 스키마
        const masterResponse = await getTableSchema(masterTable);
        if (masterResponse.success && masterResponse.data) {
          const masterCols = masterResponse.data.columns
            .filter((col) => !AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase()))
            .map((col) => ({
              ...col,
              // 유니크 키를 위해 테이블명 접두사 추가
              name: `${masterTable}.${col.name}`,
              label: `[마스터] ${col.label || col.name}`,
              originalName: col.name,
              sourceTable: masterTable,
            }));
          allColumns = [...allColumns, ...masterCols];
        }

        // 디테일 테이블 스키마 (FK 컬럼 제외)
        const detailResponse = await getTableSchema(detailTable);
        if (detailResponse.success && detailResponse.data) {
          const detailCols = detailResponse.data.columns
            .filter((col) => 
              !AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase()) &&
              col.name !== detailFkColumn // FK 컬럼 제외
            )
            .map((col) => ({
              ...col,
              // 유니크 키를 위해 테이블명 접두사 추가
              name: `${detailTable}.${col.name}`,
              label: `[디테일] ${col.label || col.name}`,
              originalName: col.name,
              sourceTable: detailTable,
            }));
          allColumns = [...allColumns, ...detailCols];
        }

        console.log("✅ 마스터-디테일 컬럼 로드 완료:", allColumns.length);
      } else {
        // 기존 단일 테이블 모드
        const response = await getTableSchema(tableName);

        console.log("📊 테이블 스키마 응답:", response);

        if (response.success && response.data) {
          // 자동 생성 컬럼 제외
          allColumns = response.data.columns.filter(
            (col) => !AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase())
          );
        } else {
          console.error("❌ 테이블 스키마 로드 실패:", response);
          return;
        }
      }

      // 채번 정보 병합: table_type_columns에서 inputType 가져오기
      try {
        const { getTableColumns } = await import("@/lib/api/tableManagement");
        const targetTables = isMasterDetail && masterDetailRelation
          ? [masterDetailRelation.masterTable, masterDetailRelation.detailTable]
          : [tableName];

        // 테이블별 채번 컬럼 수집
        const numberingColSet = new Set<string>();
        for (const tbl of targetTables) {
          const typeResponse = await getTableColumns(tbl);
          if (typeResponse.success && typeResponse.data?.columns) {
            for (const tc of typeResponse.data.columns) {
              if (tc.inputType === "numbering") {
                try {
                  const settings = typeof tc.detailSettings === "string"
                    ? JSON.parse(tc.detailSettings) : tc.detailSettings;
                  if (settings?.numberingRuleId) {
                    numberingColSet.add(tc.columnName);
                  }
                } catch { /* 파싱 실패 무시 */ }
              }
            }
          }
        }

        // systemColumns에 isNumbering 플래그 추가
        if (numberingColSet.size > 0) {
          allColumns = allColumns.map((col) => {
            const rawName = (col as any).originalName || col.name;
            const colName = rawName.includes(".") ? rawName.split(".")[1] : rawName;
            if (numberingColSet.has(colName)) {
              return { ...col, isNumbering: true } as any;
            }
            return col;
          });
          console.log("✅ 채번 컬럼 감지:", Array.from(numberingColSet));
        }
      } catch (error) {
        console.warn("채번 정보 로드 실패 (무시):", error);
      }

      console.log("✅ 시스템 컬럼 로드 완료 (자동 생성 컬럼 제외):", allColumns);
      setSystemColumns(allColumns);

      // 기존 매핑 템플릿 조회
      console.log("🔍 매핑 템플릿 조회 중...", { tableName, excelColumns });
      const mappingResponse = await findMappingByColumns(tableName, excelColumns);

      if (mappingResponse.success && mappingResponse.data) {
        // 저장된 매핑 템플릿이 있으면 자동 적용
        console.log("✅ 기존 매핑 템플릿 발견:", mappingResponse.data);
        const savedMappings = mappingResponse.data.columnMappings;

        const appliedMappings: ColumnMapping[] = excelColumns.map((col) => ({
          excelColumn: col,
          systemColumn: savedMappings[col] || null,
        }));
        setColumnMappings(appliedMappings);
        setIsAutoMappingLoaded(true);

        const matchedCount = appliedMappings.filter((m) => m.systemColumn).length;
        toast.success(`이전 매핑 템플릿이 적용되었습니다. (${matchedCount}개 컬럼)`);
      } else {
        // 매핑 템플릿이 없으면 초기 상태로 설정
        console.log("ℹ️ 매핑 템플릿 없음 - 새 엑셀 구조");
        const initialMappings: ColumnMapping[] = excelColumns.map((col) => ({
          excelColumn: col,
          systemColumn: null,
        }));
        setColumnMappings(initialMappings);
        setIsAutoMappingLoaded(false);
      }
    } catch (error) {
      console.error("❌ 테이블 스키마 로드 실패:", error);
      toast.error("테이블 스키마를 불러올 수 없습니다.");
    }
  };

  // 자동 매핑 - 컬럼명과 라벨 모두 비교
  const handleAutoMapping = () => {
    const newMappings = excelColumns.map((excelCol) => {
      const normalizedExcelCol = excelCol.toLowerCase().trim();
      // [마스터], [디테일] 접두사 제거 후 비교
      const cleanExcelCol = normalizedExcelCol.replace(/^\[(마스터|디테일)\]\s*/i, "");

      // 1. 먼저 라벨로 매칭 시도 (접두사 제거 후)
      let matchedSystemCol = systemColumns.find((sysCol) => {
        if (!sysCol.label) return false;
        // [마스터], [디테일] 접두사 제거 후 비교
        const cleanLabel = sysCol.label.toLowerCase().trim().replace(/^\[(마스터|디테일)\]\s*/i, "");
        return cleanLabel === normalizedExcelCol || cleanLabel === cleanExcelCol;
      });

      // 2. 라벨로 매칭되지 않으면 컬럼명으로 매칭 시도
      if (!matchedSystemCol) {
        matchedSystemCol = systemColumns.find((sysCol) => {
          // 마스터-디테일 모드: originalName이 있으면 사용
          const originalName = (sysCol as any).originalName;
          const colName = originalName || sysCol.name;
          return colName.toLowerCase().trim() === normalizedExcelCol || colName.toLowerCase().trim() === cleanExcelCol;
        });
      }

      // 3. 여전히 매칭 안되면 전체 이름(테이블.컬럼)에서 컬럼 부분만 추출해서 비교
      if (!matchedSystemCol) {
        matchedSystemCol = systemColumns.find((sysCol) => {
          // 테이블.컬럼 형식에서 컬럼만 추출
          const nameParts = sysCol.name.split(".");
          const colNameOnly = nameParts.length > 1 ? nameParts[1] : nameParts[0];
          return colNameOnly.toLowerCase().trim() === normalizedExcelCol || colNameOnly.toLowerCase().trim() === cleanExcelCol;
        });
      }

      return {
        excelColumn: excelCol,
        systemColumn: matchedSystemCol ? matchedSystemCol.name : null,
      };
    });

    setColumnMappings(newMappings);
    const matchedCount = newMappings.filter((m) => m.systemColumn).length;
    toast.success(`${matchedCount}개 컬럼이 자동 매핑되었습니다.`);
  };

  // 컬럼 매핑 변경
  const handleMappingChange = (excelColumn: string, systemColumn: string | null) => {
    setColumnMappings((prev) =>
      prev.map((mapping) =>
        mapping.excelColumn === excelColumn ? { ...mapping, systemColumn } : mapping
      )
    );
  };

  // 중복 체크 설정 변경
  const handleDuplicateCheckChange = (excelColumn: string, checkDuplicate: boolean) => {
    setColumnMappings((prev) =>
      prev.map((mapping) =>
        mapping.excelColumn === excelColumn
          ? { ...mapping, checkDuplicate }
          : mapping
      )
    );
  };

  // 중복 체크 설정된 컬럼 수
  const duplicateCheckCount = columnMappings.filter((m) => m.checkDuplicate && m.systemColumn).length;

  // 카테고리 컬럼 검증: 엑셀 데이터에서 유효하지 않은 카테고리 값 감지
  const validateCategoryColumns = async () => {
    try {
      setIsCategoryValidating(true);

      const targetTableName = isMasterDetail && masterDetailRelation
        ? masterDetailRelation.detailTable
        : tableName;

      // 테이블의 카테고리 타입 컬럼 조회
      const colResponse = await getTableColumns(targetTableName);
      if (!colResponse.success || !colResponse.data?.columns) {
        return null;
      }

      const categoryColumns = colResponse.data.columns.filter(
        (col: any) => col.inputType === "category"
      );

      if (categoryColumns.length === 0) {
        return null;
      }

      // 매핑된 컬럼 중 카테고리 타입인 것 찾기
      const mappedCategoryColumns: Array<{
        systemCol: string;
        excelCol: string;
        displayName: string;
      }> = [];

      for (const mapping of columnMappings) {
        if (!mapping.systemColumn) continue;
        const rawName = mapping.systemColumn.includes(".")
          ? mapping.systemColumn.split(".")[1]
          : mapping.systemColumn;

        const catCol = categoryColumns.find(
          (cc: any) => (cc.columnName || cc.column_name) === rawName
        );
        if (catCol) {
          mappedCategoryColumns.push({
            systemCol: rawName,
            excelCol: mapping.excelColumn,
            displayName: catCol.displayName || catCol.display_name || rawName,
          });
        }
      }

      if (mappedCategoryColumns.length === 0) {
        return null;
      }

      // 각 카테고리 컬럼의 유효값 조회 및 엑셀 데이터 검증
      const mismatches: typeof categoryMismatches = {};

      for (const catCol of mappedCategoryColumns) {
        const valuesResponse = await getCategoryValues(targetTableName, catCol.systemCol);
        if (!valuesResponse.success || !valuesResponse.data) continue;

        const validValues = flattenCategoryValues(valuesResponse.data as any[]);

        const validCodes = new Set(validValues.map((v) => v.valueCode));
        const validLabels = new Set(validValues.map((v) => v.valueLabel));
        const validLabelsLower = new Set(validValues.map((v) => v.valueLabel.toLowerCase()));

        // 엑셀 데이터에서 유효하지 않은 값 수집
        const invalidMap = new Map<string, number[]>();

        allData.forEach((row, rowIdx) => {
          const val = row[catCol.excelCol];
          if (val === undefined || val === null || String(val).trim() === "") return;
          const strVal = String(val).trim();

          // 코드 매칭 → 라벨 매칭 → 소문자 라벨 매칭
          if (validCodes.has(strVal)) return;
          if (validLabels.has(strVal)) return;
          if (validLabelsLower.has(strVal.toLowerCase())) return;

          if (!invalidMap.has(strVal)) {
            invalidMap.set(strVal, []);
          }
          invalidMap.get(strVal)!.push(rowIdx);
        });

        if (invalidMap.size > 0) {
          const options = validValues.map((v) => ({
            code: v.valueCode,
            label: v.valueLabel,
            depth: v.depth,
            ancestors: v.ancestors,
          }));

          mismatches[`${catCol.systemCol}|||${catCol.displayName}`] = Array.from(invalidMap.entries()).map(
            ([invalidValue, rowIndices]) => ({
              invalidValue,
              replacement: null,
              validOptions: options,
              rowIndices,
            })
          );
        }
      }

      if (Object.keys(mismatches).length > 0) {
        return mismatches;
      }
      return null;
    } catch (error) {
      console.error("카테고리 검증 실패:", error);
      return null;
    } finally {
      setIsCategoryValidating(false);
    }
  };

  // 카테고리 대체값 선택 후 데이터에 적용
  const applyCategoryReplacements = () => {
    // 모든 대체값이 선택되었는지 확인
    for (const [key, items] of Object.entries(categoryMismatches)) {
      for (const item of items) {
        if (item.replacement === null) {
          toast.error("모든 항목의 대체 값을 선택해주세요.");
          return false;
        }
      }
    }

    // 엑셀 컬럼명 → 시스템 컬럼명 매핑 구축
    const systemToExcelMap = new Map<string, string>();
    for (const mapping of columnMappings) {
      if (!mapping.systemColumn) continue;
      const rawName = mapping.systemColumn.includes(".")
        ? mapping.systemColumn.split(".")[1]
        : mapping.systemColumn;
      systemToExcelMap.set(rawName, mapping.excelColumn);
    }

    const newData = allData.map((row) => ({ ...row }));

    for (const [key, items] of Object.entries(categoryMismatches)) {
      const systemCol = key.split("|||")[0];
      const excelCol = systemToExcelMap.get(systemCol);
      if (!excelCol) continue;

      for (const item of items) {
        if (!item.replacement) continue;
        // 선택된 대체값의 라벨 찾기
        const selectedOption = item.validOptions.find((opt) => opt.code === item.replacement);
        const replacementLabel = selectedOption?.label || item.replacement;

        for (const rowIdx of item.rowIndices) {
          if (newData[rowIdx]) {
            newData[rowIdx][excelCol] = replacementLabel;
          }
        }
      }
    }

    setAllData(newData);
    setDisplayData(newData);
    setShowCategoryValidation(false);
    setCategoryMismatches({});
    toast.success("카테고리 값이 대체되었습니다. '다음'을 눌러 진행해주세요.");
    return true;
  };

  // 템플릿 다운로드: 테이블 스키마 기반으로 빈 엑셀 파일 생성
  const handleDownloadTemplate = async () => {
    try {
      const { exportToExcel } = await import("@/lib/utils/excelExport");
      const response = await getTableSchema(tableName);
      if (!response.success || !response.data) {
        toast.error("테이블 정보를 가져올 수 없습니다.");
        return;
      }

      const AUTO_COLS = ["id", "created_date", "updated_date", "writer", "company_code"];
      const columns = response.data.columns.filter(
        (col) => !AUTO_COLS.includes(col.name.toLowerCase())
      );

      // 필수 컬럼에 * 표시
      const headerRow: Record<string, any> = {};
      for (const col of columns) {
        const label = col.label || col.name;
        const isRequired = !col.nullable;
        headerRow[isRequired ? `${label} *` : label] = "";
      }

      await exportToExcel([headerRow], `${tableName}_템플릿.xlsx`, "Sheet1");
      toast.success("템플릿 파일이 다운로드되었습니다.");
    } catch (error) {
      console.error("템플릿 다운로드 실패:", error);
      toast.error("템플릿 다운로드에 실패했습니다.");
    }
  };

  // 다음 단계
  const handleNext = async () => {
    if (currentStep === 1 && !file) {
      toast.error("파일을 선택해주세요.");
      return;
    }

    if (currentStep === 1 && displayData.length === 0) {
      toast.error("데이터가 없습니다.");
      return;
    }

    // 🆕 마스터-디테일 간단 모드: 마스터 필드 유효성 검사
    if (currentStep === 1 && hasMasterSelectFields && !isMasterFieldsValid()) {
      toast.error("마스터 정보를 모두 입력해주세요.");
      return;
    }

    // 1단계 → 2단계 전환 시: 빈 헤더 열 제외
    if (currentStep === 1) {
      // 빈 헤더가 아닌 열만 필터링
      const validColumnIndices: number[] = [];
      const validColumns: string[] = [];

      excelColumns.forEach((col, index) => {
        if (col && col.trim() !== "") {
          validColumnIndices.push(index);
          validColumns.push(col);
        }
      });

      // 빈 헤더 열이 있었다면 데이터에서도 해당 열 제거
      if (validColumns.length < excelColumns.length) {
        const removedCount = excelColumns.length - validColumns.length;
        
        // 새로운 데이터: 유효한 열만 포함
        const cleanedData = displayData.map((row) => {
          const newRow: Record<string, any> = {};
          validColumns.forEach((colName) => {
            newRow[colName] = row[colName];
          });
          return newRow;
        });

        setExcelColumns(validColumns);
        setDisplayData(cleanedData);
        setAllData(cleanedData);

        if (removedCount > 0) {
          toast.info(`빈 헤더 ${removedCount}개 열이 제외되었습니다.`);
        }
      }
    }

    // 2단계 → 3단계 전환 시: NOT NULL 컬럼 매핑 필수 검증 + 카테고리 검증
    if (currentStep === 2) {
      // 매핑된 시스템 컬럼 (원본 이름 그대로 + dot 뒤 이름 둘 다 저장)
      const mappedSystemCols = new Set<string>();
      columnMappings.filter((m) => m.systemColumn).forEach((m) => {
        const colName = m.systemColumn!;
        mappedSystemCols.add(colName); // 원본 (예: user_info.user_id)
        if (colName.includes(".")) {
          mappedSystemCols.add(colName.split(".")[1]); // dot 뒤 (예: user_id)
        }
      });

      const unmappedRequired = systemColumns.filter((col) => {
        const rawName = col.name.includes(".") ? col.name.split(".")[1] : col.name;
        if (AUTO_GENERATED_COLUMNS.includes(rawName.toLowerCase())) return false;
        if (col.nullable) return false;
        if (mappedSystemCols.has(col.name) || mappedSystemCols.has(rawName)) return false;
        if ((col as any).isNumbering) return false;
        return true;
      });

      if (unmappedRequired.length > 0) {
        const colNames = unmappedRequired.map((c) => c.label || c.name).join(", ");
        toast.error(`필수(NOT NULL) 컬럼이 매핑되지 않았습니다: ${colNames}`);
        return;
      }

      // 카테고리 컬럼 검증
      const mismatches = await validateCategoryColumns();
      if (mismatches) {
        setCategoryMismatches(mismatches);
        setShowCategoryValidation(true);
        return;
      }

      // 데이터 사전 검증 (NOT NULL 값 누락, UNIQUE 중복)
      setDbDuplicateAction(null);
      setIsDataValidating(true);
      try {
        const { validateExcelData: validateExcel } = await import("@/lib/api/tableManagement");

        // 매핑된 데이터 구성
        const mappedForValidation = allData.map((row) => {
          const mapped: Record<string, any> = {};
          columnMappings.forEach((m) => {
            if (m.systemColumn) {
              let colName = m.systemColumn;
              if (isMasterDetail && colName.includes(".")) {
                colName = colName.split(".")[1];
              }
              mapped[colName] = row[m.excelColumn];
            }
          });
          return mapped;
        }).filter((row) => Object.values(row).some((v) => v !== null && v !== undefined && String(v).trim() !== ""));

        if (mappedForValidation.length > 0) {
          const result = await validateExcel(tableName, mappedForValidation);
          if (result.success && result.data) {
            setValidationResult(result.data);
          } else {
            setValidationResult(null);
          }
        } else {
          setValidationResult(null);
        }
      } catch (err) {
        console.warn("데이터 사전 검증 실패 (무시):", err);
        setValidationResult(null);
      } finally {
        setIsDataValidating(false);
      }
    }

    setCurrentStep((prev) => Math.min(prev + 1, 3));
  };

  // 이전 단계
  const handlePrevious = () => {
    setCurrentStep((prev) => Math.max(prev - 1, 1));
  };

  // 테이블 타입 관리에서 채번 컬럼 자동 감지
  const detectNumberingColumn = async (
    targetTableName: string
  ): Promise<{ columnName: string; numberingRuleId: string } | null> => {
    try {
      const { getTableColumns } = await import("@/lib/api/tableManagement");
      const response = await getTableColumns(targetTableName);

      if (response.success && response.data?.columns) {
        for (const col of response.data.columns) {
          if (col.inputType === "numbering") {
            try {
              const settings =
                typeof col.detailSettings === "string"
                  ? JSON.parse(col.detailSettings)
                  : col.detailSettings;
              if (settings?.numberingRuleId) {
                console.log(
                  `✅ 채번 컬럼 자동 감지: ${col.columnName} → 규칙 ID: ${settings.numberingRuleId}`
                );
                return {
                  columnName: col.columnName,
                  numberingRuleId: settings.numberingRuleId,
                };
              }
            } catch {
              // detailSettings 파싱 실패 시 무시
            }
          }
        }
      }
      return null;
    } catch (error) {
      console.error("채번 컬럼 감지 실패:", error);
      return null;
    }
  };

  // 업로드 핸들러
  const handleUpload = async () => {
    if (!file || !tableName) {
      toast.error("필수 정보가 누락되었습니다.");
      return;
    }

    setIsUploading(true);

    try {
      // allData를 사용하여 전체 데이터 업로드
      const mappedData = allData.map((row) => {
        const mappedRow: Record<string, any> = {};
        columnMappings.forEach((mapping) => {
          if (mapping.systemColumn) {
            // 마스터-디테일 모드: 테이블.컬럼 형식에서 컬럼명만 추출
            let colName = mapping.systemColumn;
            if (isMasterDetail && colName.includes(".")) {
              colName = colName.split(".")[1];
            }
            mappedRow[colName] = row[mapping.excelColumn];
          }
        });
        return mappedRow;
      });

      // 빈 행 필터링: 모든 값이 비어있거나 undefined/null인 행 제외
      const filteredData = mappedData.filter((row) => {
        const values = Object.values(row);
        return values.some((value) => {
          if (value === undefined || value === null) return false;
          if (typeof value === "string" && value.trim() === "") return false;
          return true;
        });
      });

      console.log(
        `📊 엑셀 업로드: 전체 ${mappedData.length}행 중 유효한 ${filteredData.length}행`
      );

      // 마스터-디테일 간단 모드 처리 (마스터 필드 선택 + 채번 자동 감지)
      if (isSimpleMasterDetailMode && screenId && masterDetailRelation) {
        // 마스터 테이블에서 채번 컬럼 자동 감지
        const masterNumberingInfo = await detectNumberingColumn(masterDetailRelation.masterTable);
        const detectedNumberingRuleId = masterNumberingInfo?.numberingRuleId || masterDetailExcelConfig?.numberingRuleId;

        console.log("📊 마스터-디테일 간단 모드 업로드:", {
          masterDetailRelation,
          masterFieldValues,
          detectedNumberingRuleId,
          autoDetected: !!masterNumberingInfo,
        });

        const uploadResult = await DynamicFormApi.uploadMasterDetailSimple(
          screenId,
          filteredData,
          masterFieldValues,
          detectedNumberingRuleId || undefined,
          masterDetailExcelConfig?.afterUploadFlowId || undefined, // 하위 호환성
          masterDetailExcelConfig?.afterUploadFlows || undefined // 다중 제어
        );

        if (uploadResult.success && uploadResult.data) {
          const { masterInserted, detailInserted, generatedKey, errors } = uploadResult.data;
          
          toast.success(
            `마스터 ${masterInserted}건(${generatedKey || ""}), 디테일 ${detailInserted}건 처리되었습니다.` +
            (errors?.length > 0 ? ` (오류: ${errors.length}건)` : "")
          );

          // 매핑 템플릿 저장
          await saveMappingTemplateInternal();

          onSuccess?.();
        } else {
          toast.error(uploadResult.message || "마스터-디테일 업로드에 실패했습니다.");
        }
      }
      // 🆕 마스터-디테일 기존 모드 처리
      else if (isMasterDetail && screenId && masterDetailRelation) {
        console.log("📊 마스터-디테일 업로드 모드:", masterDetailRelation);

        // 마스터 키 컬럼 매핑 검증 (채번 타입이면 자동 생성되므로 검증 생략)
        const masterKeyCol = masterDetailRelation.masterKeyColumn;
        const hasMasterKey = filteredData.length > 0 && filteredData[0][masterKeyCol] !== undefined && filteredData[0][masterKeyCol] !== null && filteredData[0][masterKeyCol] !== "";
        if (!hasMasterKey) {
          // 채번 여부 확인 - 채번이면 백엔드에서 자동 생성하므로 통과
          const numberingInfo = await detectNumberingColumn(masterDetailRelation.masterTable);
          const isMasterKeyAutoNumbering = numberingInfo && numberingInfo.columnName === masterKeyCol;
          
          if (!isMasterKeyAutoNumbering) {
            toast.error(
              `마스터 키 컬럼(${masterKeyCol})이 매핑되지 않았습니다. 컬럼 매핑에서 [마스터] 항목을 확인해주세요.`
            );
            setIsUploading(false);
            return;
          }
          console.log(`✅ 마스터 키(${masterKeyCol})는 채번 타입 → 백엔드에서 자동 생성`);
        }

        const uploadResult = await DynamicFormApi.uploadMasterDetailData(
          screenId,
          filteredData
        );

        if (uploadResult.success && uploadResult.data) {
          const { masterInserted, masterUpdated, detailInserted, errors } = uploadResult.data;
          
          toast.success(
            `마스터 ${masterInserted + masterUpdated}건, 디테일 ${detailInserted}건 처리되었습니다.` +
            (errors.length > 0 ? ` (오류: ${errors.length}건)` : "")
          );

          // 매핑 템플릿 저장
          await saveMappingTemplateInternal();

          onSuccess?.();
        } else {
          toast.error(uploadResult.message || "마스터-디테일 업로드에 실패했습니다.");
        }
      } else {
        // 기존 단일 테이블 업로드 로직
        let successCount = 0;
        let failCount = 0;
        let skipCount = 0;
        let overwriteCount = 0;

        // 단일 테이블 채번 자동 감지 (테이블 타입 관리에서 input_type = 'numbering' 컬럼)
        const numberingInfo = await detectNumberingColumn(tableName);
        const hasNumbering = !!numberingInfo;

        // 중복 체크 설정 확인
        let duplicateCheckMappings = columnMappings.filter(
          (m) => m.checkDuplicate && m.systemColumn
        );
        let effectiveDuplicateAction = duplicateAction;

        // 검증 화면에서 DB 중복 처리 방법을 선택한 경우, 유니크 컬럼을 자동으로 중복 체크에 추가
        if (dbDuplicateAction && validationResult?.uniqueInDbErrors && validationResult.uniqueInDbErrors.length > 0) {
          effectiveDuplicateAction = dbDuplicateAction;
          const uniqueColumns = new Set(validationResult.uniqueInDbErrors.map((e) => e.column));
          for (const colName of uniqueColumns) {
            const alreadyAdded = duplicateCheckMappings.some((m) => {
              const mapped = m.systemColumn?.includes(".") ? m.systemColumn.split(".")[1] : m.systemColumn;
              return mapped === colName;
            });
            if (!alreadyAdded) {
              const mapping = columnMappings.find((m) => {
                const mapped = m.systemColumn?.includes(".") ? m.systemColumn.split(".")[1] : m.systemColumn;
                return mapped === colName;
              });
              if (mapping) {
                duplicateCheckMappings = [...duplicateCheckMappings, { ...mapping, checkDuplicate: true }];
              }
            }
          }
          console.log(`📊 검증 화면 DB 중복 처리: ${dbDuplicateAction}, 체크 컬럼: ${[...uniqueColumns].join(", ")}`);
        }

        const hasDuplicateCheck = duplicateCheckMappings.length > 0;

        // 중복 체크를 위한 기존 데이터 조회 (중복 체크가 설정된 경우에만)
        let existingDataMap: Map<string, any> = new Map();
        if (hasDuplicateCheck) {
          try {
            // 중복 체크할 컬럼들의 값 조회
            const checkColumns = duplicateCheckMappings.map((m) => {
              let colName = m.systemColumn!;
              if (isMasterDetail && colName.includes(".")) {
                colName = colName.split(".")[1];
              }
              return colName;
            });

            // DynamicFormApi.getTableData 사용
            const existingResponse = await DynamicFormApi.getTableData(tableName, {
              page: 1,
              pageSize: 10000,
            });

            console.log("📊 중복 체크용 기존 데이터 조회 결과:", existingResponse);

            // getTableData는 { success, data: [...] } 또는 { success, data: { rows: [...] } } 형식
            const rows = existingResponse.data?.rows || existingResponse.data;
            if (existingResponse.success && rows && Array.isArray(rows)) {
              // 중복 체크 컬럼 값을 키로 하는 맵 생성
              rows.forEach((row: any) => {
                const keyParts = checkColumns.map((col) => String(row[col] || "").trim());
                const key = keyParts.join("|||");
                existingDataMap.set(key, row);
              });
              console.log(`📊 중복 체크용 기존 데이터 로드: ${existingDataMap.size}건`);
            }
          } catch (error) {
            console.error("중복 체크 데이터 조회 오류:", error);
          }
        }

        for (let rowIdx = 0; rowIdx < filteredData.length; rowIdx++) {
          const row = filteredData[rowIdx];
          try {
            let dataToSave = { ...row };
            let shouldSkip = false;
            let shouldUpdate = false;
            let existingRow: any = null;

            // 중복 체크
            if (hasDuplicateCheck) {
              const checkColumns = duplicateCheckMappings.map((m) => {
                let colName = m.systemColumn!;
                if (isMasterDetail && colName.includes(".")) {
                  colName = colName.split(".")[1];
                }
                return colName;
              });
              
              const keyParts = checkColumns.map((col) => String(dataToSave[col] || "").trim());
              const key = keyParts.join("|||");
              
              if (existingDataMap.has(key)) {
                existingRow = existingDataMap.get(key);
                if (effectiveDuplicateAction === "skip") {
                  shouldSkip = true;
                  skipCount++;
                  console.log(`⏭️ [행 ${rowIdx + 1}] 중복으로 건너뛰기: ${key}`);
                } else {
                  shouldUpdate = true;
                  console.log(`🔄 [행 ${rowIdx + 1}] 중복으로 덮어쓰기: ${key}`);
                }
              } else {
                console.log(`✅ [행 ${rowIdx + 1}] 중복 아님 (신규 데이터): ${key}`);
              }
            }

            // 건너뛰기 처리
            if (shouldSkip) {
              continue;
            }

            // 채번 적용: 엑셀에 값이 없거나 빈 값이면 채번 규칙으로 자동 생성, 값이 있으면 그대로 사용
            if (hasNumbering && numberingInfo && (uploadMode === "insert" || uploadMode === "upsert") && !shouldUpdate) {
              const existingValue = dataToSave[numberingInfo.columnName];
              const hasExcelValue = existingValue !== undefined && existingValue !== null && String(existingValue).trim() !== "";

              if (!hasExcelValue) {
                try {
                  const { apiClient } = await import("@/lib/api/client");
                  const numberingResponse = await apiClient.post(`/numbering-rules/${numberingInfo.numberingRuleId}/allocate`);
                  const generatedCode = numberingResponse.data?.data?.generatedCode || numberingResponse.data?.data?.code;
                  if (numberingResponse.data?.success && generatedCode) {
                    dataToSave[numberingInfo.columnName] = generatedCode;
                  }
                } catch (numError) {
                  console.error("채번 오류:", numError);
                }
              }
            }

            if (shouldUpdate && existingRow) {
              // 덮어쓰기: 기존 데이터 업데이트
              const formData = { 
                screenId: 0, 
                tableName, 
                data: dataToSave,
              };
              console.log(`📝 [행 ${rowIdx + 1}] 덮어쓰기 시도: id=${existingRow.id}`, dataToSave);
              const result = await DynamicFormApi.updateFormData(existingRow.id, formData);
              if (result.success) {
                overwriteCount++;
                successCount++;
              } else {
                console.error(`❌ [행 ${rowIdx + 1}] 덮어쓰기 실패:`, result.message);
                failCount++;
              }
            } else if (uploadMode === "insert" || uploadMode === "upsert") {
              // 신규 등록 (insert, upsert 모드)
              const formData = { screenId: 0, tableName, data: dataToSave };
              console.log(`📝 [행 ${rowIdx + 1}] 신규 등록 시도 (mode: ${uploadMode}):`, dataToSave);
              const result = await DynamicFormApi.saveFormData(formData);
              if (result.success) {
                successCount++;
                console.log(`✅ [행 ${rowIdx + 1}] 신규 등록 성공`);
              } else {
                console.error(`❌ [행 ${rowIdx + 1}] 신규 등록 실패:`, result.message);
                failCount++;
              }
            } else if (uploadMode === "update") {
              // update 모드에서 기존 데이터가 없는 행은 건너뛰기
              console.log(`⏭️ [행 ${rowIdx + 1}] update 모드: 기존 데이터 없음, 건너뛰기`);
              skipCount++;
            }
          } catch (error: any) {
            console.error(`❌ [행 ${rowIdx + 1}] 업로드 처리 오류:`, error?.response?.data || error?.message || error);
            failCount++;
          }
        }

        // 업로드 후 제어 실행
        if (afterUploadFlows && afterUploadFlows.length > 0 && successCount > 0) {
          console.log("🔄 업로드 후 제어 실행:", afterUploadFlows);
          try {
            const { apiClient } = await import("@/lib/api/client");
            // 순서대로 실행
            const sortedFlows = [...afterUploadFlows].sort((a, b) => a.order - b.order);
            for (const flow of sortedFlows) {
              await apiClient.post(`/dataflow/node-flows/${flow.flowId}/execute`, {
                sourceData: { tableName, uploadedCount: successCount },
              });
              console.log(`✅ 제어 실행 완료: flowId=${flow.flowId}`);
            }
          } catch (controlError) {
            console.error("제어 실행 오류:", controlError);
          }
        }

        console.log(`📊 엑셀 업로드 결과 요약: 성공=${successCount}, 건너뛰기=${skipCount}, 덮어쓰기=${overwriteCount}, 실패=${failCount}`);

        if (successCount > 0 || skipCount > 0) {
          let message = "";
          if (successCount > 0) {
            message += `${successCount}개 행 업로드`;
            if (overwriteCount > 0) {
              message += ` (덮어쓰기 ${overwriteCount}건)`;
            }
          }
          if (skipCount > 0) {
            message += message ? `, ` : "";
            message += `중복 건너뛰기 ${skipCount}개`;
          }
          if (failCount > 0) {
            message += `, 실패 ${failCount}개`;
          }
          
          if (failCount > 0 && successCount === 0) {
            toast.warning(message);
          } else {
            toast.success(message);
          }

          // 매핑 템플릿 저장
          await saveMappingTemplateInternal();

          if (successCount > 0 || overwriteCount > 0) {
            onSuccess?.();
          }
        } else if (failCount > 0) {
          toast.error(`업로드 실패: ${failCount}개 행 저장에 실패했습니다. 브라우저 콘솔에서 상세 오류를 확인하세요.`);
        } else {
          toast.error("업로드에 실패했습니다.");
        }
      }
    } catch (error) {
      console.error("❌ 엑셀 업로드 실패:", error);
      toast.error("엑셀 업로드 중 오류가 발생했습니다.");
    } finally {
      setIsUploading(false);
    }
  };

  // 매핑 템플릿 저장 헬퍼 함수
  const saveMappingTemplateInternal = async () => {
    try {
      const mappingsToSave: Record<string, string | null> = {};
      columnMappings.forEach((mapping) => {
        mappingsToSave[mapping.excelColumn] = mapping.systemColumn;
      });

      console.log("💾 매핑 템플릿 저장 중...", {
        tableName,
        excelColumns,
        mappingsToSave,
      });
      const saveResult = await saveMappingTemplate(
        tableName,
        excelColumns,
        mappingsToSave
      );

      if (saveResult.success) {
        console.log("✅ 매핑 템플릿 저장 완료:", saveResult.data);
      } else {
        console.warn("⚠️ 매핑 템플릿 저장 실패:", saveResult.error);
      }
    } catch (error) {
      console.warn("⚠️ 매핑 템플릿 저장 중 오류:", error);
    }
  };

  // 모달 닫기 시 초기화
  useEffect(() => {
    if (!open) {
      setCurrentStep(1);
      setFile(null);
      setSheetNames([]);
      setSelectedSheet("");
      setIsAutoMappingLoaded(false);
      setDetectedRange("");
      setAllData([]);
      setDisplayData([]);
      setExcelColumns([]);
      setSystemColumns([]);
      setColumnMappings([]);
      setDuplicateAction("skip");
      setDbDuplicateAction(null);
      // 검증 상태 초기화
      setValidationResult(null);
      setIsDataValidating(false);
      // 카테고리 검증 초기화
      setShowCategoryValidation(false);
      setCategoryMismatches({});
      setIsCategoryValidating(false);
      // 🆕 마스터-디테일 모드 초기화
      setMasterFieldValues({});
    }
  }, [open]);

  return (
    <>
    <Dialog open={open} onOpenChange={(v) => { if (!showCategoryValidation) onOpenChange(v); }}>
      <DialogContent
        className="max-h-[95vh] max-w-[95vw] sm:max-w-[1200px]"
        style={{
          width: "1000px",
          height: "700px",
          minWidth: "700px",
          minHeight: "500px",
          maxWidth: "1400px",
          maxHeight: "900px",
        }}
      >
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2 text-base sm:text-lg">
            <FileSpreadsheet className="h-5 w-5" />
            엑셀 데이터 업로드
            {isMasterDetail && (
              <span className="ml-2 rounded bg-primary/10 px-2 py-0.5 text-xs font-normal text-primary">
                마스터-디테일
              </span>
            )}
          </DialogTitle>
          <DialogDescription className="text-xs sm:text-sm">
            {isMasterDetail && masterDetailRelation ? (
              <>
                마스터({masterDetailRelation.masterTable}) + 디테일({masterDetailRelation.detailTable}) 구조입니다.
                마스터 데이터는 중복 입력 시 병합됩니다.
              </>
            ) : (
              "엑셀 파일을 선택하고 컬럼을 매핑하여 데이터를 업로드하세요."
            )}
          </DialogDescription>
        </DialogHeader>

        {/* 스텝 인디케이터 (3단계) */}
        <div className="flex items-center justify-between">
          {[
            { num: 1, label: "파일 선택" },
            { num: 2, label: "컬럼 매핑" },
            { num: 3, label: "확인" },
          ].map((step, index) => (
            <React.Fragment key={step.num}>
              <div className="flex flex-col items-center gap-1">
                <div
                  className={cn(
                    "flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium transition-colors sm:h-10 sm:w-10",
                    currentStep === step.num
                      ? "bg-primary text-primary-foreground"
                      : currentStep > step.num
                        ? "bg-success text-white"
                        : "bg-muted text-muted-foreground"
                  )}
                >
                  {currentStep > step.num ? (
                    <CheckCircle2 className="h-4 w-4 sm:h-5 sm:w-5" />
                  ) : (
                    step.num
                  )}
                </div>
                <span
                  className={cn(
                    "text-[10px] font-medium sm:text-xs",
                    currentStep === step.num ? "text-primary" : "text-muted-foreground"
                  )}
                >
                  {step.label}
                </span>
              </div>
              {index < 2 && (
                <div
                  className={cn(
                    "h-0.5 flex-1 transition-colors",
                    currentStep > step.num ? "bg-success" : "bg-muted"
                  )}
                />
              )}
            </React.Fragment>
          ))}
        </div>

        {/* 스텝별 컨텐츠 */}
        <div className="max-h-[calc(95vh-200px)] space-y-4 overflow-y-auto">
          {/* 1단계: 파일 선택 & 미리보기 (통합) */}
          {currentStep === 1 && (
            <div className="space-y-4">
              {/* 🆕 마스터-디테일 간단 모드: 마스터 필드 입력 */}
              {hasMasterSelectFields && (
                <div className="grid gap-3 sm:grid-cols-2">
                  {masterDetailExcelConfig?.masterSelectFields?.map((field) => (
                      <div key={field.columnName} className="space-y-1">
                        <Label className="text-xs">
                          {field.columnLabel}
                          {field.required && <span className="ml-1 text-destructive">*</span>}
                        </Label>
                        {field.inputType === "entity" ? (
                          <Select
                            value={masterFieldValues[field.columnName]?.toString() || ""}
                            onValueChange={(value) => {
                              // 선택한 item 찾기
                              const selectedItem = entitySearchData[field.columnName]?.find(
                                (item: any) => item[field.referenceColumn || "id"]?.toString() === value
                              );
                              
                              // displayColumn에서 name 값도 가져오기
                              const displayColName = 
                                field.displayColumn || 
                                entityDisplayColumns[field.columnName] || 
                                field.referenceColumn || 
                                "id";
                              const displayValue = selectedItem?.[displayColName];
                              
                              // code와 name 컬럼명 추출 (예: supplier_code → supplier_name)
                              const codeColName = field.columnName; // supplier_code
                              const nameColName = codeColName.replace(/_code$/, "_name"); // supplier_name
                              
                              setMasterFieldValues((prev) => {
                                const newValues = {
                                  ...prev,
                                  [codeColName]: value,
                                };
                                
                                // _code로 끝나는 컬럼이면 _name도 함께 저장
                                if (codeColName.endsWith("_code") && displayValue) {
                                  newValues[nameColName] = displayValue;
                                  console.log(`🔗 엔티티 연동: ${codeColName}=${value}, ${nameColName}=${displayValue}`);
                                }
                                
                                return newValues;
                              });
                            }}
                          >
                            <SelectTrigger className="h-9 text-xs">
                              <SelectValue placeholder={`${field.columnLabel} 선택`} />
                            </SelectTrigger>
                            <SelectContent>
                              {entitySearchLoading[field.columnName] ? (
                                <SelectItem value="loading" disabled>
                                  로딩 중...
                                </SelectItem>
                              ) : (
                                entitySearchData[field.columnName]?.map((item: any) => {
                                  const keyValue = item[field.referenceColumn || "id"];
                                  // displayColumn: 저장된 값 → DB에서 조회한 값 → referenceColumn → id
                                  const displayColName = 
                                    field.displayColumn || 
                                    entityDisplayColumns[field.columnName] || 
                                    field.referenceColumn || 
                                    "id";
                                  const displayValue = item[displayColName] || keyValue;
                                  return (
                                    <SelectItem
                                      key={keyValue}
                                      value={keyValue?.toString()}
                                      className="text-xs"
                                    >
                                      {displayValue}
                                    </SelectItem>
                                  );
                                })
                              )}
                            </SelectContent>
                          </Select>
                        ) : field.inputType === "date" ? (
                          <input
                            type="date"
                            value={masterFieldValues[field.columnName] || ""}
                            onChange={(e) =>
                              setMasterFieldValues((prev) => ({
                                ...prev,
                                [field.columnName]: e.target.value,
                              }))
                            }
                            className="h-9 w-full rounded-md border px-3 text-xs"
                          />
                        ) : (
                          <input
                            type="text"
                            value={masterFieldValues[field.columnName] || ""}
                            onChange={(e) =>
                              setMasterFieldValues((prev) => ({
                                ...prev,
                                [field.columnName]: e.target.value,
                              }))
                            }
                            placeholder={field.columnLabel}
                            className="h-9 w-full rounded-md border px-3 text-xs"
                          />
                        )}
                      </div>
                    ))}
                </div>
              )}

              {/* 템플릿 다운로드 + 파일 선택 영역 */}
              <div>
                <div className="flex items-center justify-between">
                  <Label htmlFor="file-upload" className="text-xs sm:text-sm">
                    파일 선택 *
                  </Label>
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-7 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
                    onClick={handleDownloadTemplate}
                  >
                    <ArrowRight className="h-3 w-3 rotate-90" />
                    업로드 템플릿 다운로드
                  </Button>
                </div>
                <div
                  onDragOver={handleDragOver}
                  onDragLeave={handleDragLeave}
                  onDrop={handleDrop}
                  onClick={() => fileInputRef.current?.click()}
                  className={cn(
                    "mt-2 flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-4 transition-colors",
                    isDragOver
                      ? "border-primary bg-primary/5"
                      : file
                        ? "border-emerald-500 bg-emerald-50"
                        : "border-muted-foreground/25 hover:border-primary hover:bg-muted/50"
                  )}
                >
                  {file ? (
                    <div className="flex items-center gap-3">
                      <FileSpreadsheet className="h-8 w-8 text-emerald-600" />
                      <div>
                        <p className="text-sm font-medium text-emerald-700">{file.name}</p>
                        <p className="text-xs text-muted-foreground">
                          클릭하여 다른 파일 선택
                        </p>
                      </div>
                    </div>
                  ) : (
                    <>
                      <Upload
                        className={cn(
                          "mb-2 h-8 w-8",
                          isDragOver ? "text-primary" : "text-muted-foreground"
                        )}
                      />
                      <p
                        className={cn(
                          "text-sm font-medium",
                          isDragOver ? "text-primary" : "text-muted-foreground"
                        )}
                      >
                        {isDragOver
                          ? "파일을 놓으세요"
                          : "파일을 드래그하거나 클릭하여 선택"}
                      </p>
                      <p className="mt-1 text-xs text-muted-foreground">
                        지원 형식: .xlsx, .xls, .csv
                      </p>
                    </>
                  )}
                  <input
                    ref={fileInputRef}
                    id="file-upload"
                    type="file"
                    accept=".xlsx,.xls,.csv"
                    onChange={handleFileChange}
                    className="hidden"
                  />
                </div>
              </div>

              {/* 파일이 선택된 경우에만 미리보기 표시 */}
              {file && displayData.length > 0 && (
                <>
                  {/* 시트 선택 */}
                  <div className="flex items-center gap-3">
                    <div className="flex items-center gap-2">
                      <Label className="text-xs text-muted-foreground sm:text-sm">
                        시트:
                      </Label>
                      <Select value={selectedSheet} onValueChange={handleSheetChange}>
                        <SelectTrigger className="h-8 w-[140px] text-xs sm:h-9 sm:w-[180px] sm:text-sm">
                          <SelectValue placeholder="Sheet1" />
                        </SelectTrigger>
                        <SelectContent>
                          {sheetNames.map((sheetName) => (
                            <SelectItem
                              key={sheetName}
                              value={sheetName}
                              className="text-xs sm:text-sm"
                            >
                              {sheetName}
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                    <span className="text-xs text-muted-foreground">
                      {displayData.length}개 행 · 셀을 클릭하여 편집, Tab/Enter로 이동
                    </span>
                  </div>

                  {/* 엑셀처럼 편집 가능한 스프레드시트 */}
                  <EditableSpreadsheet
                    columns={excelColumns}
                    data={displayData}
                    onColumnsChange={(newColumns) => {
                      setExcelColumns(newColumns);
                      // 범위 재계산
                      const lastCol =
                        newColumns.length > 0
                          ? String.fromCharCode(64 + newColumns.length)
                          : "A";
                      setDetectedRange(`A1:${lastCol}${displayData.length + 1}`);
                    }}
                    onDataChange={(newData) => {
                      setDisplayData(newData);
                      setAllData(newData);
                      // 범위 재계산
                      const lastCol =
                        excelColumns.length > 0
                          ? String.fromCharCode(64 + excelColumns.length)
                          : "A";
                      setDetectedRange(`A1:${lastCol}${newData.length + 1}`);
                    }}
                    maxHeight="320px"
                  />
                </>
              )}
            </div>
          )}

          {/* 2단계: 컬럼 매핑 */}
          {currentStep === 2 && (
            <div className="space-y-4">
              {/* 상단: 제목 + 자동 매핑 버튼 */}
              <div className="flex items-center justify-between">
                <h3 className="text-sm font-semibold sm:text-base">컬럼 매핑 설정</h3>
                <Button
                  type="button"
                  variant="default"
                  size="sm"
                  onClick={handleAutoMapping}
                  className="h-8 text-xs sm:h-9 sm:text-sm"
                >
                  <Zap className="mr-2 h-4 w-4" />
                  자동 매핑
                </Button>
              </div>

              {/* 매핑 리스트 */}
              <div className="space-y-2">
                <div className="grid grid-cols-[1fr_auto_1fr_80px] gap-2 text-[10px] font-medium text-muted-foreground sm:text-xs">
                  <div>엑셀 컬럼</div>
                  <div></div>
                  <div>시스템 컬럼</div>
                  <div className="text-center">중복 키</div>
                </div>

                <div className="max-h-[300px] space-y-2 overflow-y-auto">
                  {columnMappings.map((mapping, index) => (
                    <div
                      key={index}
                      className="grid grid-cols-[1fr_auto_1fr_80px] items-center gap-2"
                    >
                      <div className="rounded-md border border-border bg-muted px-3 py-2 text-xs font-medium sm:text-sm">
                        {mapping.excelColumn}
                      </div>
                      <ArrowRight className="h-4 w-4 text-muted-foreground" />
                      <Select
                        value={mapping.systemColumn || "none"}
                        onValueChange={(value) =>
                          handleMappingChange(
                            mapping.excelColumn,
                            value === "none" ? null : value
                          )
                        }
                      >
                        <SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
                          <SelectValue placeholder="매핑 안함">
                            {mapping.systemColumn
                              ? (() => {
                                  const col = systemColumns.find(
                                    (c) => c.name === mapping.systemColumn
                                  );
                                  return col?.label || mapping.systemColumn;
                                })()
                              : "매핑 안함"}
                          </SelectValue>
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="none" className="text-xs sm:text-sm">
                            매핑 안함
                          </SelectItem>
                          {systemColumns.map((col) => {
                            const isRequired = !col.nullable && !AUTO_GENERATED_COLUMNS.includes(col.name.toLowerCase()) && !(col as any).isNumbering;
                            return (
                            <SelectItem
                              key={col.name}
                              value={col.name}
                              className="text-xs sm:text-sm"
                            >
                              {isRequired && <span className="text-destructive mr-1">*</span>}
                              {col.label || col.name} ({col.type})
                            </SelectItem>
                            );
                          })}
                        </SelectContent>
                      </Select>
                      {/* 중복 체크 체크박스 */}
                      <div className="flex justify-center">
                        {mapping.systemColumn ? (
                          <Checkbox
                            checked={mapping.checkDuplicate || false}
                            onCheckedChange={(checked) =>
                              handleDuplicateCheckChange(mapping.excelColumn, checked as boolean)
                            }
                            className="h-4 w-4"
                          />
                        ) : (
                          <span className="text-[10px] text-muted-foreground">-</span>
                        )}
                      </div>
                    </div>
                  ))}
                </div>
              </div>

              {/* 미매핑 필수(NOT NULL) 컬럼 경고 */}
              {(() => {
                const mappedCols = new Set<string>();
                columnMappings.filter((m) => m.systemColumn).forEach((m) => {
                  const n = m.systemColumn!;
                  mappedCols.add(n);
                  if (n.includes(".")) mappedCols.add(n.split(".")[1]);
                });
                const missing = systemColumns.filter((col) => {
                  const rawName = col.name.includes(".") ? col.name.split(".")[1] : col.name;
                  if (AUTO_GENERATED_COLUMNS.includes(rawName.toLowerCase())) return false;
                  if (col.nullable) return false;
                  if (mappedCols.has(col.name) || mappedCols.has(rawName)) return false;
                  if ((col as any).isNumbering) return false;
                  return true;
                });
                if (missing.length === 0) return null;
                return (
                  <div className="rounded-md border border-destructive/50 bg-destructive/10 p-3">
                    <div className="flex items-start gap-2">
                      <AlertCircle className="mt-0.5 h-4 w-4 text-destructive" />
                      <div className="text-[10px] text-destructive sm:text-xs">
                        <p className="font-medium">필수(NOT NULL) 컬럼이 매핑되지 않았습니다:</p>
                        <p className="mt-1">
                          {missing.map((c) => c.label || c.name).join(", ")}
                        </p>
                      </div>
                    </div>
                  </div>
                );
              })()}

              {/* 중복 체크 안내 */}
              {duplicateCheckCount > 0 ? (
                <div className="rounded-md border border-primary/20 bg-primary/10 p-3">
                  <div className="flex items-start justify-between gap-4">
                    <div className="flex items-start gap-2">
                      <Copy className="mt-0.5 h-4 w-4 text-primary" />
                      <div className="text-[10px] text-primary sm:text-xs">
                        <p className="font-medium">
                          중복 키: {columnMappings
                            .filter((m) => m.checkDuplicate && m.systemColumn)
                            .map((m) => {
                              const col = systemColumns.find((c) => c.name === m.systemColumn);
                              return col?.label || m.systemColumn;
                            })
                            .join(" + ")}
                        </p>
                        <p className="mt-1">
                          위 컬럼 값이 모두 일치하는 기존 데이터가 있으면 중복으로 처리합니다.
                        </p>
                      </div>
                    </div>
                    <div className="flex items-center gap-2 shrink-0">
                      <span className="text-[10px] text-primary sm:text-xs">중복 시:</span>
                      <Select
                        value={duplicateAction}
                        onValueChange={(value) => setDuplicateAction(value as "overwrite" | "skip")}
                      >
                        <SelectTrigger className="h-7 w-[100px] text-[10px] sm:text-xs border-primary/40 bg-white">
                          <SelectValue />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="skip" className="text-xs">건너뛰기</SelectItem>
                          <SelectItem value="overwrite" className="text-xs">덮어쓰기</SelectItem>
                        </SelectContent>
                      </Select>
                    </div>
                  </div>
                </div>
              ) : (
                <div className="rounded-md border border-muted bg-muted/30 p-3">
                  <div className="flex items-start gap-2">
                    <Copy className="mt-0.5 h-4 w-4 text-muted-foreground" />
                    <div className="text-[10px] text-muted-foreground sm:text-xs">
                      <p className="font-medium">중복 체크 (선택사항)</p>
                      <p className="mt-1">
                        "중복 키" 체크박스를 선택하면 해당 컬럼 값으로 기존 데이터와 비교합니다.
                        여러 컬럼을 선택하면 복합 키로 중복을 판단합니다.
                      </p>
                    </div>
                  </div>
                </div>
              )}

              {/* 매핑 자동 저장 안내 */}
              {isAutoMappingLoaded ? (
                <div className="rounded-md border border-success bg-success/10 p-3">
                  <div className="flex items-start gap-2">
                    <CheckCircle2 className="mt-0.5 h-4 w-4 text-success" />
                    <div className="text-[10px] text-success sm:text-xs">
                      <p className="font-medium">이전 매핑이 자동 적용됨</p>
                      <p className="mt-1">
                        동일한 엑셀 구조가 감지되어 이전에 저장된 매핑이 적용되었습니다.
                        수정하면 업로드 시 자동 저장됩니다.
                      </p>
                    </div>
                  </div>
                </div>
              ) : (
                <div className="rounded-md border border-muted bg-muted/30 p-3">
                  <div className="flex items-start gap-2">
                    <Zap className="mt-0.5 h-4 w-4 text-muted-foreground" />
                    <div className="text-[10px] text-muted-foreground sm:text-xs">
                      <p className="font-medium">새로운 엑셀 구조</p>
                      <p className="mt-1">
                        이 엑셀 구조는 처음입니다. 매핑을 설정하면 다음에 같은 구조의
                        엑셀에 자동 적용됩니다.
                      </p>
                    </div>
                  </div>
                </div>
              )}
            </div>
          )}

          {/* 3단계: 확인 */}
          {currentStep === 3 && (
            <div className="space-y-4">
              <div className="rounded-md border border-border bg-muted/50 p-4">
                <h3 className="text-sm font-medium sm:text-base">업로드 요약</h3>
                <div className="mt-2 space-y-1 text-[10px] text-muted-foreground sm:text-xs">
                  <p>
                    <span className="font-medium">파일:</span> {file?.name}
                  </p>
                  <p>
                    <span className="font-medium">시트:</span> {selectedSheet}
                  </p>
                  <p>
                    <span className="font-medium">데이터 행:</span> {allData.length}개
                  </p>
                  <p>
                    <span className="font-medium">테이블:</span> {tableName}
                  </p>
                  <p>
                    <span className="font-medium">모드:</span>{" "}
                    {uploadMode === "insert"
                      ? "신규 등록"
                      : uploadMode === "update"
                        ? "업데이트"
                        : "Upsert"}
                  </p>
                </div>
              </div>

              {/* 데이터 검증 결과 */}
              {validationResult && !validationResult.isValid && (
                <div className="space-y-3">
                  {/* NOT NULL 에러 */}
                  {validationResult.notNullErrors.length > 0 && (
                    <div className="rounded-md border border-destructive bg-destructive/10 p-4">
                      <h3 className="flex items-center gap-2 text-sm font-medium text-destructive sm:text-base">
                        <XCircle className="h-4 w-4" />
                        필수값 누락 ({validationResult.notNullErrors.length}건)
                      </h3>
                      <div className="mt-2 max-h-[150px] space-y-0.5 overflow-y-auto text-[10px] text-destructive sm:text-xs">
                        {(() => {
                          const grouped = new Map<string, number[]>();
                          for (const err of validationResult.notNullErrors) {
                            const key = err.label;
                            if (!grouped.has(key)) grouped.set(key, []);
                            grouped.get(key)!.push(err.row);
                          }
                          return Array.from(grouped).map(([label, rows]) => (
                            <p key={label}>
                              <span className="font-medium">{label}</span>: {rows.length > 5 ? `행 ${rows.slice(0, 5).join(", ")} 외 ${rows.length - 5}건` : `행 ${rows.join(", ")}`}
                            </p>
                          ));
                        })()}
                      </div>
                    </div>
                  )}

                  {/* 엑셀 내부 중복 */}
                  {validationResult.uniqueInExcelErrors.length > 0 && (
                    <div className="rounded-md border border-warning bg-warning/10 p-4">
                      <h3 className="flex items-center gap-2 text-sm font-medium text-warning sm:text-base">
                        <AlertCircle className="h-4 w-4" />
                        엑셀 내 중복 ({validationResult.uniqueInExcelErrors.length}건)
                      </h3>
                      <div className="mt-2 max-h-[150px] space-y-0.5 overflow-y-auto text-[10px] text-warning sm:text-xs">
                        {validationResult.uniqueInExcelErrors.slice(0, 10).map((err, i) => (
                          <p key={i}>
                            <span className="font-medium">{err.label}</span> "{err.value}": 행 {err.rows.join(", ")}
                          </p>
                        ))}
                        {validationResult.uniqueInExcelErrors.length > 10 && (
                          <p className="font-medium">...외 {validationResult.uniqueInExcelErrors.length - 10}건</p>
                        )}
                      </div>
                    </div>
                  )}

                  {/* DB 기존 데이터 중복 */}
                  {validationResult.uniqueInDbErrors.length > 0 && (
                    <div className={cn(
                      "rounded-md border p-4",
                      dbDuplicateAction
                        ? "border-primary/30 bg-primary/5"
                        : "border-destructive bg-destructive/10"
                    )}>
                      <div className="flex items-start justify-between gap-3">
                        <h3 className={cn(
                          "flex items-center gap-2 text-sm font-medium sm:text-base",
                          dbDuplicateAction ? "text-primary" : "text-destructive"
                        )}>
                          {dbDuplicateAction ? <CheckCircle2 className="h-4 w-4" /> : <XCircle className="h-4 w-4" />}
                          DB 기존 데이터와 중복 ({validationResult.uniqueInDbErrors.length}건)
                        </h3>
                        <div className="flex items-center gap-2 shrink-0">
                          <span className={cn(
                            "text-[10px] sm:text-xs",
                            dbDuplicateAction ? "text-primary" : "text-destructive"
                          )}>
                            중복 시:
                          </span>
                          <Select
                            value={dbDuplicateAction || ""}
                            onValueChange={(value) => setDbDuplicateAction(value as "overwrite" | "skip")}
                          >
                            <SelectTrigger className={cn(
                              "h-7 w-[100px] text-[10px] sm:text-xs",
                              dbDuplicateAction
                                ? "border-primary/40 bg-white"
                                : "border-destructive/40 bg-white animate-pulse"
                            )}>
                              <SelectValue placeholder="선택" />
                            </SelectTrigger>
                            <SelectContent>
                              <SelectItem value="skip" className="text-xs">건너뛰기</SelectItem>
                              <SelectItem value="overwrite" className="text-xs">덮어쓰기</SelectItem>
                            </SelectContent>
                          </Select>
                        </div>
                      </div>
                      <div className={cn(
                        "mt-2 max-h-[150px] space-y-0.5 overflow-y-auto text-[10px] sm:text-xs",
                        dbDuplicateAction ? "text-primary/80" : "text-destructive"
                      )}>
                        {(() => {
                          const grouped = new Map<string, { value: string; rows: number[] }[]>();
                          for (const err of validationResult.uniqueInDbErrors) {
                            const key = err.label;
                            if (!grouped.has(key)) grouped.set(key, []);
                            const existing = grouped.get(key)!.find((e) => e.value === err.value);
                            if (existing) existing.rows.push(err.row);
                            else grouped.get(key)!.push({ value: err.value, rows: [err.row] });
                          }
                          return Array.from(grouped).map(([label, items]) => (
                            <div key={label}>
                              {items.slice(0, 5).map((item, i) => (
                                <p key={i}>
                                  <span className="font-medium">{label}</span> &quot;{item.value}&quot;: 행 {item.rows.join(", ")}
                                </p>
                              ))}
                              {items.length > 5 && <p className="font-medium">...외 {items.length - 5}건</p>}
                            </div>
                          ));
                        })()}
                      </div>
                      {dbDuplicateAction && (
                        <p className="mt-2 text-[10px] text-primary sm:text-xs font-medium">
                          {dbDuplicateAction === "skip"
                            ? "중복 데이터는 건너뛰고 신규 데이터만 업로드합니다."
                            : "중복 데이터는 새 값으로 덮어씁니다."}
                        </p>
                      )}
                    </div>
                  )}
                </div>
              )}

              {validationResult?.isValid && (
                <div className="rounded-md border border-success bg-success/10 p-4">
                  <h3 className="flex items-center gap-2 text-sm font-medium text-success sm:text-base">
                    <CheckCircle2 className="h-4 w-4" />
                    데이터 검증 통과
                  </h3>
                  <p className="mt-1 text-[10px] text-success sm:text-xs">
                    필수값 및 중복 검사를 통과했습니다.
                  </p>
                </div>
              )}

              <div className="rounded-md border border-border bg-muted/50 p-4">
                <h3 className="text-sm font-medium sm:text-base">컬럼 매핑</h3>
                <div className="mt-2 space-y-1 text-[10px] text-muted-foreground sm:text-xs">
                  {columnMappings
                    .filter((m) => m.systemColumn)
                    .map((mapping, index) => {
                      const col = systemColumns.find(
                        (c) => c.name === mapping.systemColumn
                      );
                      return (
                        <p key={index}>
                          <span className="font-medium">{mapping.excelColumn}</span> →{" "}
                          {col?.label || mapping.systemColumn}
                          {mapping.checkDuplicate && (
                            <span className="ml-2 text-primary">
                              (중복 체크: {mapping.duplicateAction === "overwrite" ? "덮어쓰기" : "건너뛰기"})
                            </span>
                          )}
                        </p>
                      );
                    })}
                  {columnMappings.filter((m) => m.systemColumn).length === 0 && (
                    <p className="text-destructive">매핑된 컬럼이 없습니다.</p>
                  )}
                </div>
              </div>

              {/* 중복 체크 요약 */}
              {duplicateCheckCount > 0 && (
                <div className="rounded-md border border-primary/20 bg-primary/10 p-4">
                  <h3 className="text-sm font-medium text-primary sm:text-base">중복 체크 설정</h3>
                  <div className="mt-2 space-y-1 text-[10px] text-primary sm:text-xs">
                    <p>
                      <span className="font-medium">중복 키:</span>{" "}
                      {columnMappings
                        .filter((m) => m.checkDuplicate && m.systemColumn)
                        .map((m) => {
                          const col = systemColumns.find((c) => c.name === m.systemColumn);
                          return col?.label || m.systemColumn;
                        })
                        .join(" + ")}
                    </p>
                    <p>
                      <span className="font-medium">중복 시 처리:</span>{" "}
                      {duplicateAction === "overwrite" ? "덮어쓰기 (기존 데이터 업데이트)" : "건너뛰기 (해당 행 무시)"}
                    </p>
                  </div>
                </div>
              )}

              <div className="rounded-md border border-warning bg-warning/10 p-3">
                <div className="flex items-start gap-2">
                  <AlertCircle className="mt-0.5 h-4 w-4 text-warning" />
                  <div className="text-[10px] text-warning sm:text-xs">
                    <p className="font-medium">주의사항</p>
                    <p className="mt-1">
                      업로드를 진행하면 데이터가 데이터베이스에 저장됩니다.
                      계속하시겠습니까?
                    </p>
                  </div>
                </div>
              </div>
            </div>
          )}
        </div>

        <DialogFooter className="gap-2 sm:gap-0">
          <Button
            variant="outline"
            onClick={currentStep === 1 ? () => onOpenChange(false) : handlePrevious}
            disabled={isUploading}
            className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
          >
            {currentStep === 1 ? "취소" : "이전"}
          </Button>
          {currentStep < 3 ? (
            <Button
              onClick={handleNext}
              disabled={isUploading || isCategoryValidating || isDataValidating || (currentStep === 1 && !file)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              {isCategoryValidating || isDataValidating ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  검증 중...
                </>
              ) : (
                "다음"
              )}
            </Button>
          ) : (
            <Button
              onClick={handleUpload}
              disabled={
                isUploading ||
                columnMappings.filter((m) => m.systemColumn).length === 0 ||
                (validationResult !== null && !validationResult.isValid && !(
                  validationResult.notNullErrors.length === 0 &&
                  validationResult.uniqueInExcelErrors.length === 0 &&
                  validationResult.uniqueInDbErrors.length > 0 &&
                  dbDuplicateAction !== null
                ))
              }
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              {isUploading ? "업로드 중..." :
                validationResult && !validationResult.isValid && !(
                  validationResult.notNullErrors.length === 0 &&
                  validationResult.uniqueInExcelErrors.length === 0 &&
                  validationResult.uniqueInDbErrors.length > 0 &&
                  dbDuplicateAction !== null
                ) ? "검증 실패 - 이전으로 돌아가 수정" :
                dbDuplicateAction === "skip" ? "업로드 (중복 건너뛰기)" :
                dbDuplicateAction === "overwrite" ? "업로드 (중복 덮어쓰기)" : "업로드"}
            </Button>
          )}
        </DialogFooter>
      </DialogContent>
    </Dialog>

    {/* 카테고리 대체값 선택 다이얼로그 */}
    <Dialog open={showCategoryValidation} onOpenChange={(open) => {
      if (!open) {
        setShowCategoryValidation(false);
        setCategoryMismatches({});
      }
    }}>
      <DialogContent className="max-w-[95vw] sm:max-w-[600px]">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2 text-base sm:text-lg">
            <AlertCircle className="h-5 w-5 text-warning" />
            존재하지 않는 카테고리 값 감지
          </DialogTitle>
          <DialogDescription className="text-xs sm:text-sm">
            엑셀 데이터에 등록되지 않은 카테고리 값이 있습니다. 각 항목에 대해 대체할 값을 선택해주세요.
          </DialogDescription>
        </DialogHeader>

        <div className="max-h-[400px] space-y-4 overflow-y-auto pr-1">
          {Object.entries(categoryMismatches).map(([key, items]) => {
            const [columnName, displayName] = key.split("|||");
            return (
              <div key={key} className="space-y-2">
                <h4 className="text-sm font-semibold text-foreground">
                  {displayName || columnName}
                </h4>
                {items.map((item, idx) => (
                  <div
                    key={`${key}-${idx}`}
                    className="grid grid-cols-[1fr_auto_1fr] items-center gap-2 rounded-md border border-border bg-muted/30 p-2"
                  >
                    <div className="flex flex-col">
                      <span className="text-xs font-medium text-destructive line-through">
                        {item.invalidValue}
                      </span>
                      <span className="text-[10px] text-muted-foreground">
                        {item.rowIndices.length}건
                      </span>
                    </div>
                    <ArrowRight className="h-4 w-4 text-muted-foreground" />
                    <Popover>
                      <PopoverTrigger asChild>
                        <Button
                          variant="outline"
                          role="combobox"
                          className="h-8 w-full justify-between text-xs sm:h-9 sm:text-sm"
                        >
                          <span className="truncate">
                            {item.replacement
                              ? item.validOptions.find((o) => o.code === item.replacement)?.label || item.replacement
                              : "대체 값 선택"}
                          </span>
                          <ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
                        </Button>
                      </PopoverTrigger>
                      <PopoverContent className="w-[260px] p-0" align="start">
                        <Command
                          filter={(value, search) => {
                            const opt = item.validOptions.find((o) => o.code === value);
                            if (!opt) return 0;
                            const s = search.toLowerCase();
                            if (opt.label.toLowerCase().includes(s)) return 1;
                            if (opt.ancestors.some((a) => a.toLowerCase().includes(s))) return 1;
                            return 0;
                          }}
                        >
                          <CommandInput placeholder="카테고리 검색..." className="text-xs" />
                          <CommandList className="max-h-52">
                            <CommandEmpty className="py-3 text-xs">찾을 수 없습니다</CommandEmpty>
                            <CommandGroup>
                              {item.validOptions.map((opt) => (
                                <CommandItem
                                  key={opt.code}
                                  value={opt.code}
                                  onSelect={(val) => {
                                    setCategoryMismatches((prev) => {
                                      const updated = { ...prev };
                                      updated[key] = updated[key].map((it, i) =>
                                        i === idx ? { ...it, replacement: val } : it
                                      );
                                      return updated;
                                    });
                                  }}
                                  className="text-xs sm:text-sm"
                                >
                                  <Check className={cn("mr-2 h-3 w-3", item.replacement === opt.code ? "opacity-100" : "opacity-0")} />
                                  <span style={{ paddingLeft: `${opt.depth * 12}px` }}>
                                    {opt.depth > 0 && <span className="mr-1 text-muted-foreground">ㄴ</span>}
                                    {opt.label}
                                  </span>
                                </CommandItem>
                              ))}
                            </CommandGroup>
                          </CommandList>
                        </Command>
                      </PopoverContent>
                    </Popover>
                  </div>
                ))}
              </div>
            );
          })}
        </div>

        <DialogFooter className="gap-2 sm:gap-0">
          <Button
            variant="outline"
            onClick={() => {
              setShowCategoryValidation(false);
              setCategoryMismatches({});
            }}
            className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
          >
            취소
          </Button>
          <Button
            onClick={applyCategoryReplacements}
            className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
          >
            적용
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  </>
  );
};
