"use client";

import React, { useState, useEffect, useMemo } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/ui/dialog";
import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic";
import { screenApi } from "@/lib/api/screen";
import { ComponentData } from "@/types/screen";
import { toast } from "sonner";
import { dynamicFormApi } from "@/lib/api/dynamicForm";
import { useAuth } from "@/hooks/useAuth";
import { ConditionalZone, LayerDefinition } from "@/types/screen-management";
import { convertV2ToLegacy, isValidV2Layout } from "@/lib/utils/layoutV2Converter";
import { ScreenContextProvider } from "@/contexts/ScreenContext";
import { entityJoinApi } from "@/lib/api/entityJoin";
import { useTabId } from "@/contexts/TabIdContext";
import { useTabStore } from "@/stores/tabStore";

interface EditModalState {
  isOpen: boolean;
  screenId: number | null;
  title: string;
  description?: string;
  modalSize: "sm" | "md" | "lg" | "xl";
  editData: Record<string, any>;
  onSave?: () => void;
  groupByColumns?: string[]; // 🆕 그룹핑 컬럼 (예: ["order_no"])
  tableName?: string; // 🆕 테이블명 (그룹 조회용)
  buttonConfig?: any; // 🆕 버튼 설정 (제어로직 실행용)
  buttonContext?: any; // 🆕 버튼 컨텍스트 (screenId, userId 등)
  saveButtonConfig?: {
    enableDataflowControl?: boolean;
    dataflowConfig?: any;
    dataflowTiming?: string;
  }; // 🆕 모달 내부 저장 버튼의 제어로직 설정
  menuObjid?: number; // 🆕 메뉴 OBJID (카테고리 스코프용)
}

interface EditModalProps {
  className?: string;
}

/**
 * 모달 내부에서 저장 버튼 찾기 (재귀적으로 탐색)
 * action.type이 "save"인 button-primary 컴포넌트를 찾음
 */
const findSaveButtonInComponents = (components: any[]): any | null => {
  if (!components || !Array.isArray(components)) return null;

  for (const comp of components) {
    // button-primary이고 action.type이 save인 경우
    if (comp.componentType === "button-primary" && comp.componentConfig?.action?.type === "save") {
      return comp;
    }

    // conditional-container의 sections 내부 탐색
    if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) {
      for (const section of comp.componentConfig.sections) {
        if (section.screenId) {
          // 조건부 컨테이너의 내부 화면은 별도로 로드해야 함
          // 여기서는 null 반환하고, loadSaveButtonConfig에서 처리
          continue;
        }
      }
    }

    // 자식 컴포넌트가 있으면 재귀 탐색
    if (comp.children && Array.isArray(comp.children)) {
      const found = findSaveButtonInComponents(comp.children);
      if (found) return found;
    }
  }

  return null;
};

