"use client";

import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { useSearchParams } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
// Card 컴포넌트 제거 - 외부 박스 없이 직접 렌더링
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Separator } from "@/components/ui/separator";
import {
  Search,
  ChevronLeft,
  ChevronRight,
  RotateCcw,
  Database,
  Loader2,
  Plus,
  Edit,
  Trash2,
  File,
  Download,
  Eye,
  X,
  ZoomIn,
  ZoomOut,
  RotateCw,
  Folder,
  FolderOpen,
  Grid,
  Filter,
} from "lucide-react";
import { tableTypeApi } from "@/lib/api/screen";
import { commonCodeApi } from "@/lib/api/commonCode";
import { apiClient, getCurrentUser, UserInfo, getFullImageUrl } from "@/lib/api/client";
import { useAuth } from "@/hooks/useAuth";
import { DataTableComponent, DataTableColumn, DataTableFilter } from "@/types/screen-legacy-backup";
import { cn } from "@/lib/utils";
import { downloadFile, getLinkedFiles, getFilePreviewUrl, getDirectFileUrl } from "@/lib/api/file";
import { toast } from "sonner";
import { FileUpload } from "@/components/screen/widgets/FileUpload";
import { AdvancedSearchFilters } from "./filters/AdvancedSearchFilters";
import { SaveModal } from "./SaveModal";
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
import { useTableOptions } from "@/contexts/TableOptionsContext";
import { TableFilter, ColumnVisibility } from "@/types/table-options";
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
import { useScreenContextOptional } from "@/contexts/ScreenContext";
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
import { CascadingDropdownConfig } from "@/types/screen-management";

/**
 * 🔗 연쇄 드롭다운 컴포넌트 (폼 내부용)
 */
interface CascadingDropdownInFormProps {
  config: CascadingDropdownConfig;
  parentValue?: string | number | null;
  value?: string;
  onChange?: (value: string) => void;
  placeholder?: string;
  className?: string;
}

const CascadingDropdownInForm: React.FC<CascadingDropdownInFormProps> = ({
  config,
  parentValue,
  value,
  onChange,
  placeholder,
  className,
}) => {
  const { options, loading } = useCascadingDropdown({
    config,
    parentValue,
  });

  const getPlaceholder = () => {
    if (!parentValue) {
      return config.emptyParentMessage || "상위 항목을 먼저 선택하세요";
    }
    if (loading) {
      return config.loadingMessage || "로딩 중...";
    }
    if (options.length === 0) {
      return config.noOptionsMessage || "선택 가능한 항목이 없습니다";
    }
    return placeholder || "선택하세요";
  };

  const isDisabled = !parentValue || loading;

  return (
    <Select value={value || ""} onValueChange={(newValue) => onChange?.(newValue)} disabled={isDisabled}>
      <SelectTrigger className={className}>
        {loading ? (
          <div className="flex items-center gap-2">
            <Loader2 className="h-4 w-4 animate-spin" />
            <span className="text-muted-foreground text-sm">로딩 중...</span>
          </div>
        ) : (
          <SelectValue placeholder={getPlaceholder()} />
        )}
      </SelectTrigger>
      <SelectContent>
        {options.length === 0 ? (
          <div className="text-muted-foreground px-2 py-4 text-center text-sm">
            {!parentValue
              ? config.emptyParentMessage || "상위 항목을 먼저 선택하세요"
              : config.noOptionsMessage || "선택 가능한 항목이 없습니다"}
          </div>
        ) : (
          options.map((option) => (
            <SelectItem key={option.value} value={option.value}>
              {option.label}
            </SelectItem>
          ))
        )}
      </SelectContent>
    </Select>
  );
};

// 파일 데이터 타입 정의 (AttachedFileInfo와 호환)
interface FileInfo {
  // AttachedFileInfo 기본 속성들
  objid: string;
  savedFileName: string;
  realFileName: string;
  fileSize: number;
  fileExt: string;
  filePath: string;
  docType: string;
  docTypeName: string;
  targetObjid: string;
  parentTargetObjid?: string;
  companyCode: string;
  writer: string;
  regdate: string;
  status: string;

  // 추가 호환성 속성들
  path?: string; // filePath와 동일
  name?: string; // realFileName과 동일
  id?: string; // objid와 동일
  size?: number; // fileSize와 동일
  type?: string; // docType과 동일
  uploadedAt?: string; // regdate와 동일
}

interface FileColumnData {
  files: FileInfo[];
  totalCount: number;
  totalSize: number;
  lastModified: string;
}

interface InteractiveDataTableProps {
  component: DataTableComponent;
  className?: string;
  style?: React.CSSProperties;
  onRefresh?: () => void; // 테이블 새로고침 콜백
}

export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
  component,
  className = "",
  style = {},
  onRefresh,
}) => {
  const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
  const { user } = useAuth(); // 사용자 정보 가져오기
  const { registerTable, unregisterTable } = useTableOptions(); // Context 훅
  const splitPanelContext = useSplitPanelContext(); // 분할 패널 컨텍스트
  const screenContext = useScreenContextOptional(); // 화면 컨텍스트 (좌측/우측 위치 확인용)
  const splitPanelPosition = screenContext?.splitPanelPosition; // 분할 패널 내 위치

  // URL에서 menuObjid 가져오기 (카테고리 값 조회 시 필요)
  const searchParams = useSearchParams();
  const menuObjid = useMemo(() => {
    // 1. ScreenContext에서 가져오기
    if (screenContext?.menuObjid) return screenContext.menuObjid;
    // 2. URL 쿼리에서 가져오기
    const urlMenuObjid = searchParams.get("menuObjid");
    return urlMenuObjid ? parseInt(urlMenuObjid) : undefined;
  }, [screenContext?.menuObjid, searchParams]);

  const [data, setData] = useState<Record<string, any>[]>([]);
  const [loading, setLoading] = useState(false);
  const [searchValues, setSearchValues] = useState<Record<string, any>>({});
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);
  const [total, setTotal] = useState(0);
  const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
  const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
  const hasInitializedWidthsRef = useRef(false);
  const columnRefs = useRef<Record<string, HTMLTableCellElement | null>>({});
  const isResizingRef = useRef(false);

  // TableOptions 상태
  const [filters, setFilters] = useState<TableFilter[]>([]);
  const [grouping, setGrouping] = useState<string[]>([]);
  const [columnVisibility, setColumnVisibility] = useState<ColumnVisibility[]>([]);

  // SaveModal 상태 (등록/수정 통합)
  const [showSaveModal, setShowSaveModal] = useState(false);
  const [saveModalData, setSaveModalData] = useState<Record<string, any> | undefined>(undefined);
  const [saveModalScreenId, setSaveModalScreenId] = useState<number | undefined>(undefined);

  // 이미지 미리보기 상태
  const [previewImage, setPreviewImage] = useState<FileInfo | null>(null);
  const [showPreviewModal, setShowPreviewModal] = useState(false);
  const [zoom, setZoom] = useState(1);
  const [rotation, setRotation] = useState(0);
  const [imageLoadError, setImageLoadError] = useState(false);
  const [alternativeImageUrl, setAlternativeImageUrl] = useState<string | null>(null);

  // 파일 관리 상태
  const [fileStatusMap, setFileStatusMap] = useState<Record<string, { hasFiles: boolean; fileCount: number }>>({}); // 행별 파일 상태
  const [showFileManagementModal, setShowFileManagementModal] = useState(false);
  const [selectedRowForFiles, setSelectedRowForFiles] = useState<Record<string, any> | null>(null);
  const [selectedColumnForFiles, setSelectedColumnForFiles] = useState<DataTableColumn | null>(null); // 선택된 컬럼 정보
  const [linkedFiles, setLinkedFiles] = useState<any[]>([]);

  // 공통코드 관리 상태
  const [codeOptions, setCodeOptions] = useState<Record<string, Array<{ value: string; label: string }>>>({});

  // 🆕 검색 필터 관련 상태 (FlowWidget과 동일)
  const [searchFilterColumns, setSearchFilterColumns] = useState<Set<string>>(new Set()); // 검색 필터로 사용할 컬럼
  const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false); // 필터 설정 다이얼로그
  const [allAvailableColumns, setAllAvailableColumns] = useState<string[]>([]); // 전체 컬럼 목록
  const [filteredData, setFilteredData] = useState<any[]>([]); // 필터링된 데이터
  const [columnLabels, setColumnLabels] = useState<Record<string, string>>({}); // 컬럼명 -> 라벨 매핑

  // 카테고리 값 매핑 캐시 (컬럼명 -> {코드 -> {라벨, 색상}})
  const [categoryMappings, setCategoryMappings] = useState<
    Record<string, Record<string, { label: string; color?: string }>>
  >({});

  // 카테고리 코드 라벨 캐시 (CATEGORY_* 코드 -> 라벨)
  const [categoryCodeLabels, setCategoryCodeLabels] = useState<Record<string, string>>({});

  // 테이블 등록 (Context에 등록)
  const tableId = `datatable-${component.id}`;

  useEffect(() => {
    if (!component.tableName || !component.columns) return;

    registerTable({
      tableId,
      label: component.title || "데이터 테이블",
      tableName: component.tableName,
      columns: component.columns.map((col) => ({
        columnName: col.columnName,
        columnLabel: col.label,
        inputType: col.widgetType || "text",
        visible: col.visible !== false,
        width: (col as any).width || 150,
        sortable: col.sortable,
        filterable: col.filterable !== false,
      })),
      onFilterChange: setFilters,
      onGroupChange: setGrouping,
      onColumnVisibilityChange: setColumnVisibility,
    });

    return () => unregisterTable(tableId);
  }, [component.id, component.tableName, component.columns, component.title]);

  // 공통코드 옵션 가져오기
  const loadCodeOptions = useCallback(
    async (categoryCode: string) => {
      if (codeOptions[categoryCode]) {
        return codeOptions[categoryCode]; // 이미 로드된 경우 캐시된 데이터 사용
      }

      try {
        const response = await commonCodeApi.options.getOptions(categoryCode);
        if (response.success && response.data) {
          const options = response.data.map((code) => ({
            value: code.value,
            label: code.label,
          }));

          setCodeOptions((prev) => ({
            ...prev,
            [categoryCode]: options,
          }));

          return options;
        }
      } catch (error) {
        // console.error(`공통코드 옵션 로드 실패: ${categoryCode}`, error);
      }

      return [];
    },
    [codeOptions],
  );

  // 데이터 로드 함수 (useEffect보다 먼저 선언해야 함 - TS2448 방지)
  const loadData = useCallback(
    async (page: number = 1, searchParams: Record<string, any> = {}) => {
      if (!component.tableName) return;

      // 프리뷰 모드에서는 샘플 데이터만 표시
      if (isPreviewMode) {
        const sampleData = Array.from({ length: 3 }, (_, i) => {
          const sample: Record<string, any> = { id: i + 1 };
          component.columns.forEach((col) => {
            if (col.widgetType === "number") {
              sample[col.columnName] = Math.floor(Math.random() * 1000);
            } else if (col.widgetType === "boolean") {
              sample[col.columnName] = i % 2 === 0 ? "Y" : "N";
            } else {
              sample[col.columnName] = `샘플 ${col.label} ${i + 1}`;
            }
          });
          return sample;
        });
        setData(sampleData);
        setTotal(3);
        setTotalPages(1);
        setCurrentPage(1);
        setLoading(false);
        return;
      }

      setLoading(true);
      try {
        // 🆕 연결 필터 값 가져오기 (분할 패널 내부일 때)
        let linkedFilterValues: Record<string, any> = {};
        let hasLinkedFiltersConfigured = false; // 연결 필터가 설정되어 있는지 여부
        let hasSelectedLeftData = false; // 좌측에서 데이터가 선택되었는지 여부

        if (splitPanelContext) {
          // 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지)
          const linkedFiltersConfig = splitPanelContext.linkedFilters || [];
          hasLinkedFiltersConfigured = linkedFiltersConfig.some(
            (filter) =>
              filter.targetColumn?.startsWith(component.tableName + ".") || filter.targetColumn === component.tableName,
          );

          // 좌측 데이터 선택 여부 확인
          hasSelectedLeftData =
            splitPanelContext.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0;

          linkedFilterValues = splitPanelContext.getLinkedFilterValues();
          // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서)
          const tableSpecificFilters: Record<string, any> = {};
          for (const [key, value] of Object.entries(linkedFilterValues)) {
            // key가 "테이블명.컬럼명" 형식인 경우
            if (key.includes(".")) {
              const [tableName, columnName] = key.split(".");
              if (tableName === component.tableName) {
                tableSpecificFilters[columnName] = value;
                hasLinkedFiltersConfigured = true; // 이 테이블에 대한 필터가 있음
              }
            } else {
              // 테이블명 없이 컬럼명만 있는 경우 그대로 사용
              tableSpecificFilters[key] = value;
            }
          }
          linkedFilterValues = tableSpecificFilters;
        }

        // 🆕 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우
        // → 빈 데이터 표시 (모든 데이터를 보여주지 않음)
        if (hasLinkedFiltersConfigured && !hasSelectedLeftData) {
          console.log("⚠️ [InteractiveDataTable] 연결 필터 설정됨 but 좌측 데이터 미선택 → 빈 데이터 표시");
          setData([]);
          setTotal(0);
          setTotalPages(0);
          setCurrentPage(1);
          setLoading(false);
          return;
        }

        // 🆕 RelatedDataButtons 필터 적용
        const relatedButtonFilterValues: Record<string, any> = {};
        if (relatedButtonFilter) {
          relatedButtonFilterValues[relatedButtonFilter.filterColumn] = relatedButtonFilter.filterValue;
        }

        // 검색 파라미터와 연결 필터 병합
        const mergedSearchParams = {
          ...searchParams,
          ...linkedFilterValues,
          ...relatedButtonFilterValues, // 🆕 RelatedDataButtons 필터 추가
        };

        const currentPageSize = component.pagination?.pageSize || 10;

        console.log("🔍 데이터 조회 시작:", {
          tableName: component.tableName,
          page,
          pageSize: currentPageSize,
          linkedFilterValues,
          relatedButtonFilterValues,
          mergedSearchParams,
        });

        const result = await tableTypeApi.getTableData(component.tableName, {
          page,
          size: currentPageSize,
          search: mergedSearchParams,
          autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달
        });

        console.log("✅ 데이터 조회 완료:", {
          tableName: component.tableName,
          dataLength: result.data.length,
          total: result.total,
          page: result.page,
        });

        setData(result.data);
        setTotal(result.total);
        setTotalPages(result.totalPages);
        setCurrentPage(result.page);

        // 카테고리 코드 패턴(CATEGORY_*) 검출 및 라벨 조회
        const detectAndLoadCategoryLabels = async () => {
          const categoryCodes = new Set<string>();
          result.data.forEach((row: Record<string, any>) => {
            Object.values(row).forEach((value) => {
              if (typeof value === "string" && value.startsWith("CATEGORY_")) {
                categoryCodes.add(value);
              }
            });
          });

          console.log("🏷️ [InteractiveDataTable] 감지된 카테고리 코드:", Array.from(categoryCodes));

          // 새로운 카테고리 코드만 필터링 (이미 캐시된 것 제외)
          const newCodes = Array.from(categoryCodes);

          if (newCodes.length > 0) {
            try {
              console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 호출:", newCodes);
              const response = await apiClient.post("/table-categories/labels-by-codes", { valueCodes: newCodes });
              console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 응답:", response.data);
              if (response.data.success && response.data.data) {
                setCategoryCodeLabels((prev) => {
                  const newLabels = {
                    ...prev,
                    ...response.data.data,
                  };
                  console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 캐시 업데이트:", newLabels);
                  return newLabels;
                });
              }
            } catch (error) {
              console.error("카테고리 라벨 조회 실패:", error);
            }
          }
        };

        detectAndLoadCategoryLabels();

        // 각 행의 파일 상태 확인 (전체 행 + 가상 파일 컬럼별)
        const fileStatusPromises = result.data.map(async (rowData: Record<string, any>) => {
          const primaryKeyField = Object.keys(rowData)[0];
          const recordId = rowData[primaryKeyField];

          if (!recordId) return { rowKey: recordId, statuses: {} };

          try {
            const fileResponse = await getLinkedFiles(component.tableName, recordId);
            const allFiles = fileResponse.files || [];

            // 전체 행에 대한 파일 상태
            const rowStatus = {
              hasFiles: allFiles.length > 0,
              fileCount: allFiles.length,
            };

            // 가상 파일 컬럼별 파일 상태
            const columnStatuses: Record<string, { hasFiles: boolean; fileCount: number }> = {};

            // 가상 파일 컬럼 찾기
            const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn);

            virtualFileColumns.forEach((column) => {
              // 해당 컬럼의 파일만 필터링 (targetObjid로 수정)
              let columnFiles = allFiles.filter((file: any) => file.targetObjid?.endsWith(`:${column.columnName}`));

              // fallback: 컬럼명으로 찾지 못한 경우 모든 파일 컬럼 파일 포함
              if (columnFiles.length === 0) {
                columnFiles = allFiles.filter((file: any) =>
                  file.targetObjid?.startsWith(`${component.tableName}:${recordId}:file_column_`),
                );
              }

              const columnKey = `${recordId}_${column.columnName}`;
              columnStatuses[columnKey] = {
                hasFiles: columnFiles.length > 0,
                fileCount: columnFiles.length,
              };
            });

            return {
              rowKey: recordId,
              statuses: {
                [recordId]: rowStatus, // 전체 행 상태
                ...columnStatuses, // 컬럼별 상태
              },
            };
          } catch {
            // 에러 시 기본값
            const defaultStatuses: Record<string, { hasFiles: boolean; fileCount: number }> = {
              [recordId]: { hasFiles: false, fileCount: 0 },
            };

            // 가상 파일 컬럼에 대해서도 기본값 설정
            const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn);
            virtualFileColumns.forEach((column) => {
              const columnKey = `${recordId}_${column.columnName}`;
              defaultStatuses[columnKey] = { hasFiles: false, fileCount: 0 };
            });

            return { rowKey: recordId, statuses: defaultStatuses };
          }
        });

        // 파일 상태 업데이트
        Promise.all(fileStatusPromises).then((results) => {
          const statusMap: Record<string, { hasFiles: boolean; fileCount: number }> = {};

          results.forEach((result) => {
            Object.assign(statusMap, result.statuses);
          });

          setFileStatusMap(statusMap);
        });
      } catch (error) {
        // console.error("❌ 테이블 데이터 조회 실패:", error);
        setData([]);
        setTotal(0);
        setTotalPages(1);
      } finally {
        setLoading(false);
      }
    },
    [component.tableName, component.pagination?.pageSize, component.autoFilter, splitPanelContext?.selectedLeftData, relatedButtonFilter],
  );

  // 🆕 전역 테이블 새로고침 이벤트 리스너
  useEffect(() => {
    const handleRefreshTable = () => {
      if (component.tableName) {
        loadData(currentPage, searchValues);
      }
    };

    window.addEventListener("refreshTable", handleRefreshTable);

    return () => {
      window.removeEventListener("refreshTable", handleRefreshTable);
    };
  }, [currentPage, searchValues, loadData, component.tableName]);

  // 🆕 RelatedDataButtons 선택 이벤트 리스너 (버튼 선택 시 테이블 필터링)
  const [relatedButtonFilter, setRelatedButtonFilter] = useState<{
    filterColumn: string;
    filterValue: any;
  } | null>(null);

  useEffect(() => {
    const handleRelatedButtonSelect = (event: CustomEvent) => {
      const { targetTable, filterColumn, filterValue } = event.detail || {};

      // 이 테이블이 대상 테이블인지 확인
      if (targetTable === component.tableName) {
        console.log("📌 [InteractiveDataTable] RelatedDataButtons 필터 적용:", {
          tableName: component.tableName,
          filterColumn,
          filterValue,
        });
        setRelatedButtonFilter({ filterColumn, filterValue });
      }
    };

    window.addEventListener("related-button-select" as any, handleRelatedButtonSelect);

    return () => {
      window.removeEventListener("related-button-select" as any, handleRelatedButtonSelect);
    };
  }, [component.tableName]);

  // relatedButtonFilter 변경 시 데이터 다시 로드
  useEffect(() => {
    if (relatedButtonFilter) {
      loadData(1, searchValues);
    }
  }, [relatedButtonFilter]);

  // TableOptionsContext 필터 변경 시 데이터 재조회 (TableSearchWidget 연동)
  const filtersAppliedRef = useRef(false);
  useEffect(() => {
    // 초기 렌더 시 빈 배열은 무시 (불필요한 재조회 방지)
    if (!filtersAppliedRef.current && filters.length === 0) return;
    filtersAppliedRef.current = true;

    const filterSearchParams: Record<string, any> = {};
    filters.forEach((f) => {
      if (f.value !== "" && f.value !== undefined && f.value !== null) {
        filterSearchParams[f.columnName] = f.value;
      }
    });
    loadData(1, { ...searchValues, ...filterSearchParams });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filters]);

  // 카테고리 타입 컬럼의 값 매핑 로드
  useEffect(() => {
    const loadCategoryMappings = async () => {
      if (!component.tableName) return;

      try {
        // 카테고리 타입 컬럼 찾기
        const categoryColumns = component.columns?.filter((col) => {
          const webType = getColumnWebType(col.columnName);
          return webType === "category";
        });

        if (!categoryColumns || categoryColumns.length === 0) return;

        // 각 카테고리 컬럼의 값 목록 조회
        const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};

        const flattenCategoryTree = (items: any[], parentLabel: string = ""): Record<string, { label: string; color?: string }> => {
          const mapping: Record<string, { label: string; color?: string }> = {};
          items.forEach((item: any) => {
            const displayLabel = parentLabel
              ? `${parentLabel} / ${item.valueLabel}`
              : item.valueLabel;
            if (item.valueCode) {
              mapping[item.valueCode] = { label: displayLabel, color: item.color };
            }
            if (item.valueId !== undefined && item.valueId !== null) {
              mapping[String(item.valueId)] = { label: displayLabel, color: item.color };
            }
            if (item.children && Array.isArray(item.children) && item.children.length > 0) {
              Object.assign(mapping, flattenCategoryTree(item.children, item.valueLabel));
            }
          });
          return mapping;
        };

        for (const col of categoryColumns) {
          try {
            const queryParams = menuObjid ? `?menuObjid=${menuObjid}&includeInactive=true` : "?includeInactive=true";
            const response = await apiClient.get(
              `/table-categories/${component.tableName}/${col.columnName}/values${queryParams}`,
            );

            if (response.data.success && response.data.data) {
              const mapping = flattenCategoryTree(response.data.data);
              if (Object.keys(mapping).length > 0) {
                mappings[col.columnName] = mapping;
              } else {
                // 해당 테이블에 카테고리 값이 없으면 item_info에서 fallback 조회
                try {
                  const fbRes = await apiClient.get(`/table-categories/item_info/${col.columnName}/values?includeInactive=true`);
                  if (fbRes.data.success && fbRes.data.data) {
                    const fbMapping = flattenCategoryTree(fbRes.data.data);
                    if (Object.keys(fbMapping).length > 0) mappings[col.columnName] = fbMapping;
                  }
                } catch { /* 무시 */ }
              }
              console.log(`✅ 카테고리 매핑 로드 성공 [${col.columnName}]:`, mappings[col.columnName], { menuObjid });
            }
          } catch (error) {
            console.error(`❌ 카테고리 값 로드 실패 [${col.columnName}]:`, error);
          }
        }

        // 카테고리 타입이 아닌 컬럼 중 division/unit/type 등 흔한 카테고리 컬럼은 item_info에서 fallback 로드
        const KNOWN_CAT_COLS = ["division", "unit", "type", "material"];
        const allColNames = (component.columns || []).map((c) => c.columnName);
        for (const colName of allColNames) {
          if (mappings[colName]) continue; // 이미 로드됨
          if (!KNOWN_CAT_COLS.includes(colName)) continue;
          try {
            const fbRes = await apiClient.get(`/table-categories/item_info/${colName}/values?includeInactive=true`);
            if (fbRes.data.success && fbRes.data.data?.length > 0) {
              const fbMapping = flattenCategoryTree(fbRes.data.data);
              if (Object.keys(fbMapping).length > 0) mappings[colName] = fbMapping;
            }
          } catch { /* 무시 */ }
        }

        console.log("📊 전체 카테고리 매핑:", mappings);
        setCategoryMappings(mappings);
      } catch (error) {
        console.error("카테고리 매핑 로드 실패:", error);
      }
    };

    loadCategoryMappings();
  }, [component.tableName, component.columns, getColumnWebType, menuObjid]);

  // 파일 상태 확인 함수
  const checkFileStatus = useCallback(
    async (rowData: Record<string, any>) => {
      if (!component.tableName) return;

      // 첫 번째 컬럼을 기본키로 사용 (실제로는 더 정교한 로직 필요)
      const primaryKeyField = Object.keys(rowData)[0]; // 임시로 첫 번째 컬럼 사용
      const recordId = rowData[primaryKeyField];

      if (!recordId) return;

      try {
        const response = await getLinkedFiles(component.tableName, recordId);
        const hasFiles = response.files && response.files.length > 0;
        const fileCount = response.files ? response.files.length : 0;

        return { hasFiles, fileCount, files: response.files || [] };
      } catch (error) {
        // console.error("파일 상태 확인 오류:", error);
        return { hasFiles: false, fileCount: 0, files: [] };
      }
    },
    [component.tableName],
  );

  // 파일 폴더 아이콘 클릭 핸들러 (전체 행 파일 관리)
  const handleFileIconClick = useCallback(
    async (rowData: Record<string, any>) => {
      const fileStatus = await checkFileStatus(rowData);
      if (fileStatus) {
        setSelectedRowForFiles(rowData);
        setLinkedFiles(fileStatus.files);
        setShowFileManagementModal(true);
      }
    },
    [checkFileStatus],
  );

  // 컬럼별 파일 상태 확인
  const checkColumnFileStatus = useCallback(
    async (rowData: Record<string, any>, column: DataTableColumn) => {
      if (!component.tableName) return null;

      const primaryKeyField = Object.keys(rowData)[0];
      const recordId = rowData[primaryKeyField];
      if (!recordId) return null;

      try {
        // 가상 파일 컬럼의 경우: tableName:recordId:columnName 형태로 target_objid 생성
        const targetObjid = column.isVirtualFileColumn
          ? `${component.tableName}:${recordId}:${column.columnName}`
          : `${component.tableName}:${recordId}`;

        const response = await getLinkedFiles(component.tableName, recordId);

        // 가상 파일 컬럼의 경우 해당 컬럼의 파일만 필터링
        let files = response.files || [];
        if (column.isVirtualFileColumn) {
          // 현재 컬럼명으로 먼저 시도
          files = files.filter(
            (file: any) => file.targetObjid === targetObjid || file.targetObjid?.endsWith(`:${column.columnName}`), // target_objid → targetObjid
          );

          // 파일이 없는 경우 fallback: 모든 파일 컬럼 패턴 시도
          if (files.length === 0) {
            // 해당 테이블:레코드의 모든 파일 컬럼 파일들을 가져옴
            files = (response.files || []).filter(
              (file: any) => file.targetObjid?.startsWith(`${component.tableName}:${recordId}:file_column_`), // target_objid → targetObjid
            );
          }
        }

        const hasFiles = files.length > 0;
        const fileCount = files.length;

        return { hasFiles, fileCount, files, targetObjid };
      } catch (error) {
        // console.error("컬럼별 파일 상태 확인 오류:", error);
        return { hasFiles: false, fileCount: 0, files: [], targetObjid: null };
      }
    },
    [component.tableName],
  );

  // 컬럼별 파일 클릭 핸들러
  const handleColumnFileClick = useCallback(
    async (rowData: Record<string, any>, column: DataTableColumn) => {
      // 컬럼별 파일 상태 확인
      const fileStatus = await checkColumnFileStatus(rowData, column);
      setSelectedRowForFiles(rowData);
      setSelectedColumnForFiles(column); // 선택된 컬럼 정보 저장
      setLinkedFiles(fileStatus?.files || []);
      setShowFileManagementModal(true);

      // TODO: 모달에 컬럼 정보 전달하여 해당 컬럼 전용 파일 업로드 가능하게 하기
    },
    [checkColumnFileStatus],
  );

  // 이미지 미리보기 핸들러들
  const handlePreviewImage = useCallback((fileInfo: FileInfo) => {
    setPreviewImage(fileInfo);
    setShowPreviewModal(true);
    setZoom(1);
    setRotation(0);
    setImageLoadError(false);
    setAlternativeImageUrl(null);
  }, []);

  const closePreviewModal = useCallback(() => {
    setShowPreviewModal(false);
    setPreviewImage(null);
    setZoom(1);
    setRotation(0);
    setImageLoadError(false);
    setAlternativeImageUrl(null);
  }, []);

  const handleZoom = useCallback((direction: "in" | "out") => {
    setZoom((prev) => {
      if (direction === "in") {
        return Math.min(prev + 0.25, 3);
      } else {
        return Math.max(prev - 0.25, 0.25);
      }
    });
  }, []);

  const handleRotate = useCallback(() => {
    setRotation((prev) => (prev + 90) % 360);
  }, []);

  const formatFileSize = useCallback((bytes: number): string => {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }, []);

  // 이미지 로딩 실패 시 대체 URL 시도
  const handleImageError = useCallback(() => {
    if (!imageLoadError && previewImage) {
      // console.error("이미지 로딩 실패:", previewImage);
      setImageLoadError(true);

      // 대체 URL 생성 (직접 파일 경로 사용)
      if (previewImage.path) {
        const altUrl = getDirectFileUrl(previewImage.path);
        setAlternativeImageUrl(altUrl);
      } else {
        toast.error("이미지를 불러올 수 없습니다.");
      }
    } else {
      toast.error("이미지를 불러올 수 없습니다.");
    }
  }, [imageLoadError, previewImage]);
  const [showFileModal, setShowFileModal] = useState(false);
  const [currentFileData, setCurrentFileData] = useState<FileColumnData | null>(null);
  const [currentFileColumn, setCurrentFileColumn] = useState<DataTableColumn | null>(null);
  const [showDeleteDialog, setShowDeleteDialog] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);

  // 추가/수정 모달 상태
  const [showAddModal, setShowAddModal] = useState(false);
  const [showEditModal, setShowEditModal] = useState(false);
  const [addFormData, setAddFormData] = useState<Record<string, any>>({});
  const [editFormData, setEditFormData] = useState<Record<string, any>>({});
  const [editingRowData, setEditingRowData] = useState<Record<string, any> | null>(null);
  const [isAdding, setIsAdding] = useState(false);
  const [isEditing, setIsEditing] = useState(false);

  // 현재 사용자 정보
  const [currentUser, setCurrentUser] = useState<UserInfo | null>(null);

  // 테이블 컬럼 타입 정보 (웹 타입 포함)
  const [tableColumns, setTableColumns] = useState<any[]>([]);

  // 파일 업로드 관련 상태
  const [uploadingFiles, setUploadingFiles] = useState<Record<string, boolean>>({});
  const [uploadedFiles, setUploadedFiles] = useState<Record<string, File[]>>({});

  // 검색 가능한 컬럼만 필터링
  const visibleColumns = component.columns?.filter((col: DataTableColumn) => col.visible) || [];

  // 컬럼의 실제 웹 타입 정보 찾기 (webType 또는 input_type)
  const getColumnWebType = useCallback(
    (columnName: string) => {
      // 먼저 컴포넌트에 설정된 컬럼에서 찾기 (화면 관리에서 설정한 값 우선)
      const componentColumn = component.columns?.find((col) => col.columnName === columnName);
      if (componentColumn?.widgetType && componentColumn.widgetType !== "text") {
        return componentColumn.widgetType;
      }

      // 없으면 테이블 타입 관리에서 설정된 값 찾기
      const tableColumn = tableColumns.find((col) => col.columnName === columnName);

      // input_type 우선 사용 (category 등)
      const inputType = (tableColumn as any)?.input_type || (tableColumn as any)?.inputType;
      if (inputType) {
        return inputType;
      }

      // 없으면 webType 사용
      return tableColumn?.webType || "text";
    },
    [component.columns, tableColumns],
  );

  // 컬럼의 상세 설정 정보 찾기
  const getColumnDetailSettings = useCallback(
    (columnName: string) => {
      // 먼저 컴포넌트에 설정된 컬럼에서 찾기 (화면 관리에서 설정한 값 우선)
      const componentColumn = component.columns?.find((col) => col.columnName === columnName);
      if (componentColumn?.webTypeConfig) {
        return componentColumn.webTypeConfig;
      }

      // 없으면 테이블 타입 관리에서 설정된 값 찾기
      const tableColumn = tableColumns.find((col) => col.columnName === columnName);
      try {
        return tableColumn?.detailSettings ? JSON.parse(tableColumn.detailSettings) : {};
      } catch {
        return {};
      }
    },
    [component.columns, tableColumns],
  );

  // 컬럼의 코드 카테고리 가져오기
  const getColumnCodeCategory = useCallback(
    (columnName: string) => {
      const column = component.columns.find((col) => col.columnName === columnName);
      // webTypeConfig가 CodeTypeConfig인 경우 codeCategory 반환
      const webTypeConfig = column?.webTypeConfig as any;
      return webTypeConfig?.codeCategory || column?.codeCategory;
    },
    [component.columns],
  );

  // 그리드 컬럼 계산
  const totalGridColumns = visibleColumns.reduce((sum, col) => sum + (col.gridColumns || 2), 0);

  // 페이지 크기 설정
  const pageSize = component.pagination?.pageSize || 10;

  // 초기 컬럼 너비 측정 (한 번만)
  useEffect(() => {
    if (!hasInitializedWidthsRef.current && visibleColumns.length > 0) {
      // 약간의 지연을 두고 DOM이 완전히 렌더링된 후 측정
      const timer = setTimeout(() => {
        const newWidths: Record<string, number> = {};
        let hasAnyWidth = false;

        visibleColumns.forEach((column) => {
          const thElement = columnRefs.current[column.id];
          if (thElement) {
            const measuredWidth = thElement.offsetWidth;
            if (measuredWidth > 0) {
              newWidths[column.id] = measuredWidth;
              hasAnyWidth = true;
            }
          }
        });

        if (hasAnyWidth) {
          setColumnWidths(newWidths);
          hasInitializedWidthsRef.current = true;
        }
      }, 100);

      return () => clearTimeout(timer);
    }
  }, [visibleColumns]);

  // 현재 사용자 정보 로드
  useEffect(() => {
    const fetchCurrentUser = async () => {
      try {
        const response = await getCurrentUser();
        if (response.success && response.data) {
          setCurrentUser(response.data);
        }
      } catch (error) {
        // console.error("현재 사용자 정보 로드 실패:", error);
      }
    };

    fetchCurrentUser();
  }, []);

  // 파일 상태 새로고침 이벤트 리스너
  useEffect(() => {
    const handleRefreshFileStatus = async (event: CustomEvent) => {
      const { tableName, recordId, columnName, targetObjid, fileCount } = event.detail;

      // 현재 테이블과 일치하는지 확인
      if (tableName === component.tableName) {
        // 해당 행의 파일 상태 업데이트
        const columnKey = `${recordId}_${columnName}`;
        setFileStatusMap((prev) => ({
          ...prev,
          [recordId]: { hasFiles: fileCount > 0, fileCount },
          [columnKey]: { hasFiles: fileCount > 0, fileCount },
        }));
      }
    };

    if (typeof window !== "undefined") {
      window.addEventListener("refreshFileStatus", handleRefreshFileStatus as EventListener);

      return () => {
        window.removeEventListener("refreshFileStatus", handleRefreshFileStatus as EventListener);
      };
    }
  }, [component.tableName]);

  // 테이블 컬럼 정보 로드 (웹 타입 정보 포함)
  useEffect(() => {
    const fetchTableColumns = async () => {
      try {
        const columns = await tableTypeApi.getColumns(component.tableName);
        setTableColumns(columns);

        // 🆕 전체 컬럼 목록 설정
        const columnNames = columns.map((col) => col.columnName);
        setAllAvailableColumns(columnNames);

        // 🆕 컬럼명 -> 라벨 매핑 생성
        const labels: Record<string, string> = {};
        columns.forEach((col) => {
          labels[col.columnName] = col.displayName || col.columnName;
        });
        setColumnLabels(labels);

        // 🆕 localStorage에서 필터 설정 복원
        if (user?.userId && component.componentId) {
          const storageKey = `table-search-filter-${user.userId}-${component.componentId}`;
          const savedFilter = localStorage.getItem(storageKey);
          if (savedFilter) {
            try {
              const parsed = JSON.parse(savedFilter);
              setSearchFilterColumns(new Set(parsed));
            } catch (e) {
              console.error("필터 설정 복원 실패:", e);
            }
          }
        }
      } catch (error) {
        // console.error("테이블 컬럼 정보 로드 실패:", error);
      }
    };

    if (component.tableName) {
      fetchTableColumns();
    }
  }, [component.tableName, component.componentId, user?.userId]);

  // 실제 사용할 필터 (설정된 필터만 사용, 자동 생성 안함)
  const searchFilters = useMemo(() => {
    return component.filters || [];
  }, [component.filters]);

  // 초기 데이터 로드
  useEffect(() => {
    loadData(1, searchValues);
  }, [loadData]);

  // 검색 실행
  const handleSearch = useCallback(() => {
    loadData(1, searchValues);
  }, [searchValues, loadData]);

  // 검색값 변경
  const handleSearchValueChange = useCallback((columnName: string, value: any) => {
    setSearchValues((prev) => ({
      ...prev,
      [columnName]: value,
    }));
  }, []);

  // 페이지 변경
  const handlePageChange = useCallback(
    (page: number) => {
      loadData(page, searchValues);
    },
    [loadData, searchValues],
  );

  // 행 선택 핸들러
  const handleRowSelect = useCallback(
    (rowIndex: number, isSelected: boolean) => {
      setSelectedRows((prev) => {
        const newSet = new Set(prev);
        if (isSelected) {
          newSet.add(rowIndex);
        } else {
          newSet.delete(rowIndex);
        }
        return newSet;
      });

      // 분할 패널 좌측에서 선택 시 컨텍스트에 데이터 저장 (연결 필터용)
      if (splitPanelContext && splitPanelPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
        if (isSelected && data[rowIndex]) {
          splitPanelContext.setSelectedLeftData(data[rowIndex]);
          console.log("🔗 [InteractiveDataTable] 좌측 선택 데이터 저장:", data[rowIndex]);
        } else if (!isSelected) {
          splitPanelContext.setSelectedLeftData(null);
          console.log("🔗 [InteractiveDataTable] 좌측 선택 데이터 초기화");
        }
      }
    },
    [data, splitPanelContext, splitPanelPosition],
  );

  // 전체 선택/해제 핸들러
  const handleSelectAll = useCallback(
    (isSelected: boolean) => {
      if (isSelected) {
        setSelectedRows(new Set(Array.from({ length: data.length }, (_, i) => i)));
      } else {
        setSelectedRows(new Set());
      }
    },
    [data.length],
  );

  // 모달에 표시할 컬럼 계산
  const getDisplayColumns = useCallback(() => {
    const { hiddenFields, fieldOrder, advancedFieldConfigs } = component.addModalConfig || {};

    // 숨겨진 필드와 고급 설정에서 숨겨진 필드 제외
    let displayColumns = visibleColumns.filter((col) => {
      // 기본 숨김 필드 체크
      if (hiddenFields?.includes(col.columnName)) return false;

      // 고급 설정에서 숨김 체크
      const config = advancedFieldConfigs?.[col.columnName];
      if (config?.inputType === "hidden") return false;

      return true;
    });

    // 필드 순서 적용
    if (fieldOrder && fieldOrder.length > 0) {
      const orderedColumns: typeof displayColumns = [];
      const remainingColumns = [...displayColumns];

      // 지정된 순서대로 추가
      fieldOrder.forEach((columnName) => {
        const column = remainingColumns.find((col) => col.columnName === columnName);
        if (column) {
          orderedColumns.push(column);
          const index = remainingColumns.indexOf(column);
          remainingColumns.splice(index, 1);
        }
      });

      // 나머지 컬럼들 추가
      orderedColumns.push(...remainingColumns);
      displayColumns = orderedColumns;
    }

    return displayColumns;
  }, [visibleColumns, component.addModalConfig]);

  // 자동 값 생성
  const generateAutoValue = useCallback(
    (autoValueType: string): string => {
      const now = new Date();
      const pad = (n: number) => String(n).padStart(2, "0");
      const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
      const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
      switch (autoValueType) {
        case "current_datetime":
          return `${localDate} ${localTime}`;
        case "current_date":
          return localDate;
        case "current_time":
          return localTime;
        case "current_user":
          return currentUser?.userName || currentUser?.userId || "unknown_user";
        case "uuid":
          return crypto.randomUUID();
        case "sequence":
          return `SEQ_${Date.now()}`;
        default:
          return "";
      }
    },
    [currentUser],
  );

  // 데이터 추가 핸들러
  const handleAddData = useCallback(() => {
    // 폼 데이터 초기화
    const initialData: Record<string, any> = {};
    const displayColumns = getDisplayColumns();
    const advancedConfigs = component.addModalConfig?.advancedFieldConfigs || {};

    displayColumns.forEach((col) => {
      const config = advancedConfigs[col.columnName];

      if (config?.inputType === "auto") {
        // 자동 값 설정
        if (config.autoValueType === "custom") {
          initialData[col.columnName] = config.customValue || "";
        } else {
          initialData[col.columnName] = generateAutoValue(config.autoValueType);
        }
      } else if (config?.defaultValue) {
        // 기본값 설정
        initialData[col.columnName] = config.defaultValue;
      } else {
        // 일반 빈 값
        initialData[col.columnName] = "";
      }
    });

    // SaveModal 열기 (등록 모드)
    const screenId = component.addModalConfig?.screenId;

    if (!screenId) {
      toast.error("화면 설정이 필요합니다. 테이블 설정에서 추가 모달 화면을 지정해주세요.");
      return;
    }

    // SaveModal 사용
    setSaveModalData(undefined); // undefined = 등록 모드
    setSaveModalScreenId(screenId);
    setShowSaveModal(true);
  }, [getDisplayColumns, generateAutoValue, component.addModalConfig]);

  // 데이터 수정 핸들러 (EditModal 사용)
  const handleEditData = useCallback(() => {
    if (selectedRows.size !== 1) return;

    const selectedIndex = Array.from(selectedRows)[0];
    const selectedRowData = data[selectedIndex];

    if (!selectedRowData) return;

    const screenId = component.addModalConfig?.screenId;

    if (!screenId) {
      toast.error("화면 설정이 필요합니다. 테이블 설정에서 수정 모달 화면을 지정해주세요.");
      return;
    }

    // 수정할 데이터로 폼 초기화
    const initialData: Record<string, any> = {};
    const displayColumns = getDisplayColumns();

    displayColumns.forEach((col) => {
      initialData[col.columnName] = selectedRowData[col.columnName] || "";
    });

    // 수정 모달 설정에서 제목과 설명 가져오기
    const editModalTitle = component.editModalConfig?.title || "데이터 수정";
    const editModalDescription = component.editModalConfig?.description || "";

    // 전역 EditModal 열기 이벤트 발생
    const event = new CustomEvent("openEditModal", {
      detail: {
        screenId,
        title: editModalTitle,
        description: editModalDescription,
        modalSize: "lg",
        editData: initialData,
        menuObjid, // 🆕 메뉴 OBJID 전달 (카테고리 스코프용)
        onSave: () => {
          loadData(); // 테이블 데이터 새로고침
        },
      },
    });
    window.dispatchEvent(event);
  }, [selectedRows, data, getDisplayColumns, component.addModalConfig, component.editModalConfig, loadData, menuObjid]);

  // 수정 폼 데이터 변경 핸들러
  const handleEditFormChange = useCallback((columnName: string, value: any) => {
    setEditFormData((prev) => ({
      ...prev,
      [columnName]: value,
    }));
  }, []);

  const handleAddFormChange = useCallback((columnName: string, value: any) => {
    setAddFormData((prev) => ({
      ...prev,
      [columnName]: value,
    }));
  }, []);

  // 파일 업로드 핸들러
  const handleFileUpload = useCallback(
    async (columnName: string, files: FileList | null, isEdit: boolean = false) => {
      if (!files || files.length === 0) return;

      const detailSettings = getColumnDetailSettings(columnName);
      const maxSize = detailSettings?.maxSize || 10 * 1024 * 1024; // 기본 10MB
      const acceptedTypes = detailSettings?.accept
        ? detailSettings.accept.split(",").map((type: string) => type.trim())
        : [];
      const multiple = detailSettings?.multiple || false;

      // 파일 검증
      const validFiles: File[] = [];
      for (let i = 0; i < files.length; i++) {
        const file = files[i];

        // 크기 체크
        if (file.size > maxSize) {
          alert(`파일 크기가 너무 큽니다. 최대 ${Math.round(maxSize / 1024 / 1024)}MB까지 가능합니다.`);
          continue;
        }

        // 타입 체크
        if (
          acceptedTypes.length > 0 &&
          !acceptedTypes.some((type: string) => {
            if (type.startsWith(".")) {
              return file.name.toLowerCase().endsWith(type.toLowerCase());
            } else {
              return file.type.includes(type);
            }
          })
        ) {
          alert(`지원하지 않는 파일 형식입니다. (${acceptedTypes.join(", ")})`);
          continue;
        }

        validFiles.push(file);
        if (!multiple) break; // 단일 파일만 허용
      }

      if (validFiles.length === 0) return;

      try {
        setUploadingFiles((prev) => ({ ...prev, [columnName]: true }));

        // TODO: 실제 파일 업로드 API 호출
        // const uploadPromises = validFiles.map(file => uploadFileToServer(file));
        // const uploadResults = await Promise.all(uploadPromises);

        // 임시: 파일 정보를 로컬 상태에 저장
        setUploadedFiles((prev) => ({
          ...prev,
          [columnName]: multiple ? [...(prev[columnName] || []), ...validFiles] : validFiles,
        }));

        // 폼 데이터 업데이트
        const fileNames = validFiles.map((file) => file.name).join(", ");
        if (isEdit) {
          handleEditFormChange(columnName, fileNames);
        } else {
          handleAddFormChange(columnName, fileNames);
        }
      } catch (error) {
        // console.error("파일 업로드 실패:", error);
        alert("파일 업로드에 실패했습니다.");
      } finally {
        setUploadingFiles((prev) => ({ ...prev, [columnName]: false }));
      }
    },
    [getColumnDetailSettings, handleAddFormChange, handleEditFormChange],
  );

  // 파일 제거 핸들러
  const handleFileRemove = useCallback(
    (columnName: string, fileIndex: number, isEdit: boolean = false) => {
      setUploadedFiles((prev) => {
        const currentFiles = prev[columnName] || [];
        const newFiles = currentFiles.filter((_, index) => index !== fileIndex);

        // 폼 데이터 업데이트
        const fileNames = newFiles.map((file) => file.name).join(", ");
        if (isEdit) {
          handleEditFormChange(columnName, fileNames);
        } else {
          handleAddFormChange(columnName, fileNames);
        }

        return { ...prev, [columnName]: newFiles };
      });
    },
    [handleAddFormChange, handleEditFormChange],
  );

  // 파일 목록 렌더링 컴포넌트
  const renderFileList = useCallback(
    (columnName: string, isEdit: boolean = false) => {
      const currentFiles = uploadedFiles[columnName] || [];
      const isUploading = uploadingFiles[columnName];

      if (currentFiles.length === 0 && !isUploading) return null;

      return (
        <div className="mt-2 space-y-2">
          {currentFiles.map((file, index) => (
            <div key={index} className="flex items-center justify-between rounded border bg-muted p-2">
              <div className="flex items-center space-x-2">
                <div className="text-muted-foreground text-xs">📄</div>
                <div>
                  <p className="text-sm font-medium">{file.name}</p>
                  <p className="text-xs text-muted-foreground">{(file.size / 1024).toFixed(1)} KB</p>
                </div>
              </div>
              <Button
                type="button"
                variant="ghost"
                size="sm"
                onClick={() => handleFileRemove(columnName, index, isEdit)}
                className="h-6 w-6 p-0"
              >
                <Trash2 className="h-3 w-3" />
              </Button>
            </div>
          ))}
          {isUploading && (
            <div className="bg-accent flex items-center space-x-2 rounded border p-2">
              <Loader2 className="h-4 w-4 animate-spin" />
              <span className="text-primary text-sm">업로드 중...</span>
            </div>
          )}
        </div>
      );
    },
    [uploadedFiles, uploadingFiles, handleFileRemove],
  );

  // 데이터 추가 제출 핸들러
  const handleAddSubmit = useCallback(async () => {
    try {
      setIsAdding(true);

      // 실제 API 호출로 데이터 추가
      await tableTypeApi.addTableData(component.tableName, addFormData);

      // 모달 닫기 및 폼 초기화
      setShowAddModal(false);
      setAddFormData({});

      // 첫 페이지로 이동하여 새 데이터 확인
      loadData(1, searchValues);
    } catch (error) {
      // console.error("데이터 추가 실패:", error);
      alert("데이터 추가에 실패했습니다.");
    } finally {
      setIsAdding(false);
    }
  }, [addFormData, loadData, searchValues]);

  // 데이터 수정 제출 핸들러
  const handleEditSubmit = useCallback(async () => {
    try {
      setIsEditing(true);

      // 실제 API 호출로 데이터 수정
      if (editingRowData) {
        await tableTypeApi.editTableData(component.tableName, editingRowData, editFormData);

        // 모달 닫기 및 폼 초기화
        setShowEditModal(false);
        setEditFormData({});
        setEditingRowData(null);
        setSelectedRows(new Set()); // 선택 해제

        // 현재 페이지 데이터 새로고침
        loadData(currentPage, searchValues);
      }
    } catch (error) {
      // console.error("데이터 수정 실패:", error);
      alert("데이터 수정에 실패했습니다.");
    } finally {
      setIsEditing(false);
    }
  }, [editFormData, editingRowData, component.tableName, currentPage, searchValues, loadData]);

  // 추가 모달 닫기 핸들러
  const handleAddModalClose = useCallback(() => {
    if (!isAdding) {
      setShowAddModal(false);
      setAddFormData({});
      setUploadedFiles({}); // 파일 상태 초기화
    }
  }, [isAdding]);

  // 🆕 검색 필터 저장 함수
  const handleSaveSearchFilter = useCallback(() => {
    if (user?.userId && component.componentId) {
      const storageKey = `table-search-filter-${user.userId}-${component.componentId}`;
      const filterArray = Array.from(searchFilterColumns);
      localStorage.setItem(storageKey, JSON.stringify(filterArray));
      toast.success("검색 필터 설정이 저장되었습니다.");
    }
  }, [user?.userId, component.componentId, searchFilterColumns]);

  // 🆕 검색 필터 토글 함수
  const handleToggleFilterColumn = useCallback((columnName: string) => {
    setSearchFilterColumns((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(columnName)) {
        newSet.delete(columnName);
      } else {
        newSet.add(columnName);
      }
      return newSet;
    });
  }, []);

  // 데이터 삭제 핸들러
  const handleDeleteData = useCallback(() => {
    if (selectedRows.size === 0) {
      alert("삭제할 데이터를 선택해주세요.");
      return;
    }
    setShowDeleteDialog(true);
  }, [selectedRows.size]);

  // 삭제 확인 핸들러
  const handleDeleteConfirm = useCallback(async () => {
    try {
      setIsDeleting(true);

      // 선택된 행의 실제 데이터 가져오기
      const selectedData = Array.from(selectedRows).map((index) => data[index]);

      // 실제 삭제 API 호출
      await tableTypeApi.deleteTableData(component.tableName, selectedData);

      // 선택 해제 및 다이얼로그 닫기
      setSelectedRows(new Set());
      setShowDeleteDialog(false);

      // 데이터 새로고침
      loadData(currentPage, searchValues);
    } catch (error) {
      // console.error("데이터 삭제 실패:", error);
      alert("데이터 삭제에 실패했습니다.");
    } finally {
      setIsDeleting(false);
    }
  }, [selectedRows, data, currentPage, searchValues, loadData]);

  // 삭제 다이얼로그 닫기 핸들러
  const handleDeleteDialogClose = useCallback(() => {
    if (!isDeleting) {
      setShowDeleteDialog(false);
    }
  }, [isDeleting]);

  // 필수 필드 여부 확인
  const isRequiredField = useCallback(
    (columnName: string) => {
      return component.addModalConfig?.requiredFields?.includes(columnName) || false;
    },
    [component.addModalConfig],
  );

  // 모달 크기 클래스 가져오기
  const getModalSizeClass = useCallback(() => {
    const width = component.addModalConfig?.width || "lg";
    const sizeMap = {
      sm: "max-w-sm",
      md: "max-w-md",
      lg: "max-w-lg",
      xl: "max-w-xl",
      "2xl": "max-w-2xl",
      full: "max-w-full mx-4",
    };
    return sizeMap[width];
  }, [component.addModalConfig]);

  // 레이아웃 클래스 가져오기
  const getLayoutClass = useCallback(() => {
    const layout = component.addModalConfig?.layout || "two-column";
    const gridColumns = component.addModalConfig?.gridColumns || 2;

    switch (layout) {
      case "single":
        return "grid grid-cols-1 gap-4";
      case "two-column":
        return "grid grid-cols-2 gap-4";
      case "grid":
        return `grid grid-cols-${Math.min(gridColumns, 4)} gap-4`;
      default:
        return "grid grid-cols-2 gap-4";
    }
  }, [component.addModalConfig]);

  // 수정 폼 입력 컴포넌트 렌더링
  const renderEditFormInput = (column: DataTableColumn) => {
    const value = editFormData[column.columnName] || "";
    const isRequired = isRequiredField(column.columnName);
    const advancedConfig = component.addModalConfig?.advancedFieldConfigs?.[column.columnName];

    // 데이터베이스에서 실제 웹 타입 가져오기
    const actualWebType = getColumnWebType(column.columnName);
    const detailSettings = getColumnDetailSettings(column.columnName);

    // 자동 생성 필드는 수정에서 읽기 전용으로 처리
    if (advancedConfig?.inputType === "auto") {
      return (
        <div className="relative">
          <Input
            value={value}
            readOnly
            className="bg-muted text-foreground"
            placeholder={`${column.label} (자동 생성됨)`}
          />
          <p className="mt-1 text-xs text-muted-foreground">자동 생성된 필드는 수정할 수 없습니다.</p>
        </div>
      );
    }

    // 읽기 전용 필드
    if (advancedConfig?.inputType === "readonly") {
      return (
        <div className="relative">
          <Input
            value={value}
            readOnly
            className="bg-muted text-foreground"
            placeholder={advancedConfig?.placeholder || `${column.label} (읽기 전용)`}
          />
          {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
        </div>
      );
    }

    // 일반 입력 필드 렌더링
    const commonProps = {
      value,
      onChange: (e: React.ChangeEvent<HTMLInputElement>) => handleEditFormChange(column.columnName, e.target.value),
      placeholder: advancedConfig?.placeholder || `${column.label} 입력...`,
      required: isRequired,
      className: isRequired && !value ? "border-orange-300 focus:border-orange-500" : "",
    };

    // 실제 웹 타입에 따라 입력 컴포넌트 렌더링
    switch (actualWebType) {
      case "text":
      case "email":
      case "tel":
        return (
          <div>
            <Input
              type={actualWebType === "email" ? "email" : actualWebType === "tel" ? "tel" : "text"}
              {...commonProps}
              maxLength={detailSettings?.maxLength}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "number":
      case "decimal":
        return (
          <div>
            <Input
              type="number"
              step={actualWebType === "decimal" ? detailSettings?.step || "0.01" : "1"}
              min={detailSettings?.min}
              max={detailSettings?.max}
              {...commonProps}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "date":
        return (
          <div>
            <Input type="date" min={detailSettings?.minDate} max={detailSettings?.maxDate} {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "datetime":
        return (
          <div>
            <Input type="datetime-local" min={detailSettings?.minDate} max={detailSettings?.maxDate} {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "select":
      case "dropdown":
        // 🆕 연쇄 드롭다운 처리
        const cascadingConfig = detailSettings?.cascading as CascadingDropdownConfig | undefined;
        if (cascadingConfig?.enabled) {
          const parentValue = editFormData[cascadingConfig.parentField];
          return (
            <div>
              <CascadingDropdownInForm
                config={cascadingConfig}
                parentValue={parentValue}
                value={value}
                onChange={(newValue) => handleEditFormChange(column.columnName, newValue)}
                placeholder={commonProps.placeholder}
                className={commonProps.className}
              />
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        }

        // 상세 설정에서 옵션 목록 가져오기
        const options = detailSettings?.options || [];
        if (options.length > 0) {
          return (
            <div>
              <Select value={value} onValueChange={(newValue) => handleEditFormChange(column.columnName, newValue)}>
                <SelectTrigger className={commonProps.className}>
                  <SelectValue placeholder={commonProps.placeholder} />
                </SelectTrigger>
                <SelectContent>
                  {options.map((option: any, index: number) => (
                    <SelectItem key={index} value={option.value || option}>
                      {option.label || option}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        } else {
          return <Input {...commonProps} placeholder={`${column.label} 선택... (옵션 설정 필요)`} readOnly />;
        }

      case "radio":
        // 상세 설정에서 옵션 목록 가져오기
        const radioOptions = detailSettings?.options || [];
        if (radioOptions.length > 0) {
          return (
            <div>
              <div className="space-y-2">
                {radioOptions.map((option: any, index: number) => (
                  <div key={index} className="flex items-center space-x-2">
                    <input
                      type="radio"
                      id={`${column.columnName}-edit-${index}`}
                      name={`${column.columnName}-edit`}
                      value={option.value || option}
                      checked={value === (option.value || option)}
                      onChange={(e) => handleEditFormChange(column.columnName, e.target.value)}
                      className="text-primary focus:ring-primary"
                    />
                    <Label htmlFor={`${column.columnName}-edit-${index}`} className="text-sm">
                      {option.label || option}
                    </Label>
                  </div>
                ))}
              </div>
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        } else {
          return <Input {...commonProps} placeholder={`${column.label} 선택... (옵션 설정 필요)`} readOnly />;
        }

      case "textarea":
        return (
          <div>
            <textarea
              value={value}
              onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
                handleEditFormChange(column.columnName, e.target.value)
              }
              placeholder={advancedConfig?.placeholder || `${column.label} 입력...`}
              required={isRequired}
              className={`border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
                isRequired && !value ? "border-orange-300 focus:border-orange-500" : ""
              }`}
              rows={detailSettings?.rows || 3}
              maxLength={detailSettings?.maxLength}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "boolean":
      case "checkbox":
        return (
          <div className="flex items-center space-x-2">
            <Checkbox
              checked={value === true || value === "true" || value === 1}
              onCheckedChange={(checked) => handleEditFormChange(column.columnName, checked)}
            />
            <Label>{column.label}</Label>
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "file":
        return (
          <div>
            <div className="space-y-2">
              <div className="flex items-center space-x-2">
                <Input
                  type="file"
                  accept={detailSettings?.accept}
                  multiple={detailSettings?.multiple}
                  onChange={(e) => handleFileUpload(column.columnName, e.target.files, true)}
                  className="hidden"
                  id={`file-edit-${column.columnName}`}
                />
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  onClick={() => document.getElementById(`file-edit-${column.columnName}`)?.click()}
                  disabled={uploadingFiles[column.columnName]}
                  className="gap-2"
                >
                  {uploadingFiles[column.columnName] ? (
                    <Loader2 className="h-4 w-4 animate-spin" />
                  ) : (
                    <Plus className="h-4 w-4" />
                  )}
                  파일 선택
                </Button>
                {detailSettings?.accept && <span className="text-xs text-muted-foreground">({detailSettings.accept})</span>}
              </div>
              {renderFileList(column.columnName, true)}
            </div>
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "category": {
        // 카테고리 셀렉트 (동적 import)
        const {
          CategorySelectComponent,
        } = require("@/lib/registry/components/category-select/CategorySelectComponent");
        return (
          <div>
            <CategorySelectComponent
              tableName={component.tableName}
              columnName={column.columnName}
              value={value}
              onChange={(newValue) => handleEditFormChange(column.columnName, newValue)}
              placeholder={advancedConfig?.placeholder || `${column.label} 선택...`}
              required={isRequired}
              className={commonProps.className}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );
      }

      default:
        return (
          <div>
            <Input {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );
    }
  };

  // 추가 폼 입력 컴포넌트 렌더링
  const renderAddFormInput = (column: DataTableColumn) => {
    const value = addFormData[column.columnName] || "";
    const isRequired = isRequiredField(column.columnName);
    const advancedConfig = component.addModalConfig?.advancedFieldConfigs?.[column.columnName];

    // 데이터베이스에서 실제 웹 타입 가져오기
    const actualWebType = getColumnWebType(column.columnName);
    const detailSettings = getColumnDetailSettings(column.columnName);

    // 읽기 전용 또는 자동 값인 경우
    if (advancedConfig?.inputType === "readonly" || advancedConfig?.inputType === "auto") {
      return (
        <div className="relative">
          <Input
            value={value}
            readOnly
            className="bg-muted text-foreground"
            placeholder={advancedConfig?.placeholder || `${column.label} (자동 생성)`}
          />
          {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
        </div>
      );
    }

    // 일반 입력 필드 렌더링
    const commonProps = {
      value,
      onChange: (e: React.ChangeEvent<HTMLInputElement>) => handleAddFormChange(column.columnName, e.target.value),
      placeholder: advancedConfig?.placeholder || `${column.label} 입력...`,
      required: isRequired,
      className: isRequired && !value ? "border-orange-300 focus:border-orange-500" : "",
    };

    // 실제 웹 타입에 따라 입력 컴포넌트 렌더링
    switch (actualWebType) {
      case "text":
      case "email":
      case "tel":
        return (
          <div>
            <Input
              type={actualWebType === "email" ? "email" : actualWebType === "tel" ? "tel" : "text"}
              {...commonProps}
              maxLength={detailSettings?.maxLength}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "number":
      case "decimal":
        return (
          <div>
            <Input
              type="number"
              step={actualWebType === "decimal" ? detailSettings?.step || "0.01" : "1"}
              min={detailSettings?.min}
              max={detailSettings?.max}
              {...commonProps}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "date":
        return (
          <div>
            <Input type="date" min={detailSettings?.minDate} max={detailSettings?.maxDate} {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "datetime":
        return (
          <div>
            <Input type="datetime-local" min={detailSettings?.minDate} max={detailSettings?.maxDate} {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "select":
      case "dropdown":
        // 🆕 연쇄 드롭다운 처리
        const cascadingConfigAdd = detailSettings?.cascading as CascadingDropdownConfig | undefined;
        if (cascadingConfigAdd?.enabled) {
          const parentValueAdd = addFormData[cascadingConfigAdd.parentField];
          return (
            <div>
              <CascadingDropdownInForm
                config={cascadingConfigAdd}
                parentValue={parentValueAdd}
                value={value}
                onChange={(newValue) => handleAddFormChange(column.columnName, newValue)}
                placeholder={commonProps.placeholder}
                className={commonProps.className}
              />
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        }

        // 상세 설정에서 옵션 목록 가져오기
        const optionsAdd = detailSettings?.options || [];
        if (optionsAdd.length > 0) {
          return (
            <div>
              <Select value={value} onValueChange={(newValue) => handleAddFormChange(column.columnName, newValue)}>
                <SelectTrigger className={commonProps.className}>
                  <SelectValue placeholder={commonProps.placeholder} />
                </SelectTrigger>
                <SelectContent>
                  {optionsAdd.map((option: any, index: number) => (
                    <SelectItem key={index} value={option.value || option}>
                      {option.label || option}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        } else {
          return <Input {...commonProps} placeholder={`${column.label} 선택... (옵션 설정 필요)`} readOnly />;
        }

      case "radio":
        // 상세 설정에서 옵션 목록 가져오기
        const radioOptionsAdd = detailSettings?.options || [];
        const defaultValueAdd = detailSettings?.defaultValue;

        // 추가 모달에서는 기본값이 있으면 초기값으로 설정
        if (radioOptionsAdd.length > 0) {
          // 폼 데이터에 값이 없고 기본값이 있으면 기본값 설정
          if (!value && defaultValueAdd) {
            setTimeout(() => handleAddFormChange(column.columnName, defaultValueAdd), 0);
          }

          return (
            <div>
              <div className="space-y-2">
                {radioOptionsAdd.map((option: any, index: number) => (
                  <div key={index} className="flex items-center space-x-2">
                    <input
                      type="radio"
                      id={`${column.columnName}-add-${index}`}
                      name={`${column.columnName}-add`}
                      value={option.value || option}
                      checked={value === (option.value || option)}
                      onChange={(e) => handleAddFormChange(column.columnName, e.target.value)}
                      className="text-primary focus:ring-primary"
                    />
                    <Label htmlFor={`${column.columnName}-add-${index}`} className="text-sm">
                      {option.label || option}
                    </Label>
                  </div>
                ))}
              </div>
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        } else {
          return <Input {...commonProps} placeholder={`${column.label} 선택... (옵션 설정 필요)`} readOnly />;
        }

      case "boolean":
      case "checkbox":
        return (
          <div className="flex items-center space-x-2">
            <Checkbox
              checked={value === true || value === "true" || value === 1}
              onCheckedChange={(checked) => handleAddFormChange(column.columnName, checked)}
            />
            <Label>{column.label}</Label>
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "textarea":
        return (
          <div>
            <textarea
              value={value}
              onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
                handleAddFormChange(column.columnName, e.target.value)
              }
              placeholder={advancedConfig?.placeholder || `${column.label} 입력...`}
              required={isRequired}
              className={`border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
                isRequired && !value ? "border-orange-300 focus:border-orange-500" : ""
              }`}
              rows={detailSettings?.rows || 3}
              maxLength={detailSettings?.maxLength}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "code":
        // 코드 카테고리에서 코드 옵션 가져오기
        const codeCategory = getColumnCodeCategory(column.columnName);
        if (codeCategory) {
          const codeOptionsForCategory = codeOptions[codeCategory] || [];

          // 코드 옵션이 없으면 로드
          if (codeOptionsForCategory.length === 0) {
            loadCodeOptions(codeCategory);
          }

          return (
            <div>
              <Select value={value} onValueChange={(newValue) => handleAddFormChange(column.columnName, newValue)}>
                <SelectTrigger className={commonProps.className}>
                  <SelectValue placeholder={`${column.label} 선택...`} />
                </SelectTrigger>
                <SelectContent>
                  {codeOptionsForCategory.map((option) => (
                    <SelectItem key={option.value} value={option.value}>
                      {option.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        } else {
          return (
            <div>
              <Input {...commonProps} placeholder={`${column.label} (코드 카테고리 설정 필요)`} readOnly />
              {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
            </div>
          );
        }

      case "file":
        return (
          <div>
            <div className="space-y-2">
              <div className="flex items-center space-x-2">
                <Input
                  type="file"
                  accept={detailSettings?.accept}
                  multiple={detailSettings?.multiple}
                  onChange={(e) => handleFileUpload(column.columnName, e.target.files, false)}
                  className="hidden"
                  id={`file-add-${column.columnName}`}
                />
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  onClick={() => document.getElementById(`file-add-${column.columnName}`)?.click()}
                  disabled={uploadingFiles[column.columnName]}
                  className="gap-2"
                >
                  {uploadingFiles[column.columnName] ? (
                    <Loader2 className="h-4 w-4 animate-spin" />
                  ) : (
                    <Plus className="h-4 w-4" />
                  )}
                  파일 선택
                </Button>
                {detailSettings?.accept && <span className="text-xs text-muted-foreground">({detailSettings.accept})</span>}
              </div>
              {renderFileList(column.columnName, false)}
            </div>
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );

      case "category": {
        // 카테고리 셀렉트 (동적 import)
        const {
          CategorySelectComponent,
        } = require("@/lib/registry/components/category-select/CategorySelectComponent");
        return (
          <div>
            <CategorySelectComponent
              tableName={component.tableName}
              columnName={column.columnName}
              value={value}
              onChange={(newValue) => handleAddFormChange(column.columnName, newValue)}
              placeholder={advancedConfig?.placeholder || `${column.label} 선택...`}
              required={isRequired}
              className={commonProps.className}
            />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );
      }

      default:
        return (
          <div>
            <Input {...commonProps} />
            {advancedConfig?.helpText && <p className="mt-1 text-xs text-muted-foreground">{advancedConfig.helpText}</p>}
          </div>
        );
    }
  };

  // 기존 renderSearchFilter 함수는 AdvancedSearchFilters 컴포넌트로 대체됨

  // 파일 다운로드
  const handleDownloadFile = useCallback(async (fileInfo: FileInfo) => {
    try {
      // savedFileName이 없는 경우 파일 경로에서 추출 시도
      const serverFilename = fileInfo.savedFileName || (fileInfo.path ? fileInfo.path.split("/").pop() : null);

      if (!serverFilename) {
        // _file 속성이 있는 경우 로컬 파일로 다운로드
        if ((fileInfo as any)._file) {
          try {
            const file = (fileInfo as any)._file;

            // File 객체 유효성 검사
            if (!(file instanceof File) && !(file instanceof Blob)) {
              // console.error("❌ 잘못된 파일 객체:", file);
              toast.error("파일 객체가 손상되었습니다. 파일을 다시 업로드해주세요.");
              return;
            }

            const url = URL.createObjectURL(file);
            const link = document.createElement("a");
            link.href = url;
            link.download = fileInfo.name || file.name || "download";
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
            toast.success(`${fileInfo.name} 다운로드가 완료되었습니다.`);
            return;
          } catch (error) {
            // console.error("❌ 로컬 파일 다운로드 오류:", error);
            toast.error("로컬 파일 다운로드에 실패했습니다. 파일을 다시 업로드해주세요.");
            return;
          }
        }

        toast.error("이 파일은 이전 버전에서 저장된 파일입니다. 파일을 다시 업로드해주세요.");
        return;
      }

      toast.loading(`${fileInfo.name} 다운로드 중...`);

      await downloadFile({
        fileId: fileInfo.objid || fileInfo.id,
        serverFilename: serverFilename,
        originalName: fileInfo.name,
      });

      toast.success(`${fileInfo.name} 다운로드가 완료되었습니다.`);
    } catch (error) {
      // console.error("파일 다운로드 오류:", error);
      toast.error(`${fileInfo.name} 다운로드에 실패했습니다.`);
    }
  }, []);

  // 🗑️ 연결된 파일 삭제 함수
  const handleDeleteLinkedFile = useCallback(
    async (fileId: string, fileName: string) => {
      try {
        // 삭제 확인 다이얼로그
        if (!confirm(`"${fileName}" 파일을 삭제하시겠습니까?`)) {
          return;
        }

        // API 호출로 파일 삭제 (논리적 삭제) - apiClient 사용으로 JWT 토큰 자동 추가
        const apiClient = (await import("@/lib/api/client")).apiClient;
        const response = await apiClient.delete(`/files/${fileId}`, {
          data: {
            writer: "current_user", // 현재 사용자 정보
          },
        });

        const result = response.data;

        if (!result.success) {
          throw new Error(result.message || "파일 삭제 실패");
        }

        // 성공 메시지
        toast.success(`"${fileName}" 파일이 삭제되었습니다.`);

        // 파일 목록 새로고침
        if (showFileManagementModal && selectedRowForFiles && component.tableName) {
          const primaryKeyField = Object.keys(selectedRowForFiles)[0];
          const recordId = selectedRowForFiles[primaryKeyField];

          try {
            const response = await getLinkedFiles(component.tableName, recordId);
            setLinkedFiles(response.files || []);
          } catch (error) {
            // 파일 목록 새로고침 실패 시 무시
          }
        }
      } catch (error) {
        toast.error(`"${fileName}" 파일 삭제에 실패했습니다.`);
      }
    },
    [showFileManagementModal, selectedRowForFiles, component.tableName],
  );

  // 셀 값 포맷팅
  const formatCellValue = (value: any, column: DataTableColumn, rowData?: Record<string, any>): React.ReactNode => {
    // 가상 파일 컬럼의 경우 value가 없어도 파일 아이콘을 표시해야 함
    if (!column.isVirtualFileColumn && (value === null || value === undefined)) return "";

    // 실제 웹 타입 가져오기 (input_type 포함)
    const actualWebType = getColumnWebType(column.columnName);

    // 파일 타입 컬럼 처리 (가상 파일 컬럼 포함)
    const isFileColumn = actualWebType === "file" || column.columnName === "file_path" || column.isVirtualFileColumn;

    // 🖼️ 이미지 타입 컬럼: 썸네일로 표시
    const isImageColumn = actualWebType === "image" || actualWebType === "img";
    if (isImageColumn && value) {
      // value가 objid (숫자 또는 숫자 문자열)인 경우 파일 API URL 사용
      // 🔑 download 대신 preview 사용 (공개 접근 허용)
      const isObjid = /^\d+$/.test(String(value));
      // 🔑 상대 경로(/api/...) 대신 전체 URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지)
      const imageUrl = isObjid 
        ? getFilePreviewUrl(String(value)) 
        : getFullImageUrl(String(value));
      
      return (
        <div className="flex justify-center">
          <img
            src={imageUrl}
            alt="이미지"
            className="h-10 w-10 rounded object-cover cursor-pointer hover:opacity-80 transition-opacity"
            style={{ maxWidth: "40px", maxHeight: "40px" }}
            onClick={(e) => {
              e.stopPropagation();
              // 이미지 클릭 시 크게 보기 (새 탭에서 열기)
              window.open(imageUrl, "_blank");
            }}
            onError={(e) => {
              // 이미지 로드 실패 시 기본 아이콘 표시
              (e.target as HTMLImageElement).style.display = "none";
            }}
          />
        </div>
      );
    }

    // 파일 타입 컬럼은 파일 아이콘으로 표시 (컬럼별 파일 관리)
    if (isFileColumn && rowData) {
      // 현재 행의 기본키 값 가져오기
      const primaryKeyField = Object.keys(rowData)[0];
      const recordId = rowData[primaryKeyField];

      // 해당 컬럼에 대한 파일 상태 확인
      const columnFileKey = `${recordId}_${column.columnName}`;
      const columnFileStatus = fileStatusMap[columnFileKey];
      const hasFiles = columnFileStatus?.hasFiles || false;
      const fileCount = columnFileStatus?.fileCount || 0;

      return (
        <div className="flex justify-center">
          <Button
            variant="ghost"
            size="sm"
            className="hover:bg-accent h-8 w-8 p-0"
            onClick={() => handleColumnFileClick(rowData, column)}
            title={hasFiles ? `${fileCount}개 파일 보기` : "파일 업로드"}
          >
            {hasFiles ? (
              <div className="relative">
                <FolderOpen className="text-primary h-4 w-4" />
                {fileCount > 0 && (
                  <div className="absolute -top-1 -right-1 flex h-3 w-3 items-center justify-center rounded-full bg-primary text-[10px] text-white">
                    {fileCount > 9 ? "9+" : fileCount}
                  </div>
                )}
              </div>
            ) : (
              <Folder className="h-4 w-4 text-muted-foreground" />
            )}
          </Button>
        </div>
      );
    }

    // 실제 웹 타입으로 스위치 (input_type="category"도 포함됨)
    switch (actualWebType) {
      case "category": {
        // 카테고리 타입: 배지로 표시 (배지 없음 옵션 지원)
        if (!value) return "";

        const mapping = categoryMappings[column.columnName];
        const categoryData = mapping?.[String(value)];

        // 매핑 데이터가 있으면 라벨과 색상 사용, 없으면 코드값만 텍스트로 표시
        const displayLabel = categoryData?.label || String(value);
        const displayColor = categoryData?.color;

        // 배지 없음 옵션: color가 없거나, "none"이거나, 매핑 데이터가 없으면 텍스트만 표시
        if (!displayColor || displayColor === "none" || !categoryData) {
          return <span className="text-sm">{displayLabel}</span>;
        }

        return (
          <Badge
            style={{
              backgroundColor: displayColor,
              borderColor: displayColor,
            }}
            className="text-white"
          >
            {displayLabel}
          </Badge>
        );
      }

      case "date":
        if (value) {
          try {
            const date = new Date(value);
            return date.toLocaleDateString("ko-KR");
          } catch {
            return value;
          }
        }
        break;

      case "datetime":
        if (value) {
          try {
            const date = new Date(value);
            return date.toLocaleString("ko-KR");
          } catch {
            return value;
          }
        }
        break;

      case "number":
      case "decimal":
        if (value !== null && value !== undefined && value !== "") {
          const numValue = typeof value === "string" ? parseFloat(value) : value;
          if (!isNaN(numValue)) {
            return numValue.toLocaleString("ko-KR");
          }
        }
        break;

      default: {
        const strValue = String(value);
        // 카테고리 코드 패턴 감지 (CAT_ 또는 CATEGORY_로 시작하는 값, 세미콜론 구분 다중값 포함)
        const looksLikeCatCode = (v: string) => v.startsWith("CAT_") || v.startsWith("CATEGORY_");
        if (looksLikeCatCode(strValue) || (strValue.includes(";") && strValue.split(";").some(s => looksLikeCatCode(s.trim())))) {
          // 세미콜론 구분 다중값 처리
          const codes = strValue.includes(";") ? strValue.split(";").map(s => s.trim()) : [strValue];
          const labels: string[] = [];
          for (const code of codes) {
            let found = false;
            // 1. 해당 컬럼의 categoryMappings에서 먼저 검색
            const colMapping = categoryMappings[column.columnName];
            if (colMapping?.[code]) { labels.push(colMapping[code].label); found = true; }
            // 2. 전체 매핑에서 검색
            if (!found) {
              for (const cn of Object.keys(categoryMappings)) {
                const mapping = categoryMappings[cn];
                if (mapping?.[code]) { labels.push(mapping[code].label); found = true; break; }
              }
            }
            // 3. categoryCodeLabels에서 검색
            if (!found && categoryCodeLabels[code]) { labels.push(categoryCodeLabels[code]); found = true; }
            if (!found) labels.push(code);
          }
          return <span className="text-sm">{labels.join(", ")}</span>;
        }
        return strValue;
      }
    }

    return String(value);
  };

  return (
    <div
      className={cn(
        "flex h-full flex-col rounded-xl border border-border/60 bg-background shadow-sm",
        className,
      )}
      style={{ ...style, minHeight: "680px" }}
    >
      {/* 헤더 */}
      <div className="p-6 pb-3">
        <div className="flex items-center justify-between">
          <div className="flex items-center space-x-2">
            <Database className="text-muted-foreground h-4 w-4" />
            <h3 className="text-lg font-semibold">{component.title || component.label}</h3>
            {loading && (
              <Badge variant="secondary" className="flex items-center gap-1">
                <Loader2 className="h-3 w-3 animate-spin" />
                로딩중...
              </Badge>
            )}
          </div>
          <div className="flex items-center space-x-2">
            {/* 선택된 행 개수 표시 */}
            {selectedRows.size > 0 && (
              <Badge variant="secondary" className="text-xs">
                {selectedRows.size}개 선택됨
              </Badge>
            )}

            {searchFilters.length > 0 && (
              <Badge variant="outline" className="text-xs">
                <Search className="mr-1 h-3 w-3" />
                필터 {searchFilters.length}개
              </Badge>
            )}

            {/* CRUD 버튼들 */}
            {component.enableAdd && (
              <Button
                size="sm"
                onClick={() => {
                  if (isPreviewMode) {
                    return;
                  }
                  handleAddData();
                }}
                disabled={loading || isPreviewMode}
                className="gap-2"
              >
                <Plus className="h-3 w-3" />
                {component.addButtonText || "추가"}
              </Button>
            )}

            {component.enableEdit && selectedRows.size === 1 && (
              <Button
                size="sm"
                onClick={() => {
                  if (isPreviewMode) {
                    return;
                  }
                  handleEditData();
                }}
                disabled={loading || isPreviewMode}
                className="gap-2"
                variant="outline"
              >
                <Edit className="h-3 w-3" />
                {component.editButtonText || "수정"}
              </Button>
            )}

            {component.enableDelete && selectedRows.size > 0 && (
              <Button
                size="sm"
                variant="destructive"
                onClick={() => {
                  if (isPreviewMode) {
                    return;
                  }
                  handleDeleteData();
                }}
                disabled={loading || isPreviewMode}
                className="gap-2"
              >
                <Trash2 className="h-3 w-3" />
                {component.deleteButtonText || "삭제"}
              </Button>
            )}

            {component.showSearchButton && (
              <Button size="sm" onClick={handleSearch} disabled={loading} className="gap-2">
                <Search className="h-3 w-3" />
                {component.searchButtonText || "검색"}
              </Button>
            )}

            <Button size="sm" variant="outline" onClick={() => loadData(1, {})} disabled={loading} className="gap-2">
              <RotateCcw className="h-3 w-3" />
              새로고침
            </Button>
          </div>
        </div>

        {/* 검색 필터 - 항상 표시 (컬럼 정보 기반 자동 생성) */}
        {tableColumns && tableColumns.length > 0 && (
          <>
            <Separator className="my-2" />
            <AdvancedSearchFilters
              filters={searchFilters.length > 0 ? searchFilters : []}
              searchValues={searchValues}
              onSearchValueChange={handleSearchValueChange}
              onSearch={handleSearch}
              onClearFilters={() => {
                setSearchValues({});
                loadData(1, {});
              }}
              tableColumns={tableColumns}
              tableName={component.tableName}
            />
          </>
        )}
      </div>

      {/* 테이블 내용 */}
      <div className="flex-1 p-0">
        <div className="flex h-full flex-col">
          {visibleColumns.length > 0 ? (
            <>
              <div className="overflow-hidden rounded-lg border border-border/60 bg-white shadow-sm">
                <Table style={{ tableLayout: "fixed" }}>
                  <TableHeader className="bg-muted/50 border-primary/20 border-b-2">
                    <TableRow>
                      {/* 체크박스 컬럼 (삭제 기능이 활성화된 경우) */}
                      {component.enableDelete && (
                        <TableHead className="px-4" style={{ width: "48px", minWidth: "48px", maxWidth: "48px" }}>
                          <Checkbox
                            checked={selectedRows.size === data.length && data.length > 0}
                            onCheckedChange={handleSelectAll}
                          />
                        </TableHead>
                      )}
                      {visibleColumns.map((column: DataTableColumn, columnIndex) => {
                        const columnWidth = columnWidths[column.id];

                        return (
                          <TableHead
                            key={column.id}
                            ref={(el) => (columnRefs.current[column.id] = el)}
                            className="text-foreground/90 hover:bg-muted/70 relative px-4 text-center font-bold transition-colors select-none"
                            style={{
                              width: columnWidth ? `${columnWidth}px` : undefined,
                              userSelect: "none",
                            }}
                          >
                            {column.label}
                            {/* 리사이즈 핸들 */}
                            {columnIndex < visibleColumns.length - 1 && (
                              <div
                                className="absolute top-0 right-0 z-20 h-full w-2 cursor-col-resize hover:bg-primary"
                                style={{ marginRight: "-4px", paddingLeft: "4px", paddingRight: "4px" }}
                                onClick={(e) => e.stopPropagation()}
                                onMouseDown={(e) => {
                                  e.preventDefault();
                                  e.stopPropagation();

                                  const thElement = columnRefs.current[column.id];
                                  if (!thElement) return;

                                  isResizingRef.current = true;

                                  const startX = e.clientX;
                                  const startWidth = columnWidth || thElement.offsetWidth;

                                  // 드래그 중 텍스트 선택 방지
                                  document.body.style.userSelect = "none";
                                  document.body.style.cursor = "col-resize";

                                  const handleMouseMove = (moveEvent: MouseEvent) => {
                                    moveEvent.preventDefault();

                                    const diff = moveEvent.clientX - startX;
                                    const newWidth = Math.max(80, startWidth + diff);

                                    // 직접 DOM 스타일 변경 (리렌더링 없음)
                                    if (thElement) {
                                      thElement.style.width = `${newWidth}px`;
                                    }
                                  };

                                  const handleMouseUp = () => {
                                    // 최종 너비를 state에 저장
                                    if (thElement) {
                                      const finalWidth = Math.max(80, thElement.offsetWidth);
                                      setColumnWidths((prev) => ({ ...prev, [column.id]: finalWidth }));
                                    }

                                    // 텍스트 선택 복원
                                    document.body.style.userSelect = "";
                                    document.body.style.cursor = "";

                                    // 약간의 지연 후 리사이즈 플래그 해제
                                    setTimeout(() => {
                                      isResizingRef.current = false;
                                    }, 100);

                                    document.removeEventListener("mousemove", handleMouseMove);
                                    document.removeEventListener("mouseup", handleMouseUp);
                                  };

                                  document.addEventListener("mousemove", handleMouseMove);
                                  document.addEventListener("mouseup", handleMouseUp);
                                }}
                              />
                            )}
                          </TableHead>
                        );
                      })}
                      {/* 자동 파일 컬럼 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {loading ? (
                      <TableRow>
                        <TableCell
                          colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
                          className="h-32 text-center"
                        >
                          <div className="text-muted-foreground flex items-center justify-center gap-2">
                            <Loader2 className="h-4 w-4 animate-spin" />
                            데이터를 불러오는 중...
                          </div>
                        </TableCell>
                      </TableRow>
                    ) : data.length > 0 ? (
                      data.map((row, rowIndex) => (
                        <TableRow key={rowIndex} className="transition-colors duration-150 hover:bg-muted/50">
                          {/* 체크박스 셀 (삭제 기능이 활성화된 경우) */}
                          {component.enableDelete && (
                            <TableCell className="px-4" style={{ width: "48px", minWidth: "48px", maxWidth: "48px" }}>
                              <Checkbox
                                checked={selectedRows.has(rowIndex)}
                                onCheckedChange={(checked) => handleRowSelect(rowIndex, checked as boolean)}
                              />
                            </TableCell>
                          )}
                          {visibleColumns.map((column: DataTableColumn) => {
                            const isNumeric = column.widgetType === "number" || column.widgetType === "decimal";
                            return (
                              <TableCell
                                key={column.id}
                                className="overflow-hidden px-4 text-sm font-medium text-ellipsis whitespace-nowrap text-foreground"
                                style={{ textAlign: isNumeric ? "right" : "left" }}
                              >
                                {formatCellValue(row[column.columnName], column, row)}
                              </TableCell>
                            );
                          })}
                          {/* 자동 파일 셀 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
                        </TableRow>
                      ))
                    ) : (
                      <TableRow>
                        <TableCell
                          colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
                          className="h-32 text-center"
                        >
                          <div className="text-muted-foreground flex flex-col items-center gap-2">
                            <Database className="h-8 w-8 opacity-40" />
                            <p>검색 결과가 없습니다</p>
                            <p className="text-xs">검색 조건을 변경하거나 새로고침을 시도해보세요</p>
                          </div>
                        </TableCell>
                      </TableRow>
                    )}
                  </TableBody>
                </Table>
              </div>

              {/* 페이지네이션 */}
              {component.pagination?.enabled && totalPages > 1 && (
                <div className="mt-auto border-t border-border/60 bg-muted/30">
                  <div className="flex items-center justify-between px-6 py-3">
                    {component.pagination.showPageInfo && (
                      <div className="text-muted-foreground text-sm">
                        총 <span className="font-medium">{total.toLocaleString()}</span>개 중{" "}
                        <span className="font-medium">{((currentPage - 1) * pageSize + 1).toLocaleString()}</span>-
                        <span className="font-medium">{Math.min(currentPage * pageSize, total).toLocaleString()}</span>
                      </div>
                    )}
                    <div className="flex items-center space-x-2">
                      {component.pagination.showFirstLast && (
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => handlePageChange(1)}
                          disabled={currentPage === 1 || loading}
                          className="gap-1"
                        >
                          처음
                        </Button>
                      )}
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => handlePageChange(currentPage - 1)}
                        disabled={currentPage === 1 || loading}
                        className="gap-1"
                      >
                        <ChevronLeft className="h-3 w-3" />
                        이전
                      </Button>
                      <div className="flex items-center gap-1 text-sm font-medium">
                        <span>{currentPage}</span>
                        <span className="text-muted-foreground">/</span>
                        <span>{totalPages}</span>
                      </div>
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => handlePageChange(currentPage + 1)}
                        disabled={currentPage === totalPages || loading}
                        className="gap-1"
                      >
                        다음
                        <ChevronRight className="h-3 w-3" />
                      </Button>
                      {component.pagination.showFirstLast && (
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => handlePageChange(totalPages)}
                          disabled={currentPage === totalPages || loading}
                          className="gap-1"
                        >
                          마지막
                        </Button>
                      )}
                    </div>
                  </div>
                </div>
              )}
            </>
          ) : (
            <div className="flex flex-1 items-center justify-center">
              <div className="text-muted-foreground flex flex-col items-center gap-2">
                <Database className="h-8 w-8 opacity-40" />
                <p className="text-sm">표시할 컬럼이 없습니다</p>
                <p className="text-xs">테이블 설정에서 컬럼을 추가해주세요</p>
              </div>
            </div>
          )}
        </div>
      </div>

      {/* SaveModal (등록/수정 통합) */}
      <SaveModal
        isOpen={showSaveModal}
        onClose={() => {
          setShowSaveModal(false);
          setSaveModalData(undefined);
          setSaveModalScreenId(undefined);
        }}
        screenId={saveModalScreenId}
        modalSize={component.addModalConfig?.modalSize || "lg"}
        initialData={saveModalData}
        menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 (카테고리 스코프용)
        onSaveSuccess={() => {
          // 저장 성공 시 테이블 새로고침
          loadData(currentPage, searchValues); // 현재 페이지로 다시 로드
          setSelectedRows(new Set()); // 선택 해제
        }}
      />

      {/* 기존 데이터 추가 모달 (제거 예정 - SaveModal로 대체됨) */}
      <Dialog open={false} onOpenChange={() => {}}>
        <DialogContent className={`max-h-[80vh] overflow-hidden ${getModalSizeClass()}`}>
          <DialogHeader>
            <DialogTitle>{component.addModalConfig?.title || "새 데이터 추가"}</DialogTitle>
            <DialogDescription>
              {component.addModalConfig?.description ||
                `${component.title || component.label}에 새로운 데이터를 추가합니다.`}
            </DialogDescription>
          </DialogHeader>

          <div className="py-4">
            <div className={getLayoutClass()}>
              {getDisplayColumns().map((column) => (
                <div key={column.id} className="space-y-2">
                  <Label htmlFor={column.columnName} className="text-sm font-medium">
                    {column.label}
                    {isRequiredField(column.columnName) && <span className="ml-1 text-amber-500">*</span>}
                  </Label>
                  <div>{renderAddFormInput(column)}</div>
                </div>
              ))}
            </div>
          </div>

          <DialogFooter>
            <Button variant="outline" onClick={handleAddModalClose} disabled={isAdding}>
              {component.addModalConfig?.cancelButtonText || "취소"}
            </Button>
            <Button onClick={handleAddSubmit} disabled={isAdding}>
              {isAdding ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  추가 중...
                </>
              ) : (
                <>
                  <Plus className="mr-2 h-4 w-4" />
                  {component.addModalConfig?.submitButtonText || "추가"}
                </>
              )}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 기존 데이터 수정 모달 (제거 예정 - SaveModal로 대체됨) */}
      <Dialog open={false} onOpenChange={() => {}}>
        <DialogContent className={`max-h-[80vh] overflow-hidden ${getModalSizeClass()}`}>
          <DialogHeader>
            <DialogTitle>데이터 수정</DialogTitle>
            <DialogDescription>선택된 데이터를 수정합니다.</DialogDescription>
          </DialogHeader>

          <div className="py-4">
            <div className={getLayoutClass()}>
              {getDisplayColumns().map((column) => (
                <div key={column.id} className="space-y-2">
                  <Label htmlFor={`edit-${column.columnName}`} className="text-sm font-medium">
                    {column.label}
                    {isRequiredField(column.columnName) && <span className="ml-1 text-amber-500">*</span>}
                  </Label>
                  <div>{renderEditFormInput(column)}</div>
                </div>
              ))}
            </div>
          </div>

          <DialogFooter>
            <Button
              variant="outline"
              onClick={() => {
                setShowEditModal(false);
                setEditFormData({});
                setEditingRowData(null);
                setUploadedFiles({}); // 파일 상태 초기화
              }}
              disabled={isEditing}
            >
              취소
            </Button>
            <Button onClick={handleEditSubmit} disabled={isEditing}>
              {isEditing ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  수정 중...
                </>
              ) : (
                <>
                  <Edit className="mr-2 h-4 w-4" />
                  {component.editButtonText || "수정"}
                </>
              )}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 파일 정보 모달 */}
      <Dialog open={showFileModal} onOpenChange={setShowFileModal}>
        <DialogContent className="flex max-h-[80vh] max-w-2xl flex-col overflow-hidden">
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2">
              <File className="h-5 w-5" />
              파일 정보 - {currentFileColumn?.label || "파일"}
            </DialogTitle>
            <DialogDescription>
              {currentFileData?.totalCount === 1
                ? "1개의 파일이 저장되어 있습니다."
                : `총 ${currentFileData?.totalCount}개의 파일이 저장되어 있습니다.`}
            </DialogDescription>
          </DialogHeader>

          <div className="flex-1 overflow-auto">
            {currentFileData?.files && Array.isArray(currentFileData.files) && (
              <div className="space-y-3">
                {currentFileData.files.map((fileInfo: FileInfo, index: number) => {
                  const isImage = fileInfo.type?.startsWith("image/");

                  return (
                    <div key={index} className="rounded-lg border bg-muted p-4 transition-colors hover:bg-muted">
                      <div className="flex items-start gap-4">
                        {/* 파일 아이콘/미리보기 */}
                        <div className="flex-shrink-0">
                          {isImage ? (
                            <div className="flex h-16 w-16 items-center justify-center rounded border bg-white">
                              <div className="text-xs font-medium text-emerald-600">IMG</div>
                            </div>
                          ) : (
                            <div className="flex h-16 w-16 items-center justify-center rounded border bg-white">
                              <File className="h-8 w-8 text-muted-foreground" />
                            </div>
                          )}
                        </div>

                        {/* 파일 정보 */}
                        <div className="min-w-0 flex-1">
                          <h4 className="truncate font-medium text-foreground" title={fileInfo.name}>
                            {fileInfo.name}
                          </h4>
                          <div className="text-muted-foreground mt-1 space-y-1 text-sm">
                            <div className="flex items-center gap-4">
                              <span>크기: {(fileInfo.size / 1024 / 1024).toFixed(2)} MB</span>
                              <span>타입: {fileInfo.type || "알 수 없음"}</span>
                            </div>
                            <div className="flex items-center gap-4">
                              {fileInfo.regdate && (
                                <span>등록일: {new Date(fileInfo.regdate).toLocaleString("ko-KR")}</span>
                              )}
                              {fileInfo.writer && <span>등록자: {fileInfo.writer}</span>}
                            </div>
                          </div>
                        </div>

                        {/* 액션 버튼 */}
                        <div className="flex flex-col gap-2">
                          {isImage && (
                            <Button
                              variant="outline"
                              size="sm"
                              className="w-full"
                              onClick={() => handlePreviewImage(fileInfo)}
                            >
                              <Eye className="mr-1 h-4 w-4" />
                              미리보기
                            </Button>
                          )}
                          <Button
                            variant="outline"
                            size="sm"
                            className="w-full"
                            onClick={() => handleDownloadFile(fileInfo)}
                          >
                            <Download className="mr-1 h-4 w-4" />
                            다운로드
                          </Button>
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            )}

            {/* 요약 정보 */}
            {currentFileData && (
              <div className="border-primary/20 bg-accent mt-4 rounded-lg border p-3">
                <h5 className="mb-2 font-medium text-primary">파일 요약</h5>
                <div className="grid grid-cols-2 gap-4 text-sm text-primary">
                  <div>
                    <span className="font-medium">총 파일 수:</span>
                    {"  "}
                    {currentFileData.totalCount}개
                  </div>
                  <div>
                    <span className="font-medium">총 크기:</span>
                    {"  "}
                    {(currentFileData.totalSize / 1024 / 1024).toFixed(2)} MB
                  </div>
                </div>
              </div>
            )}
          </div>

          <DialogFooter>
            <Button variant="outline" onClick={() => setShowFileModal(false)}>
              <X className="mr-1 h-4 w-4" />
              닫기
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 데이터 삭제 확인 다이얼로그 */}
      <Dialog open={showDeleteDialog} onOpenChange={handleDeleteDialogClose}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>데이터 삭제 확인</DialogTitle>
            <DialogDescription>
              선택된 <strong>{selectedRows.size}개</strong>의 데이터를 삭제하시겠습니까?
              <br />
              <span className="text-destructive">이 작업은 되돌릴 수 없습니다.</span>
            </DialogDescription>
          </DialogHeader>

          <DialogFooter>
            <Button variant="outline" onClick={handleDeleteDialogClose} disabled={isDeleting}>
              취소
            </Button>
            <Button variant="destructive" onClick={handleDeleteConfirm} disabled={isDeleting}>
              {isDeleting ? (
                <>
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                  삭제 중...
                </>
              ) : (
                <>
                  <Trash2 className="mr-2 h-4 w-4" />
                  삭제
                </>
              )}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 이미지 미리보기 다이얼로그 */}
      <Dialog open={showPreviewModal} onOpenChange={closePreviewModal}>
        <DialogContent className="max-h-[90vh] max-w-4xl overflow-hidden">
          <DialogHeader>
            <DialogTitle className="flex items-center justify-between">
              <span className="truncate">{previewImage?.name}</span>
              <div className="flex items-center space-x-2">
                <Button size="sm" variant="outline" onClick={() => handleZoom("out")} disabled={zoom <= 0.25}>
                  <ZoomOut className="h-4 w-4" />
                </Button>
                <span className="min-w-[60px] text-center text-sm text-muted-foreground">{Math.round(zoom * 100)}%</span>
                <Button size="sm" variant="outline" onClick={() => handleZoom("in")} disabled={zoom >= 3}>
                  <ZoomIn className="h-4 w-4" />
                </Button>
                <Button size="sm" variant="outline" onClick={handleRotate}>
                  <RotateCw className="h-4 w-4" />
                </Button>
                {previewImage && (
                  <Button
                    size="sm"
                    variant="outline"
                    onClick={() => {
                      handleDownloadFile(previewImage);
                    }}
                  >
                    <Download className="h-4 w-4" />
                  </Button>
                )}
              </div>
            </DialogTitle>
          </DialogHeader>

          <div className="flex flex-1 items-center justify-center overflow-auto rounded-lg bg-muted p-4">
            {previewImage && (
              <img
                src={alternativeImageUrl || getFilePreviewUrl(previewImage.id)}
                alt={previewImage.name}
                className="max-h-full max-w-full object-contain transition-transform duration-200"
                style={{
                  transform: `scale(${zoom}) rotate(${rotation}deg)`,
                }}
                onError={handleImageError}
              />
            )}
          </div>

          {previewImage && (
            <div className="flex items-center justify-between border-t pt-3 text-sm text-muted-foreground">
              <div>크기: {formatFileSize(previewImage.size)}</div>
              <div>타입: {previewImage.type}</div>
              <div>업로드: {new Date(previewImage.uploadedAt).toLocaleDateString("ko-KR")}</div>
            </div>
          )}
        </DialogContent>
      </Dialog>

      {/* 파일 관리 모달 */}
      <Dialog open={showFileManagementModal} onOpenChange={setShowFileManagementModal}>
        <DialogContent className="max-h-[80vh] max-w-4xl overflow-hidden">
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2">
              <Folder className="h-5 w-5" />
              파일 관리
              {selectedRowForFiles && (
                <Badge variant="outline" className="ml-2">
                  {Object.keys(selectedRowForFiles)[0]}: {selectedRowForFiles[Object.keys(selectedRowForFiles)[0]]}
                </Badge>
              )}
            </DialogTitle>
            <DialogDescription>
              {linkedFiles.length > 0
                ? `${linkedFiles.length}개의 파일이 연결되어 있습니다.`
                : "연결된 파일이 없습니다. 새 파일을 업로드하세요."}
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-4">
            {/* 기존 파일 목록 */}
            {linkedFiles.length > 0 && (
              <div className="space-y-3">
                <h4 className="text-sm font-medium text-foreground">연결된 파일</h4>
                {linkedFiles.map((file: any, index: number) => (
                  <div key={index} className="flex items-center justify-between rounded-lg border p-3">
                    <div className="flex items-center space-x-3">
                      <File className="text-primary h-5 w-5" />
                      <div>
                        <div className="font-medium">{file.realFileName}</div>
                        <div className="text-sm text-muted-foreground">
                          {(Number(file.fileSize) / 1024 / 1024).toFixed(2)} MB • {file.docTypeName}
                          {file.regdate && <span> • {new Date(file.regdate).toLocaleString("ko-KR")}</span>}
                          {file.writer && <span> • {file.writer}</span>}
                        </div>
                      </div>
                    </div>
                    <div className="flex items-center space-x-2">
                      {file.fileExt && ["jpg", "jpeg", "png", "gif"].includes(file.fileExt.toLowerCase()) && (
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => {
                            // 이미지 미리보기 (기존 로직 재사용)
                            const fileInfo: FileInfo = {
                              id: file.objid,
                              name: file.realFileName,
                              size: Number(file.fileSize),
                              type: `image/${file.fileExt}`,
                              path: file.filePath,
                              objid: file.objid,
                              extension: file.fileExt,
                              uploadedAt: file.regdate || new Date().toISOString(),
                              lastModified: file.regdate || new Date().toISOString(),
                            };
                            handlePreviewImage(fileInfo);
                          }}
                        >
                          <Eye className="h-4 w-4" />
                        </Button>
                      )}
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => {
                          // 파일 다운로드 (기존 로직 재사용)
                          const fileInfo: FileInfo = {
                            id: file.objid,
                            name: file.realFileName,
                            size: Number(file.fileSize),
                            type: `application/${file.fileExt}`,
                            path: file.filePath,
                            objid: file.objid,
                            savedFileName: file.savedFileName,
                          };
                          handleDownloadFile(fileInfo);
                        }}
                      >
                        <Download className="h-4 w-4" />
                      </Button>

                      {/* 🗑️ 파일 삭제 버튼 */}
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => handleDeleteLinkedFile(file.objid, file.realFileName)}
                        className="hover:bg-destructive/10 text-destructive hover:text-destructive"
                      >
                        <Trash2 className="h-4 w-4" />
                      </Button>
                    </div>
                  </div>
                ))}
              </div>
            )}

            {/* 파일 업로드 섹션 */}
            <div className="space-y-3">
              <div className="flex items-center justify-between">
                <h4 className="text-sm font-medium text-foreground">{selectedColumnForFiles?.label || "파일"} 업로드</h4>
                {selectedColumnForFiles?.isVirtualFileColumn && (
                  <Badge variant="secondary" className="text-xs">
                    {selectedColumnForFiles.fileColumnConfig?.docTypeName || "문서"}
                  </Badge>
                )}
              </div>

              {selectedRowForFiles && selectedColumnForFiles && component.tableName && (
                <div className="rounded-lg border p-4">
                  <FileUpload
                    component={{
                      id: `modal-file-upload-${selectedColumnForFiles.id}`,
                      type: "file",
                      position: { x: 0, y: 0 },
                      size: { width: 400, height: 300 },
                      uploadedFiles: [], // 빈 배열로 초기화
                      fileConfig: {
                        maxSize: selectedColumnForFiles.fileColumnConfig?.maxFiles || 10,
                        maxFiles: selectedColumnForFiles.fileColumnConfig?.maxFiles || 5,
                        multiple: true,
                        showPreview: true,
                        showProgress: true,
                        autoUpload: true, // 자동 업로드 활성화
                        chunkedUpload: false, // 기본 업로드 방식
                        dragDropText: `${selectedColumnForFiles.label} 파일을 드래그하여 업로드하거나 클릭하세요`,
                        uploadButtonText: "파일 업로드", // 업로드 버튼 텍스트
                        accept: selectedColumnForFiles.fileColumnConfig?.accept || ["*/*"],
                        // 문서 분류 설정
                        docType: selectedColumnForFiles.fileColumnConfig?.docType || "DOCUMENT",
                        docTypeName: selectedColumnForFiles.fileColumnConfig?.docTypeName || "일반 문서",
                        // 자동 연결 설정
                        autoLink: true,
                        linkedTable: component.tableName,
                        linkedField: Object.keys(selectedRowForFiles)[0], // 기본키 필드
                        recordId: selectedRowForFiles[Object.keys(selectedRowForFiles)[0]], // 기본키 값
                        // 가상 파일 컬럼별 구분을 위한 추가 정보
                        columnName: selectedColumnForFiles.columnName,
                        isVirtualFileColumn: selectedColumnForFiles.isVirtualFileColumn,
                      },
                    }}
                    onUpdateComponent={() => {
                      // 모달에서는 컴포넌트 업데이트가 필요 없으므로 빈 함수 제공
                    }}
                    onFileUpload={async () => {
                      // 파일 업로드 완료 후 연결된 파일 목록 새로고침
                      if (selectedRowForFiles && selectedColumnForFiles) {
                        const result = await checkColumnFileStatus(selectedRowForFiles, selectedColumnForFiles);
                        if (result) {
                          setLinkedFiles(result.files);

                          // 파일 상태 맵도 업데이트
                          const primaryKeyField = Object.keys(selectedRowForFiles)[0];
                          const recordId = selectedRowForFiles[primaryKeyField];
                          const columnFileKey = `${recordId}_${selectedColumnForFiles.columnName}`;

                          setFileStatusMap((prev) => {
                            const newFileStatusMap = {
                              ...prev,
                              [columnFileKey]: {
                                hasFiles: result.hasFiles,
                                fileCount: result.fileCount,
                              },
                            };
                            return newFileStatusMap;
                          });

                          // 전체 테이블의 해당 컬럼 파일 상태도 강제 새로고침
                          setTimeout(() => {
                            // 테이블 데이터 새로고침을 위해 loadData 호출
                            if (data && data.length > 0) {
                              // 현재 데이터를 그대로 사용하되 파일 상태만 새로고침
                              const refreshPromises = data.map(async (row) => {
                                const pk = Object.keys(row)[0];
                                const rowId = row[pk];
                                const fileKey = `${rowId}_${selectedColumnForFiles.columnName}`;

                                const columnStatus = await checkColumnFileStatus(row, selectedColumnForFiles);
                                if (columnStatus) {
                                  setFileStatusMap((prev) => ({
                                    ...prev,
                                    [fileKey]: {
                                      hasFiles: columnStatus.hasFiles,
                                      fileCount: columnStatus.fileCount,
                                    },
                                  }));
                                }
                              });

                              Promise.all(refreshPromises);
                            }
                          }, 100);
                        }
                      }
                    }}
                  />
                </div>
              )}
            </div>
          </div>

          <DialogFooter>
            <Button variant="outline" onClick={() => setShowFileManagementModal(false)}>
              닫기
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
};