export const EditModal: React.FC<EditModalProps> = ({ className }) => {
  const { user } = useAuth();
  const tabId = useTabId();
  const activeTabId = useTabStore((s) => s[s.mode].activeTabId);
  const isTabActive = !tabId || tabId === activeTabId;
  const [modalState, setModalState] = useState<EditModalState>({
    isOpen: false,
    screenId: null,
    title: "",
    description: "",
    modalSize: "md",
    editData: {},
    onSave: undefined,
    groupByColumns: undefined,
    tableName: undefined,
    buttonConfig: undefined,
    buttonContext: undefined,
    saveButtonConfig: undefined,
    menuObjid: undefined,
  });

  const [screenData, setScreenData] = useState<{
    components: ComponentData[];
    screenInfo: any;
  } | null>(null);

  const [loading, setLoading] = useState(false);
  const [screenDimensions, setScreenDimensions] = useState<{
    width: number;
    height: number;
    offsetX?: number;
    offsetY?: number;
  } | null>(null);

  // 폼 데이터 상태 (편집 데이터로 초기화됨)
  const [formData, setFormData] = useState<Record<string, any>>({});
  const [originalData, setOriginalData] = useState<Record<string, any>>({});
  // INSERT/UPDATE 판단용 플래그 (이벤트에서 명시적으로 전달받음)
  // true = INSERT (등록/복사), false = UPDATE (수정)
  // originalData 상태에 의존하지 않고 이벤트의 isCreateMode 값을 직접 사용
  const [isCreateModeFlag, setIsCreateModeFlag] = useState<boolean>(true);

  // 🆕 그룹 데이터 상태 (같은 order_no의 모든 품목)
  const [groupData, setGroupData] = useState<Record<string, any>[]>([]);
  const [originalGroupData, setOriginalGroupData] = useState<Record<string, any>[]>([]);

  // 🆕 조건부 레이어 상태 (Zone 기반)
  const [zones, setZones] = useState<ConditionalZone[]>([]);
  const [conditionalLayers, setConditionalLayers] = useState<LayerDefinition[]>([]);

  // 화면의 실제 크기 계산 함수 (ScreenModal과 동일)
  const calculateScreenDimensions = (components: ComponentData[]) => {
    if (components.length === 0) {
      return {
        width: 400,
        height: 300,
        offsetX: 0,
        offsetY: 0,
      };
    }

    // 모든 컴포넌트의 경계 찾기
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    components.forEach((component) => {
      const x = parseFloat(component.position?.x?.toString() || "0");
      const y = parseFloat(component.position?.y?.toString() || "0");
      const width = parseFloat(component.size?.width?.toString() || "100");
      const height = parseFloat(component.size?.height?.toString() || "40");

      minX = Math.min(minX, x);
      minY = Math.min(minY, y);
      maxX = Math.max(maxX, x + width);
      maxY = Math.max(maxY, y + height);
    });

    // 실제 컨텐츠 크기 계산
    const contentWidth = maxX - minX;
    const contentHeight = maxY - minY;

    // 적절한 여백 추가 (주석처리 - 사용자 설정 크기 그대로 사용)
    // const paddingX = 40;
    // const paddingY = 40;

    const finalWidth = Math.max(contentWidth, 400); // padding 제거
    const finalHeight = Math.max(contentHeight, 300); // padding 제거

    return {
      width: Math.min(finalWidth, window.innerWidth * 0.95),
      height: Math.min(finalHeight, window.innerHeight * 0.9),
      offsetX: Math.max(0, minX), // paddingX 제거
      offsetY: Math.max(0, minY), // paddingY 제거
    };
  };

  // 🆕 모달 내부 저장 버튼의 제어로직 설정 조회
  const loadSaveButtonConfig = async (
    targetScreenId: number,
  ): Promise<{
    enableDataflowControl?: boolean;
    dataflowConfig?: any;
    dataflowTiming?: string;
  } | null> => {
    try {
      // 1. 대상 화면의 레이아웃 조회
      const layoutData = await screenApi.getLayout(targetScreenId);

      if (!layoutData?.components) {
        console.log("[EditModal] 레이아웃 컴포넌트 없음:", targetScreenId);
        return null;
      }

      // 2. 저장 버튼 찾기
      let saveButton = findSaveButtonInComponents(layoutData.components);

      // 3. conditional-container가 있는 경우 내부 화면도 탐색
      if (!saveButton) {
        for (const comp of layoutData.components) {
          if (comp.componentType === "conditional-container" && comp.componentConfig?.sections) {
            for (const section of comp.componentConfig.sections) {
              if (section.screenId) {
                try {
                  const innerLayoutData = await screenApi.getLayout(section.screenId);
                  saveButton = findSaveButtonInComponents(innerLayoutData?.components || []);
                  if (saveButton) {
                    // console.log("[EditModal] 조건부 컨테이너 내부에서 저장 버튼 발견:", {
                    //   sectionScreenId: section.screenId,
                    //   sectionLabel: section.label,
                    // });
                    break;
                  }
                } catch (innerError) {
                  // console.warn("[EditModal] 내부 화면 레이아웃 조회 실패:", section.screenId);
                }
              }
            }
            if (saveButton) break;
          }
        }
      }

      if (!saveButton) {
        // console.log("[EditModal] 저장 버튼을 찾을 수 없음:", targetScreenId);
        return null;
      }

      // 4. webTypeConfig에서 제어로직 설정 추출
      const webTypeConfig = saveButton.webTypeConfig;
      if (webTypeConfig?.enableDataflowControl) {
        const config = {
          enableDataflowControl: webTypeConfig.enableDataflowControl,
          dataflowConfig: webTypeConfig.dataflowConfig,
          dataflowTiming: webTypeConfig.dataflowConfig?.flowConfig?.executionTiming || "after",
        };
        // console.log("[EditModal] 저장 버튼 제어로직 설정 발견:", config);
        return config;
      }

      // console.log("[EditModal] 저장 버튼에 제어로직 설정 없음");
      return null;
    } catch (error) {
      // console.warn("[EditModal] 저장 버튼 설정 조회 실패:", error);
      return null;
    }
  };

  /**
   * 디테일 행의 FK를 통해 마스터 테이블 데이터를 자동 조회
   * - entity join 설정에서 FK 관계 탐지
   * - FK 값으로 마스터 테이블 전체 row 조회
   * - editData에 없는 필드만 병합 (디테일 데이터를 덮어쓰지 않음)
   */
  const loadMasterDataForDetailRow = async (
    editData: Record<string, any>,
    targetScreenId: number,
    eventTableName?: string,
  ): Promise<Record<string, any>> => {
    try {
      let detailTableName = eventTableName;
      if (!detailTableName) {
        const screenInfo = await screenApi.getScreen(targetScreenId);
        detailTableName = screenInfo?.tableName;
      }

      if (!detailTableName) {
        console.log("[EditModal:MasterLoad] 테이블명을 알 수 없음 - 스킵");
        return {};
      }

      console.log("[EditModal:MasterLoad] 시작:", { detailTableName, editDataKeys: Object.keys(editData) });

      const entityJoinRes = await entityJoinApi.getEntityJoinConfigs(detailTableName);
      const joinConfigs = entityJoinRes?.joinConfigs || [];

      if (joinConfigs.length === 0) {
        console.log("[EditModal:MasterLoad] entity join 없음 - 스킵");
        return {};
      }

      console.log(
        "[EditModal:MasterLoad] entity join:",
        joinConfigs.map((c) => `${c.sourceColumn} → ${c.referenceTable}`),
      );

      const masterDataResult: Record<string, any> = {};
      const processedTables = new Set<string>();
      const { apiClient } = await import("@/lib/api/client");

      for (const joinConfig of joinConfigs) {
        const { sourceColumn, referenceTable, referenceColumn } = joinConfig;
        if (processedTables.has(referenceTable)) continue;

        const fkValue = editData[sourceColumn];
        if (!fkValue) continue;

        try {
          const response = await apiClient.post(`/table-management/tables/${referenceTable}/data`, {
            search: { [referenceColumn || "id"]: fkValue },
            size: 1,
            page: 1,
            autoFilter: true,
          });

          const rows = response.data?.data?.data || response.data?.data?.rows || [];
          if (rows.length > 0) {
            const masterRow = rows[0];
            for (const [col, val] of Object.entries(masterRow)) {
              if (val !== undefined && val !== null && editData[col] === undefined) {
                masterDataResult[col] = val;
              }
            }
            console.log("[EditModal:MasterLoad] 조회 성공:", {
              table: referenceTable,
              fk: `${sourceColumn}=${fkValue}`,
              loadedFields: Object.keys(masterDataResult),
            });
          }
        } catch (queryError) {
          console.warn("[EditModal:MasterLoad] 조회 실패:", referenceTable, queryError);
        }

        processedTables.add(referenceTable);
      }

      console.log("[EditModal:MasterLoad] 최종 결과:", Object.keys(masterDataResult));
      return masterDataResult;
    } catch (error) {
      console.warn("[EditModal:MasterLoad] 전체 오류:", error);
      return {};
    }
  };

  // 전역 모달 이벤트 리스너 (활성 탭에서만 처리)
  useEffect(() => {
    const handleOpenEditModal = async (event: CustomEvent) => {
      const storeState = useTabStore.getState();
      const currentActiveTabId = storeState[storeState.mode].activeTabId;
      if (tabId && tabId !== currentActiveTabId) return;

      const {
        screenId,
        title,
        description,
        modalSize,
        editData,
        onSave,
        groupByColumns,
        tableName,
        isCreateMode,
        buttonConfig,
        buttonContext,
        menuObjid,
      } = event.detail;

      // 🆕 모달 내부 저장 버튼의 제어로직 설정 조회
      let saveButtonConfig: EditModalState["saveButtonConfig"] = undefined;
      if (screenId) {
        const config = await loadSaveButtonConfig(screenId);
        if (config) {
          saveButtonConfig = config;
        }
      }

      setModalState({
        isOpen: true,
        screenId,
        title,
        description: description || "",
        modalSize: modalSize || "lg",
        editData: editData || {},
        onSave,
        groupByColumns, // 🆕 그룹핑 컬럼
        tableName, // 🆕 테이블명
        buttonConfig, // 🆕 버튼 설정
        buttonContext, // 🆕 버튼 컨텍스트
        saveButtonConfig, // 🆕 모달 내부 저장 버튼의 제어로직 설정
        menuObjid, // 🆕 메뉴 OBJID (카테고리 스코프용)
      });

      // 편집 데이터로 폼 데이터 초기화
      // entity join 필드(xxx_yyy)를 dot notation(table.column)으로도 매핑
      const enriched = { ...(editData || {}) };
      if (editData) {
        Object.keys(editData).forEach((key) => {
          // item_id_item_name → item_info.item_name 패턴 변환
          const match = key.match(/^(.+?)_([a-z_]+)$/);
          if (match && editData[key] != null) {
            const [, fkCol, fieldName] = match;
            // FK가 _id로 끝나면 참조 테이블명 추론 (item_id → item_info)
            if (fkCol.endsWith("_id")) {
              const refTable = fkCol.replace(/_id$/, "_info");
              const dotKey = `${refTable}.${fieldName}`;
              if (!(dotKey in enriched)) {
                enriched[dotKey] = editData[key];
              }
            }
          }
        });
      }

      // editData로 formData를 즉시 세팅 (채번 컴포넌트가 빈 formData로 마운트되어 새 번호 생성하는 것 방지)
      setFormData(enriched);
      // originalData: changedData 계산(PATCH)에만 사용
      // INSERT/UPDATE 판단에는 사용하지 않음
      setOriginalData(isCreateMode ? {} : editData || {});
      // INSERT/UPDATE 판단: 이벤트의 isCreateMode 플래그를 직접 저장
      // isCreateMode=true(복사/등록) → INSERT, false/undefined(수정) → UPDATE
      setIsCreateModeFlag(!!isCreateMode);

      // 마스터 데이터 자동 조회 (수정 모드일 때, formData 세팅 이후 비동기로 병합)
      // 디테일 행 선택 시 마스터 테이블의 컬럼 데이터를 자동으로 가져와서 추가
      if (!isCreateMode && editData && screenId) {
        loadMasterDataForDetailRow(editData, screenId, tableName)
          .then((masterData) => {
            if (Object.keys(masterData).length > 0) {
              setFormData((prev) => ({ ...prev, ...masterData }));
              console.log("[EditModal] 마스터 데이터 비동기 병합 완료:", Object.keys(masterData));
            }
          })
          .catch((masterError) => {
            console.warn("[EditModal] 마스터 데이터 자동 조회 중 오류 (무시):", masterError);
          });
      }

      console.log("[EditModal] 모달 열림:", {
        mode: isCreateMode ? "INSERT (생성/복사)" : "UPDATE (수정)",
        hasEditData: !!editData,
        editDataId: editData?.id,
        isCreateMode,
      });
    };

    const handleCloseEditModal = () => {
      // 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
      if (modalState.onSave) {
        try {
          modalState.onSave();
        } catch (callbackError) {
          console.error("⚠️ onSave 콜백 에러:", callbackError);
        }
      }

      // 모달 닫기
      handleClose();
    };

    window.addEventListener("openEditModal", handleOpenEditModal as EventListener);
    window.addEventListener("closeEditModal", handleCloseEditModal);

    return () => {
      window.removeEventListener("openEditModal", handleOpenEditModal as EventListener);
      window.removeEventListener("closeEditModal", handleCloseEditModal);
    };
  }, [tabId, modalState.onSave]);

  // 화면 데이터 로딩
  useEffect(() => {
    if (modalState.isOpen && modalState.screenId) {
      loadScreenData(modalState.screenId);

      // 🆕 그룹 데이터 조회 (groupByColumns가 있는 경우)
      if (modalState.groupByColumns && modalState.groupByColumns.length > 0 && modalState.tableName) {
        loadGroupData();
      }
    }
  }, [modalState.isOpen, modalState.screenId, modalState.groupByColumns, modalState.tableName]);

  // 🆕 그룹 데이터 조회 함수
  const loadGroupData = async () => {
    if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) {
      return;
    }

    try {
      // 그룹핑 컬럼 값 추출 (예: order_no = "ORD-20251124-001")
      const groupValues: Record<string, any> = {};
      modalState.groupByColumns.forEach((column) => {
        if (modalState.editData[column]) {
          groupValues[column] = modalState.editData[column];
        }
      });

      if (Object.keys(groupValues).length === 0) {
        return;
      }

      // 같은 그룹의 모든 레코드 조회 (entityJoinApi 사용)
      const { entityJoinApi } = await import("@/lib/api/entityJoin");
      const response = await entityJoinApi.getTableDataWithJoins(modalState.tableName, {
        page: 1,
        size: 1000,
        search: groupValues, // search 파라미터로 전달 (백엔드에서 WHERE 조건으로 처리)
        enableEntityJoin: true,
      });

      // entityJoinApi는 배열 또는 { data: [] } 형식으로 반환
      const dataArray = Array.isArray(response) ? response : response?.data || [];

      if (dataArray.length > 0) {
        setGroupData(dataArray);
        setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy
        toast.info(`${dataArray.length}개의 관련 품목을 불러왔습니다.`);
      } else {
        setGroupData([modalState.editData]); // 기본값: 선택된 행만
        setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
      }
    } catch (error: any) {
      console.error("그룹 데이터 조회 오류:", error);
      toast.error("관련 데이터를 불러오는 중 오류가 발생했습니다.");
      setGroupData([modalState.editData]); // 기본값: 선택된 행만
      setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
    }
  };

  const loadScreenData = async (screenId: number) => {
    try {
      setLoading(true);

      // 화면 정보와 레이아웃 데이터 로딩 (ScreenModal과 동일하게 V2 API 우선)
      const [screenInfo, v2LayoutData] = await Promise.all([
        screenApi.getScreen(screenId),
        screenApi.getLayoutV2(screenId),
      ]);

      // V2 → Legacy 변환 (ScreenModal과 동일한 패턴)
      let layoutData: any = null;
      if (v2LayoutData && isValidV2Layout(v2LayoutData)) {
        layoutData = convertV2ToLegacy(v2LayoutData);
        if (layoutData) {
          layoutData.screenResolution = v2LayoutData.screenResolution || layoutData.screenResolution;
        }
      }

      // V2 없으면 기존 API fallback
      if (!layoutData) {
        console.warn("[EditModal] V2 레이아웃 없음, getLayout fallback 시도:", screenId);
        layoutData = await screenApi.getLayout(screenId);
      }

      // getLayout도 실패하면 기본 레이어(layer_id=1) 직접 로드
      if (!layoutData || !layoutData.components || layoutData.components.length === 0) {
        console.warn("[EditModal] getLayout도 실패, getLayerLayout(1) 최종 fallback:", screenId);
        try {
          const baseLayerData = await screenApi.getLayerLayout(screenId, 1);
          if (baseLayerData && isValidV2Layout(baseLayerData)) {
            layoutData = convertV2ToLegacy(baseLayerData);
            if (layoutData) {
              layoutData.screenResolution = baseLayerData.screenResolution || layoutData.screenResolution;
            }
          } else if (baseLayerData?.components) {
            layoutData = baseLayerData;
          }
        } catch (fallbackErr) {
          console.error("[EditModal] getLayerLayout(1) fallback 실패:", fallbackErr);
        }
      }

      if (screenInfo && layoutData) {
        const components = layoutData.components || [];

        // 화면 관리에서 설정한 해상도 우선 사용 (ScreenModal과 동일)
        const screenResolution = (layoutData as any).screenResolution || (screenInfo as any).screenResolution;

        let dimensions;
        if (screenResolution && screenResolution.width && screenResolution.height) {
          dimensions = {
            width: screenResolution.width,
            height: screenResolution.height,
            offsetX: 0,
            offsetY: 0,
          };
        } else {
          dimensions = calculateScreenDimensions(components);
        }
        setScreenDimensions(dimensions);

        setScreenData({
          components,
          screenInfo: screenInfo,
        });

        // 🆕 조건부 레이어/존 로드 (await으로 에러 포착)
        console.log("[EditModal] 화면 데이터 로드 완료, 조건부 레이어 로드 시작:", screenId);
        try {
          await loadConditionalLayersAndZones(screenId, components);
        } catch (layerErr) {
          console.error("[EditModal] 조건부 레이어 로드 에러:", layerErr);
        }
      } else {
        throw new Error("화면 데이터가 없습니다");
      }
    } catch (error) {
      console.error("화면 데이터 로딩 오류:", error);
      toast.error("화면을 불러오는 중 오류가 발생했습니다.");
      handleClose();
    } finally {
      setLoading(false);
    }
  };

  // 🆕 조건부 레이어 & 존 로드 함수
  const loadConditionalLayersAndZones = async (screenId: number, baseComponents: ComponentData[]) => {
    console.log("[EditModal] loadConditionalLayersAndZones 호출됨:", screenId);
    try {
      // 레이어 목록 & 존 목록 병렬 로드
      console.log("[EditModal] API 호출 시작: getScreenLayers, getScreenZones");
      const [layersRes, zonesRes] = await Promise.all([
        screenApi.getScreenLayers(screenId),
        screenApi.getScreenZones(screenId),
      ]);
      console.log("[EditModal] API 응답:", { layers: layersRes?.length, zones: zonesRes?.length });

      const loadedLayers = layersRes || [];
      const loadedZones: ConditionalZone[] = zonesRes || [];

      setZones(loadedZones);

      // 기본 레이어(layer_id=1) 제외한 조건부 레이어 처리
      const nonBaseLayers = loadedLayers.filter((l: any) => l.layer_id !== 1);

      if (nonBaseLayers.length === 0) {
        setConditionalLayers([]);
        return;
      }

      // 각 조건부 레이어의 컴포넌트 로드
      const layerDefinitions: LayerDefinition[] = [];

      for (const layer of nonBaseLayers) {
        try {
          const layerLayout = await screenApi.getLayerLayout(screenId, layer.layer_id);

          let layerComponents: ComponentData[] = [];
          if (layerLayout && isValidV2Layout(layerLayout)) {
            const legacyLayout = convertV2ToLegacy(layerLayout);
            layerComponents = (legacyLayout?.components || []) as unknown as ComponentData[];
          } else if (layerLayout?.components) {
            layerComponents = layerLayout.components;
          }

          // condition_config에서 zone_id, condition_value 추출
          const conditionConfig = layer.condition_config || {};
          const layerZoneId = conditionConfig.zone_id;
          const layerConditionValue = conditionConfig.condition_value;

          // 이 레이어가 속한 Zone 찾기
          const associatedZone = loadedZones.find((z) => z.zone_id === layerZoneId);

          layerDefinitions.push({
            id: `layer-${layer.layer_id}`,
            name: layer.layer_name || `레이어 ${layer.layer_id}`,
            type: "conditional",
            zIndex: layer.layer_id,
            isVisible: false,
            isLocked: false,
            zoneId: layerZoneId,
            conditionValue: layerConditionValue,
            condition: associatedZone
              ? {
                  targetComponentId: associatedZone.trigger_component_id || "",
                  operator: (associatedZone.trigger_operator || "eq") as any,
                  value: layerConditionValue || "",
                }
              : undefined,
            components: layerComponents,
          } as LayerDefinition & { components: ComponentData[] });
        } catch (layerError) {
          console.warn(`[EditModal] 레이어 ${layer.layer_id} 로드 실패:`, layerError);
        }
      }

      console.log(
        "[EditModal] 조건부 레이어 로드 완료:",
        layerDefinitions.length,
        "개",
        layerDefinitions.map((l) => ({
          id: l.id,
          name: l.name,
          conditionValue: l.conditionValue,
          condition: l.condition,
        })),
      );

      setConditionalLayers(layerDefinitions);
    } catch (error) {
      console.warn("[EditModal] 조건부 레이어 로드 실패:", error);
    }
  };

  // 🆕 조건부 레이어 활성화 평가 (formData 변경 시)
  const activeConditionalLayerIds = useMemo(() => {
    if (conditionalLayers.length === 0) return [];

    const newActiveIds: string[] = [];
    const allComponents = screenData?.components || [];

    conditionalLayers.forEach((layer) => {
      const layerWithComponents = layer as LayerDefinition & { components: ComponentData[] };
      if (layerWithComponents.condition) {
        const { targetComponentId, operator, value } = layerWithComponents.condition;
        if (!targetComponentId) return;

        // 트리거 컴포넌트의 columnName 찾기
        // V2 레이아웃: overrides.columnName, 레거시: componentConfig.columnName
        const targetComponent = allComponents.find((c) => c.id === targetComponentId);
        const fieldKey =
          (targetComponent as any)?.overrides?.columnName ||
          (targetComponent as any)?.columnName ||
          (targetComponent as any)?.componentConfig?.columnName ||
          targetComponentId;

        const currentFormData = groupData.length > 0 ? { ...formData, ...groupData[0] } : formData;
        const targetValue = currentFormData[fieldKey];

        let isMatch = false;
        switch (operator) {
          case "eq":
            isMatch = String(targetValue ?? "") === String(value ?? "");
            break;
          case "neq":
            isMatch = String(targetValue ?? "") !== String(value ?? "");
            break;
          case "in":
            if (Array.isArray(value)) {
              isMatch = value.some((v) => String(v) === String(targetValue ?? ""));
            } else if (typeof value === "string" && value.includes(",")) {
              isMatch = value
                .split(",")
                .map((v) => v.trim())
                .includes(String(targetValue ?? ""));
            }
            break;
        }

        // 디버그 로깅
        console.log("[EditModal] 레이어 조건 평가:", {
          layerId: layer.id,
          layerName: layer.name,
          targetComponentId,
          fieldKey,
          targetValue: targetValue !== undefined ? String(targetValue) : "(없음)",
          conditionValue: String(value),
          operator,
          isMatch,
          componentFound: !!targetComponent,
        });

        if (isMatch) {
          newActiveIds.push(layer.id);
        }
      }
    });

    return newActiveIds;
  }, [formData, groupData, conditionalLayers, screenData?.components]);

  // 활성화된 조건부 레이어의 컴포넌트 가져오기 (Zone 오프셋 적용)
  const activeConditionalComponents = useMemo(() => {
    return conditionalLayers
      .filter((layer) => activeConditionalLayerIds.includes(layer.id))
      .flatMap((layer) => {
        const layerWithComps = layer as LayerDefinition & { components: ComponentData[] };
        const comps = layerWithComps.components || [];

        // Zone 오프셋 적용: 조건부 레이어 컴포넌트는 Zone 내부 상대 좌표로 저장되므로
        // Zone의 절대 좌표를 더해줘야 EditModal에서 올바른 위치에 렌더링됨
        const associatedZone = zones.find((z) => z.zone_id === (layer as any).zoneId);
        if (!associatedZone) return comps;

        const zoneOffsetX = associatedZone.x || 0;
        const zoneOffsetY = associatedZone.y || 0;

        return comps.map((comp) => ({
          ...comp,
          position: {
            ...comp.position,
            x: parseFloat(comp.position?.x?.toString() || "0") + zoneOffsetX,
            y: parseFloat(comp.position?.y?.toString() || "0") + zoneOffsetY,
          },
        }));
      });
  }, [conditionalLayers, activeConditionalLayerIds, zones]);

  const handleClose = () => {
    setModalState({
      isOpen: false,
      screenId: null,
      title: "",
      description: "",
      modalSize: "md",
      editData: {},
      onSave: undefined,
      groupByColumns: undefined,
      tableName: undefined,
    });
    setScreenData(null);
    setFormData({});
    setZones([]);
    setConditionalLayers([]);
    setOriginalData({});
    setIsCreateModeFlag(true); // 기본값은 INSERT (안전 방향)
    setGroupData([]); // 🆕
    setOriginalGroupData([]); // 🆕
  };

  // 저장 버튼 클릭 시 - UPDATE 액션 실행
  const handleSave = async (saveData?: any) => {
    // universal-form-modal 등에서 자체 저장 완료 후 호출된 경우 스킵
    if (saveData?._saveCompleted) {
      console.log("[EditModal] 자체 저장 완료된 컴포넌트에서 호출됨 - 저장 스킵");

      // 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
      if (modalState.onSave) {
        try {
          modalState.onSave();
        } catch (callbackError) {
          console.error("onSave 콜백 에러:", callbackError);
        }
      }

      handleClose();
      return;
    }

    if (!screenData?.screenInfo?.tableName) {
      toast.error("테이블 정보가 없습니다.");
      return;
    }

    try {
      // 🆕 그룹 데이터가 있는 경우: 모든 품목 일괄 처리 (추가/수정/삭제)
      if (groupData.length > 0 || originalGroupData.length > 0) {
        console.log("🔄 그룹 데이터 일괄 처리 시작:", {
          groupDataLength: groupData.length,
          originalGroupDataLength: originalGroupData.length,
          groupData,
          originalGroupData,
          tableName: screenData.screenInfo.tableName,
          screenId: modalState.screenId,
        });

        // 🆕 날짜 필드 정규화 함수 (YYYY-MM-DD 형식으로 변환)
        const normalizeDateField = (value: any): string | null => {
          if (!value) return null;

          // ISO 8601 형식 (2025-11-26T00:00:00.000Z) 또는 Date 객체
          if (value instanceof Date || typeof value === "string") {
            try {
              const date = new Date(value);
              if (isNaN(date.getTime())) return null;

              // YYYY-MM-DD 형식으로 변환
              const year = date.getFullYear();
              const month = String(date.getMonth() + 1).padStart(2, "0");
              const day = String(date.getDate()).padStart(2, "0");
              return `${year}-${month}-${day}`;
            } catch (error) {
              console.warn("날짜 변환 실패:", value, error);
              return null;
            }
          }

          return null;
        };

        // 날짜 필드 목록
        const dateFields = ["item_due_date", "delivery_date", "due_date", "order_date"];

        let insertedCount = 0;
        let updatedCount = 0;
        let deletedCount = 0;

        // 1️⃣ 신규 품목 추가 (id가 없는 항목)
        for (const currentData of groupData) {
          if (!currentData.id) {
            console.log("➕ 신규 품목 추가:", currentData);
            console.log("📋 [신규 품목] currentData 키 목록:", Object.keys(currentData));

            // 🆕 모든 데이터를 포함 (id 제외)
            const insertData: Record<string, any> = { ...currentData };
            console.log("📦 [신규 품목] 복사 직후 insertData:", insertData);
            console.log("📋 [신규 품목] insertData 키 목록:", Object.keys(insertData));

            delete insertData.id; // id는 자동 생성되므로 제거

            // 🆕 날짜 필드 정규화 (YYYY-MM-DD 형식으로 변환)
            dateFields.forEach((fieldName) => {
              if (insertData[fieldName]) {
                const normalizedDate = normalizeDateField(insertData[fieldName]);
                if (normalizedDate) {
                  insertData[fieldName] = normalizedDate;
                  console.log(`📅 [날짜 정규화] ${fieldName}: ${currentData[fieldName]} → ${normalizedDate}`);
                }
              }
            });

            // 🆕 groupByColumns의 값을 강제로 포함 (order_no 등)
            if (modalState.groupByColumns && modalState.groupByColumns.length > 0) {
              modalState.groupByColumns.forEach((colName) => {
                // 기존 품목(originalGroupData[0])에서 groupByColumns 값 가져오기
                const referenceData = originalGroupData[0] || groupData.find((item) => item.id);
                if (referenceData && referenceData[colName]) {
                  insertData[colName] = referenceData[colName];
                  console.log(`🔑 [신규 품목] ${colName} 값 추가:`, referenceData[colName]);
                }
              });
            }

            // 🆕 공통 필드 추가 (거래처, 담당자, 납품처, 메모 등)
            // formData에서 품목별 필드가 아닌 공통 필드를 복사
            const commonFields = [
              "partner_id", // 거래처
              "manager_id", // 담당자
              "delivery_partner_id", // 납품처
              "delivery_address", // 납품장소
              "memo", // 메모
              "order_date", // 주문일
              "due_date", // 납기일
              "shipping_method", // 배송방법
              "status", // 상태
              "sales_type", // 영업유형
            ];

            commonFields.forEach((fieldName) => {
              // formData에 값이 있으면 추가
              if (formData[fieldName] !== undefined && formData[fieldName] !== null) {
                // 날짜 필드인 경우 정규화
                if (dateFields.includes(fieldName)) {
                  const normalizedDate = normalizeDateField(formData[fieldName]);
                  if (normalizedDate) {
                    insertData[fieldName] = normalizedDate;
                    console.log(`🔗 [공통 필드 - 날짜] ${fieldName} 값 추가:`, normalizedDate);
                  }
                } else {
                  insertData[fieldName] = formData[fieldName];
                  console.log(`🔗 [공통 필드] ${fieldName} 값 추가:`, formData[fieldName]);
                }
              }
            });

            console.log("📦 [신규 품목] 최종 insertData:", insertData);
            console.log("📋 [신규 품목] 최종 insertData 키 목록:", Object.keys(insertData));

            try {
              const response = await dynamicFormApi.saveFormData({
                screenId: modalState.screenId || 0,
                tableName: screenData.screenInfo.tableName,
                data: insertData,
              });

              if (response.success) {
                insertedCount++;
                console.log("✅ 신규 품목 추가 성공:", response.data);
              } else {
                console.error("❌ 신규 품목 추가 실패:", response.message);
              }
            } catch (error: any) {
              console.error("❌ 신규 품목 추가 오류:", error);
            }
          }
        }

        // 2️⃣ 기존 품목 수정 (id가 있는 항목)
        for (const currentData of groupData) {
          if (currentData.id) {
            // id 기반 매칭 (인덱스 기반 X)
            const originalItemData = originalGroupData.find((orig) => orig.id === currentData.id);

            if (!originalItemData) {
              console.warn(`원본 데이터를 찾을 수 없습니다 (id: ${currentData.id})`);
              continue;
            }

            // 🆕 값 정규화 함수 (타입 통일)
            const normalizeValue = (val: any, fieldName?: string): any => {
              if (val === null || val === undefined || val === "") return null;

              // 날짜 필드인 경우 YYYY-MM-DD 형식으로 정규화
              if (fieldName && dateFields.includes(fieldName)) {
                const normalizedDate = normalizeDateField(val);
                return normalizedDate;
              }

              if (typeof val === "string" && !isNaN(Number(val))) {
                // 숫자로 변환 가능한 문자열은 숫자로
                return Number(val);
              }
              return val;
            };

            // 변경된 필드만 추출 (id 제외)
            const changedData: Record<string, any> = {};
            Object.keys(currentData).forEach((key) => {
              // id는 변경 불가
              if (key === "id") {
                return;
              }

              // 🆕 타입 정규화 후 비교
              const currentValue = normalizeValue(currentData[key], key);
              const originalValue = normalizeValue(originalItemData[key], key);

              // 값이 변경된 경우만 포함
              if (currentValue !== originalValue) {
                console.log(`🔍 [품목 수정 감지] ${key}: ${originalValue} → ${currentValue}`);
                // 날짜 필드는 정규화된 값 사용, 나머지는 원본 값 사용
                let finalValue = dateFields.includes(key) ? currentValue : currentData[key];

                // 🔧 배열이면 쉼표 구분 문자열로 변환 (리피터 데이터 제외)
                if (Array.isArray(finalValue)) {
                  const isRepeaterData =
                    finalValue.length > 0 &&
                    typeof finalValue[0] === "object" &&
                    finalValue[0] !== null &&
                    ("_targetTable" in finalValue[0] ||
                      "_isNewItem" in finalValue[0] ||
                      "_existingRecord" in finalValue[0]);

                  if (!isRepeaterData) {
                    // 🔧 손상된 값 필터링 헬퍼
                    const isValidValue = (v: any): boolean => {
                      if (typeof v === "number" && !isNaN(v)) return true;
                      if (typeof v !== "string") return false;
                      if (!v || v.trim() === "") return false;
                      if (v.includes("{") || v.includes("}") || v.includes('"') || v.includes("\\")) return false;
                      return true;
                    };

                    const validValues = finalValue
                      .map((v: any) => (typeof v === "number" ? String(v) : v))
                      .filter(isValidValue);

                    const stringValue = validValues.join(",");
                    console.log(`🔧 [EditModal 그룹UPDATE] 배열→문자열 변환: ${key}`, {
                      original: finalValue.length,
                      valid: validValues.length,
                      converted: stringValue,
                    });
                    finalValue = stringValue;
                  }
                }

                changedData[key] = finalValue;
              }
            });

            // 변경사항이 없으면 스킵
            if (Object.keys(changedData).length === 0) {
              console.log(`변경사항 없음 (id: ${currentData.id})`);
              continue;
            }

            // UPDATE 실행
            try {
              const response = await dynamicFormApi.updateFormDataPartial(
                currentData.id,
                originalItemData,
                changedData,
                screenData.screenInfo.tableName,
              );

              if (response.success) {
                updatedCount++;
                console.log(`✅ 품목 수정 성공 (id: ${currentData.id})`);
              } else {
                console.error(`❌ 품목 수정 실패 (id: ${currentData.id}):`, response.message);
              }
            } catch (error: any) {
              console.error(`❌ 품목 수정 오류 (id: ${currentData.id}):`, error);
            }
          }
        }

        // 3️⃣ 삭제된 품목 제거 (원본에는 있지만 현재 데이터에는 없는 항목)
        const currentIds = new Set(groupData.map((item) => item.id).filter(Boolean));
        const deletedItems = originalGroupData.filter((orig) => orig.id && !currentIds.has(orig.id));

        for (const deletedItem of deletedItems) {
          console.log("🗑️ 품목 삭제:", deletedItem);

          try {
            // screenId 전달하여 제어관리 실행 가능하도록 함
            const response = await dynamicFormApi.deleteFormDataFromTable(
              deletedItem.id,
              screenData.screenInfo.tableName,
              modalState.screenId || screenData.screenInfo?.id,
            );

            if (response.success) {
              deletedCount++;
              console.log(`✅ 품목 삭제 성공 (id: ${deletedItem.id})`);
            } else {
              console.error(`❌ 품목 삭제 실패 (id: ${deletedItem.id}):`, response.message);
            }
          } catch (error: any) {
            console.error(`❌ 품목 삭제 오류 (id: ${deletedItem.id}):`, error);
          }
        }

        // V2Repeater 저장 이벤트 발생 (디테일 테이블 데이터 저장)
        const hasRepeaterInstances = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
        if (hasRepeaterInstances) {
          const masterRecordId = groupData[0]?.id || formData.id;
          window.dispatchEvent(
            new CustomEvent("repeaterSave", {
              detail: {
                parentId: masterRecordId,
                masterRecordId,
                mainFormData: formData,
                tableName: screenData.screenInfo.tableName,
              },
            }),
          );
          console.log("📋 [EditModal] 그룹 저장 후 repeaterSave 이벤트 발생:", { masterRecordId });
        }

        // 결과 메시지
        const messages: string[] = [];
        if (insertedCount > 0) messages.push(`${insertedCount}개 추가`);
        if (updatedCount > 0) messages.push(`${updatedCount}개 수정`);
        if (deletedCount > 0) messages.push(`${deletedCount}개 삭제`);

        if (messages.length > 0 || hasRepeaterInstances) {
          toast.success(messages.length > 0 ? `품목이 저장되었습니다 (${messages.join(", ")})` : "저장되었습니다.");

          // 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
          if (modalState.onSave) {
            try {
              modalState.onSave();
            } catch (callbackError) {
              console.error("⚠️ onSave 콜백 에러:", callbackError);
            }
          }

          // 🆕 저장 후 제어로직 실행 (버튼의 After 타이밍 제어)
          // 우선순위: 모달 내부 저장 버튼 설정(saveButtonConfig) > 수정 버튼에서 전달받은 설정(buttonConfig)
          try {
            const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig;

            console.log("[EditModal] 그룹 저장 완료 후 제어로직 실행 시도", {
              hasSaveButtonConfig: !!modalState.saveButtonConfig,
              hasButtonConfig: !!modalState.buttonConfig,
              controlConfig,
            });

            // 🔧 executionTiming 체크: dataflowTiming 또는 flowConfig.executionTiming 또는 flowControls 확인
            const flowTiming =
              controlConfig?.dataflowTiming ||
              controlConfig?.dataflowConfig?.flowConfig?.executionTiming ||
              (controlConfig?.dataflowConfig?.flowControls?.length > 0 ? "after" : null);

            if (controlConfig?.enableDataflowControl && flowTiming === "after") {
              console.log("🎯 [EditModal] 저장 후 제어로직 발견:", controlConfig.dataflowConfig);

              // buttonActions의 executeAfterSaveControl 동적 import
              const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions");

              // 제어로직 실행
              await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
                formData: modalState.editData,
                screenId: modalState.buttonContext?.screenId || modalState.screenId,
                tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName,
                userId: user?.userId,
                companyCode: user?.companyCode,
                onRefresh: modalState.onSave,
              });

              console.log("✅ [EditModal] 제어로직 실행 완료");
            } else {
              console.log("ℹ️ [EditModal] 저장 후 실행할 제어로직 없음");
            }
          } catch (controlError) {
            console.error("❌ [EditModal] 제어로직 실행 오류:", controlError);
            // 제어로직 오류는 저장 성공을 방해하지 않음 (경고만 표시)
            toast.warning("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
          }

          handleClose();
        } else {
          toast.info("변경된 내용이 없습니다.");
          handleClose();
        }

        return;
      }

      // ========================================
      // INSERT/UPDATE 판단 (재설계)
      // ========================================
      // 판단 기준:
      //   1. isCreateModeFlag === true → 무조건 INSERT (복사/등록 모드 보호)
      //   2. isCreateModeFlag === false → formData.id 있으면 UPDATE, 없으면 INSERT
      // originalData는 INSERT/UPDATE 판단에 사용하지 않음 (changedData 계산에만 사용)
      // ========================================
      let isCreateMode: boolean;

      if (isCreateModeFlag) {
        // 이벤트에서 명시적으로 INSERT 모드로 지정됨 (등록/복사)
        isCreateMode = true;
      } else {
        // 수정 모드: formData에 id가 있으면 UPDATE, 없으면 INSERT
        isCreateMode = !formData.id;
      }

      console.log("[EditModal] 저장 모드 판단:", {
        isCreateMode,
        isCreateModeFlag,
        formDataId: formData.id,
        originalDataLength: Object.keys(originalData).length,
        tableName: screenData.screenInfo.tableName,
      });

      if (isCreateMode) {
        // INSERT 모드
        console.log("[EditModal] INSERT 모드 - 새 데이터 생성:", formData);

        // 🆕 채번 규칙 할당 처리 (저장 시점에 실제 순번 증가)
        const dataToSave = { ...formData };
        const fieldsWithNumbering: Record<string, string> = {};

        // formData에서 채번 규칙이 설정된 필드 찾기
        for (const [key, value] of Object.entries(formData)) {
          if (key.endsWith("_numberingRuleId") && value) {
            const fieldName = key.replace("_numberingRuleId", "");
            fieldsWithNumbering[fieldName] = value as string;
            console.log(`🎯 [EditModal] 채번 규칙 발견: ${fieldName} → 규칙 ${value}`);
          }
        }

        // 채번 규칙이 있는 필드에 대해 allocateCode 호출 (🚀 병렬 처리로 최적화)
        if (Object.keys(fieldsWithNumbering).length > 0) {
          console.log("🎯 [EditModal] 채번 규칙 할당 시작, formData:", {
            material: formData.material,
            allKeys: Object.keys(formData),
          });
          const { allocateNumberingCode } = await import("@/lib/api/numberingRule");

          // 🚀 Promise.all로 병렬 처리 (여러 채번 필드가 있을 경우 성능 향상)
          const allocationPromises = Object.entries(fieldsWithNumbering).map(async ([fieldName, ruleId]) => {
            const userInputCode = dataToSave[fieldName] as string;
            console.log(`🔄 [EditModal] ${fieldName} 필드에 대해 allocateCode 호출: ${ruleId}`);

            try {
              const allocateResult = await allocateNumberingCode(ruleId, userInputCode, formData);

              if (allocateResult.success && allocateResult.data?.generatedCode) {
                return { fieldName, success: true, code: allocateResult.data.generatedCode };
              } else {
                console.warn(`⚠️ [EditModal] ${fieldName} 코드 할당 실패:`, allocateResult.error);
                return { fieldName, success: false, hasExistingValue: !!dataToSave[fieldName] };
              }
            } catch (allocateError) {
              console.error(`❌ [EditModal] ${fieldName} 코드 할당 오류:`, allocateError);
              return { fieldName, success: false, hasExistingValue: !!dataToSave[fieldName] };
            }
          });

          const allocationResults = await Promise.all(allocationPromises);

          // 결과 처리
          const failedFields: string[] = [];
          for (const result of allocationResults) {
            if (result.success && result.code) {
              console.log(`✅ [EditModal] ${result.fieldName} 새 코드 할당: ${result.code}`);
              dataToSave[result.fieldName] = result.code;
            } else if (!result.hasExistingValue) {
              failedFields.push(result.fieldName);
            }
          }

          // 채번 규칙 할당 실패 시 저장 중단
          if (failedFields.length > 0) {
            const fieldNames = failedFields.join(", ");
            toast.error(`채번 규칙 할당에 실패했습니다 (${fieldNames}). 화면 설정에서 채번 규칙을 확인해주세요.`);
            console.error(`❌ [EditModal] 채번 규칙 할당 실패로 저장 중단. 실패 필드: ${fieldNames}`);
            return;
          }

          // _numberingRuleId 필드 제거 (실제 저장하지 않음)
          for (const key of Object.keys(dataToSave)) {
            if (key.endsWith("_numberingRuleId")) {
              delete dataToSave[key];
            }
          }
        }

        // 🆕 리피터 데이터(배열)를 마스터 저장에서 제외 (V2Repeater가 별도로 저장)
        // 🔧 단, 다중 선택 배열은 쉼표 구분 문자열로 변환하여 저장
        const masterDataToSave: Record<string, any> = {};
        Object.entries(dataToSave).forEach(([key, value]) => {
          if (!Array.isArray(value)) {
            masterDataToSave[key] = value;
          } else {
            // 리피터 데이터인지 확인 (객체 배열이고 _targetTable 또는 _isNewItem이 있으면 리피터)
            const isRepeaterData =
              value.length > 0 &&
              typeof value[0] === "object" &&
              value[0] !== null &&
              ("_targetTable" in value[0] || "_isNewItem" in value[0] || "_existingRecord" in value[0]);

            if (isRepeaterData) {
              console.log(`🔄 [EditModal] 리피터 데이터 제외 (별도 저장): ${key}, ${value.length}개 항목`);
            } else {
              // 🔧 손상된 값 필터링 헬퍼 (중괄호, 따옴표, 백슬래시 포함 시 무효)
              const isValidValue = (v: any): boolean => {
                if (typeof v === "number" && !isNaN(v)) return true;
                if (typeof v !== "string") return false;
                if (!v || v.trim() === "") return false;
                if (v.includes("{") || v.includes("}") || v.includes('"') || v.includes("\\")) return false;
                return true;
              };

              // 🔧 다중 선택 배열 → 쉼표 구분 문자열로 변환 (손상된 값 필터링)
              const validValues = value.map((v: any) => (typeof v === "number" ? String(v) : v)).filter(isValidValue);

              const stringValue = validValues.join(",");
              console.log(`🔧 [EditModal CREATE] 배열→문자열 변환: ${key}`, {
                original: value.length,
                valid: validValues.length,
                converted: stringValue,
              });
              masterDataToSave[key] = stringValue;
            }
          }
        });

        console.log("[EditModal] 최종 저장 데이터:", masterDataToSave);

        const response = await dynamicFormApi.saveFormData({
          screenId: modalState.screenId!,
          tableName: screenData.screenInfo.tableName,
          data: masterDataToSave,
        });

        if (response.success) {
          const masterRecordId = response.data?.id || formData.id;

          toast.success("데이터가 생성되었습니다.");

          // 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
          if (modalState.onSave) {
            try {
              modalState.onSave();
            } catch (callbackError) {
              console.error("onSave 콜백 에러:", callbackError);
            }
          }

          // 🆕 저장 후 제어로직 실행 (버튼의 After 타이밍 제어)
          // 우선순위: 모달 내부 저장 버튼 설정(saveButtonConfig) > 수정 버튼에서 전달받은 설정(buttonConfig)
          try {
            const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig;

            console.log("[EditModal] INSERT 완료 후 제어로직 실행 시도", { controlConfig });

            // 🔧 executionTiming 체크: dataflowTiming 또는 flowConfig.executionTiming 또는 flowControls 확인
            const flowTimingInsert =
              controlConfig?.dataflowTiming ||
              controlConfig?.dataflowConfig?.flowConfig?.executionTiming ||
              (controlConfig?.dataflowConfig?.flowControls?.length > 0 ? "after" : null);

            if (controlConfig?.enableDataflowControl && flowTimingInsert === "after") {
              console.log("🎯 [EditModal] 저장 후 제어로직 발견:", controlConfig.dataflowConfig);

              const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions");

              await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
                formData,
                screenId: modalState.buttonContext?.screenId || modalState.screenId,
                tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName,
                userId: user?.userId,
                companyCode: user?.companyCode,
                onRefresh: modalState.onSave,
              });

              console.log("✅ [EditModal] 제어로직 실행 완료");
            }
          } catch (controlError) {
            console.error("❌ [EditModal] 제어로직 실행 오류:", controlError);
            toast.warning("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
          }

          // V2Repeater 디테일 데이터 저장 (등록된 인스턴스가 있을 때만)
          const hasRepeaterForInsert = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
          if (hasRepeaterForInsert) {
            try {
              const repeaterSavePromise = new Promise<void>((resolve) => {
                const fallbackTimeout = setTimeout(resolve, 5000);
                const handler = () => {
                  clearTimeout(fallbackTimeout);
                  window.removeEventListener("repeaterSaveComplete", handler);
                  resolve();
                };
                window.addEventListener("repeaterSaveComplete", handler);
              });

              window.dispatchEvent(
                new CustomEvent("repeaterSave", {
                  detail: {
                    parentId: masterRecordId,
                    tableName: screenData.screenInfo.tableName,
                    mainFormData: formData,
                    masterRecordId,
                  },
                }),
              );

              await repeaterSavePromise;
            } catch (repeaterError) {
              console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
            }
          }

          handleClose();
        } else {
          throw new Error(response.message || "생성에 실패했습니다.");
        }
      } else {
        // UPDATE 모드 - PUT (전체 업데이트)
        // VIEW에서 온 데이터의 경우 master_id를 우선 사용 (마스터-디테일 구조)
        const recordId = formData.master_id || formData.id;

        if (!recordId) {
          console.error("[EditModal] UPDATE 실패: formData에 id가 없습니다.", {
            formDataKeys: Object.keys(formData),
          });
          toast.error("수정할 레코드의 ID를 찾을 수 없습니다.");
          return;
        }

        // 배열 값 → 쉼표 구분 문자열 변환 (리피터 데이터 제외)
        const dataToSave: Record<string, any> = {};
        Object.entries(formData).forEach(([key, value]) => {
          if (Array.isArray(value)) {
            const isRepeaterData =
              value.length > 0 &&
              typeof value[0] === "object" &&
              value[0] !== null &&
              ("_targetTable" in value[0] || "_isNewItem" in value[0] || "_existingRecord" in value[0]);

            if (isRepeaterData) {
              // 리피터 데이터는 제외 (별도 저장)
              return;
            }
            // 다중 선택 배열 → 쉼표 구분 문자열
            const validValues = value
              .map((v: any) => (typeof v === "number" ? String(v) : v))
              .filter((v: any) => {
                if (typeof v === "number") return true;
                if (typeof v !== "string") return false;
                if (!v || v.trim() === "") return false;
                if (v.includes("{") || v.includes("}") || v.includes('"') || v.includes("\\")) return false;
                return true;
              });
            dataToSave[key] = validValues.join(",");
          } else {
            dataToSave[key] = value;
          }
        });

        console.log("[EditModal] UPDATE(PUT) 실행:", {
          recordId,
          fieldCount: Object.keys(dataToSave).length,
          tableName: screenData.screenInfo.tableName,
        });

        const response = await dynamicFormApi.updateFormData(recordId, {
          tableName: screenData.screenInfo.tableName,
          data: dataToSave,
        });

        if (response.success) {
          toast.success("데이터가 수정되었습니다.");

          // 🆕 저장 후 제어로직 실행 (버튼의 After 타이밍 제어)
          // 우선순위: 모달 내부 저장 버튼 설정(saveButtonConfig) > 수정 버튼에서 전달받은 설정(buttonConfig)
          try {
            const controlConfig = modalState.saveButtonConfig || modalState.buttonConfig;

            console.log("[EditModal] UPDATE 완료 후 제어로직 실행 시도", { controlConfig });

            // 🔧 executionTiming 체크: dataflowTiming 또는 flowConfig.executionTiming 또는 flowControls 확인
            const flowTimingUpdate =
              controlConfig?.dataflowTiming ||
              controlConfig?.dataflowConfig?.flowConfig?.executionTiming ||
              (controlConfig?.dataflowConfig?.flowControls?.length > 0 ? "after" : null);

            if (controlConfig?.enableDataflowControl && flowTimingUpdate === "after") {
              console.log("🎯 [EditModal] 저장 후 제어로직 발견:", controlConfig.dataflowConfig);

              const { ButtonActionExecutor } = await import("@/lib/utils/buttonActions");

              await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
                formData,
                screenId: modalState.buttonContext?.screenId || modalState.screenId,
                tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.tableName,
                userId: user?.userId,
                companyCode: user?.companyCode,
                onRefresh: modalState.onSave,
              });

              console.log("✅ [EditModal] 제어로직 실행 완료");
            }
          } catch (controlError) {
            console.error("❌ [EditModal] 제어로직 실행 오류:", controlError);
            toast.warning("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
          }

          // V2Repeater 디테일 데이터 저장 (등록된 인스턴스가 있을 때만)
          const hasRepeaterForUpdate = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
          if (hasRepeaterForUpdate) {
            try {
              const repeaterSavePromise = new Promise<void>((resolve) => {
                const fallbackTimeout = setTimeout(resolve, 5000);
                const handler = () => {
                  clearTimeout(fallbackTimeout);
                  window.removeEventListener("repeaterSaveComplete", handler);
                  resolve();
                };
                window.addEventListener("repeaterSaveComplete", handler);
              });

              window.dispatchEvent(
                new CustomEvent("repeaterSave", {
                  detail: {
                    parentId: recordId,
                    tableName: screenData.screenInfo.tableName,
                    mainFormData: formData,
                    masterRecordId: recordId,
                  },
                }),
              );

              await repeaterSavePromise;
            } catch (repeaterError) {
              console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
            }
          }

          // 리피터 저장 완료 후 메인 테이블 새로고침
          if (modalState.onSave) {
            try {
              modalState.onSave();
            } catch {}
          }
          handleClose();
        } else {
          throw new Error(response.message || "수정에 실패했습니다.");
        }
      }
    } catch (error: any) {
      console.error("❌ 수정 실패:", error);
      toast.error(error.message || "데이터 수정 중 오류가 발생했습니다.");
    }
  };

  // 모달 크기 설정 - ScreenModal과 동일한 방식 (maxHeight로 유연 처리)
  const getModalStyle = () => {
    if (!screenDimensions) {
      return {
        className: "w-fit min-w-[400px] max-w-4xl overflow-hidden",
        style: { padding: 0, gap: 0, maxHeight: "calc(100dvh - 8px)" },
      };
    }

    const finalWidth = Math.min(screenDimensions.width, window.innerWidth * 0.98);

    return {
      className: "overflow-hidden p-0",
      style: {
        width: `${finalWidth}px`,
        maxHeight: "calc(100dvh - 8px)",
        maxWidth: "98vw",
        padding: 0,
        gap: 0,
      },
    };
  };

  const modalStyle = getModalStyle();

  return (
    <Dialog open={modalState.isOpen} onOpenChange={handleClose}>
      <DialogContent className={`${modalStyle.className} ${className || ""} max-w-none`} style={modalStyle.style}>
        <DialogHeader className="shrink-0 border-b px-4 py-3">
          <div className="flex items-center gap-2">
            <DialogTitle className="text-base">{modalState.title || "데이터 수정"}</DialogTitle>
            {modalState.description && !loading && (
              <DialogDescription className="text-muted-foreground text-xs">{modalState.description}</DialogDescription>
            )}
            {loading && (
              <DialogDescription className="text-xs">{loading ? "화면을 불러오는 중입니다..." : ""}</DialogDescription>
            )}
          </div>
        </DialogHeader>

        <div className="flex-1 min-h-0 flex items-start justify-center overflow-auto">
          {loading ? (
            <div className="flex h-full items-center justify-center">
              <div className="text-center">
                <div className="border-primary mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2"></div>
                <p className="text-muted-foreground">화면을 불러오는 중...</p>
              </div>
            </div>
          ) : screenData ? (
            <ScreenContextProvider
              screenId={modalState.screenId || undefined}
              tableName={screenData.screenInfo?.tableName}
            >
              <div
                data-screen-runtime="true"
                className="relative bg-white"
                style={{
                  width: `${screenDimensions?.width || 800}px`,
                  minHeight: `${screenDimensions?.height || 600}px`,
                  height: (() => {
                    const baseHeight = screenDimensions?.height || 600;
                    if (activeConditionalComponents.length > 0) {
                      const offsetY = screenDimensions?.offsetY || 0;
                      let maxBottom = 0;
                      activeConditionalComponents.forEach((comp) => {
                        const y = parseFloat(comp.position?.y?.toString() || "0") - offsetY;
                        const h = parseFloat(comp.size?.height?.toString() || "40");
                        maxBottom = Math.max(maxBottom, y + h);
                      });
                      return `${Math.max(baseHeight, maxBottom + 20)}px`;
                    }
                    return `${baseHeight}px`;
                  })(),
                  overflow: "visible",
                }}
              >
                {/* 기본 레이어 컴포넌트 렌더링 */}
                {screenData.components.map((component) => {
                  const offsetX = screenDimensions?.offsetX || 0;
                  const offsetY = screenDimensions?.offsetY || 0;
                  // screenResolution이 있으면 offsetY=0이므로 디자이너 좌표 그대로 사용
                  // offsetY > 0 (자동 계산)일 때만 라벨 공간 보정
                  const labelSpace = offsetY > 0 ? 30 : 0;

                  const adjustedComponent = {
                    ...component,
                    position: {
                      ...component.position,
                      x: parseFloat(component.position?.x?.toString() || "0") - offsetX,
                      y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace,
                    },
                  };

                  const groupedDataProp = groupData.length > 0 ? groupData : undefined;

                  const hasUniversalFormModal = screenData.components.some((c) => {
                    if (c.componentType === "universal-form-modal") return true;
                    return false;
                  });

                  const hasTableSectionData = Object.keys(formData).some(
                    (k) => k.startsWith("_tableSection_") || k.startsWith("__tableSection_"),
                  );

                  const shouldUseEditModalSave =
                    !hasTableSectionData && (groupData.length > 0 || !hasUniversalFormModal);

                  const enrichedFormData = {
                    // 마스터 데이터(formData)를 기본으로 깔고, groupData[0]으로 덮어쓰기
                    // → 디테일 행 수정 시에도 마스터 폼 필드가 표시됨
                    ...formData,
                    ...(groupData.length > 0 ? groupData[0] : {}),
                    tableName: screenData.screenInfo?.tableName,
                    screenId: modalState.screenId,
                  };

                  return (
                    <InteractiveScreenViewerDynamic
                      key={component.id}
                      component={adjustedComponent}
                      allComponents={[...screenData.components, ...activeConditionalComponents]}
                      formData={enrichedFormData}
                      originalData={originalData}
                      onFormDataChange={(fieldName, value) => {
                        if (groupData.length > 0) {
                          if (Array.isArray(value)) {
                            setGroupData(value);
                          } else {
                            setGroupData((prev) =>
                              prev.map((item) => ({
                                ...item,
                                [fieldName]: value,
                              })),
                            );
                          }
                        } else {
                          setFormData((prev) => ({
                            ...prev,
                            [fieldName]: value,
                          }));
                        }
                      }}
                      screenInfo={{
                        id: modalState.screenId!,
                        tableName: screenData.screenInfo?.tableName,
                      }}
                      menuObjid={modalState.menuObjid}
                      onSave={shouldUseEditModalSave ? handleSave : undefined}
                      isInModal={true}
                      groupedData={groupedDataProp}
                      disabledFields={["order_no", "partner_id"]}
                    />
                  );
                })}

                {/* 조건부 레이어 컴포넌트 렌더링 */}
                {activeConditionalComponents.map((component) => {
                  const offsetX = screenDimensions?.offsetX || 0;
                  const offsetY = screenDimensions?.offsetY || 0;
                  const labelSpace = offsetY > 0 ? 30 : 0;

                  const adjustedComponent = {
                    ...component,
                    position: {
                      ...component.position,
                      x: parseFloat(component.position?.x?.toString() || "0") - offsetX,
                      y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace,
                    },
                  };

                  const enrichedFormData = {
                    ...formData,
                    ...(groupData.length > 0 ? groupData[0] : {}),
                    tableName: screenData.screenInfo?.tableName,
                    screenId: modalState.screenId,
                  };

                  const groupedDataProp = groupData.length > 0 ? groupData : undefined;

                  return (
                    <InteractiveScreenViewerDynamic
                      key={`conditional-${component.id}`}
                      component={adjustedComponent}
                      allComponents={[...screenData.components, ...activeConditionalComponents]}
                      formData={enrichedFormData}
                      originalData={originalData}
                      onFormDataChange={(fieldName, value) => {
                        if (groupData.length > 0) {
                          if (Array.isArray(value)) {
                            setGroupData(value);
                          } else {
                            setGroupData((prev) =>
                              prev.map((item) => ({
                                ...item,
                                [fieldName]: value,
                              })),
                            );
                          }
                        } else {
                          setFormData((prev) => ({
                            ...prev,
                            [fieldName]: value,
                          }));
                        }
                      }}
                      screenInfo={{
                        id: modalState.screenId!,
                        tableName: screenData.screenInfo?.tableName,
                      }}
                      menuObjid={modalState.menuObjid}
                      isInModal={true}
                      groupedData={groupedDataProp}
                    />
                  );
                })}
              </div>
            </ScreenContextProvider>
          ) : (
            <div className="flex h-full items-center justify-center">
              <p className="text-muted-foreground">화면 데이터가 없습니다.</p>
            </div>
          )}
        </div>
      </DialogContent>
    </Dialog>
  );
};

export default EditModal;
