"use client";

import React, { useState, useCallback, useEffect, useMemo, useRef } from "react";
import { ComponentRendererProps } from "../../types";
import { SplitPanelLayoutConfig, MAX_LOAD_ALL_SIZE } from "./types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
  Plus,
  Search,
  Loader2,
  ChevronDown,
  ChevronUp,
  Save,
  ChevronRight,
  ChevronLeft,
  ChevronsLeft,
  ChevronsRight,
  Pencil,
  Trash2,
  Settings,
  Move,
  FileSpreadsheet,
  List,
  PanelRight,
  GripVertical,
} from "lucide-react";
import { dataApi } from "@/lib/api/data";
import { entityJoinApi } from "@/lib/api/entityJoin";
import { formatNumber as centralFormatNumber } from "@/lib/formatting";
import { useToast } from "@/hooks/use-toast";
import { tableTypeApi } from "@/lib/api/screen";
import { apiClient, getFullImageUrl } from "@/lib/api/client";
import { getFilePreviewUrl } from "@/lib/api/file";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
  DialogDescription,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { useTableOptions } from "@/contexts/TableOptionsContext";
import { TableFilter, ColumnVisibility, GroupSumConfig } from "@/types/table-options";
import { useAuth } from "@/hooks/useAuth";
import { useSplitPanel } from "./SplitPanelContext";
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
import { PanelInlineComponent } from "./types";
import { cn } from "@/lib/utils";
import { ResponsiveGridRenderer } from "@/components/screen/ResponsiveGridRenderer";
import { BomExcelUploadModal } from "../v2-bom-tree/BomExcelUploadModal";

/** 클라이언트 사이드 데이터 필터 (페이징 OFF 전용) */
function applyClientSideFilter(data: any[], dataFilter: any): any[] {
  if (!dataFilter?.enabled) return data;

  let result = data;

  if (dataFilter.filters?.length > 0) {
    const matchFn = dataFilter.matchType === "any" ? "some" : "every";
    result = result.filter((item: any) =>
      dataFilter.filters[matchFn]((cond: any) => {
        const val = item[cond.columnName];
        switch (cond.operator) {
          case "equals":
            return val === cond.value;
          case "notEquals":
          case "not_equals":
            return val !== cond.value;
          case "in": {
            const arr = Array.isArray(cond.value) ? cond.value : [cond.value];
            return arr.includes(val);
          }
          case "not_in": {
            const arr = Array.isArray(cond.value) ? cond.value : [cond.value];
            return !arr.includes(val);
          }
          case "contains":
            return String(val || "").includes(String(cond.value));
          case "is_null":
            return val === null || val === undefined || val === "";
          case "is_not_null":
            return val !== null && val !== undefined && val !== "";
          default:
            return true;
        }
      }),
    );
  }

  // legacy conditions 형식 (하위 호환성)
  if (dataFilter.conditions?.length > 0) {
    result = result.filter((item: any) =>
      dataFilter.conditions.every((cond: any) => {
        const val = item[cond.column];
        switch (cond.operator) {
          case "equals":
            return val === cond.value;
          case "notEquals":
            return val !== cond.value;
          case "contains":
            return String(val || "").includes(String(cond.value));
          default:
            return true;
        }
      }),
    );
  }

  return result;
}

export interface SplitPanelLayoutComponentProps extends ComponentRendererProps {
  // 추가 props
  onUpdateComponent?: (component: any) => void;
  // 🆕 패널 내부 컴포넌트 선택 콜백 (탭 컴포넌트와 동일 구조)
  onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: PanelInlineComponent) => void;
  selectedPanelComponentId?: string;
}

// 이미지 셀 렌더링 컴포넌트 (objid 또는 파일 경로 지원)
const SplitPanelCellImage: React.FC<{ value: string }> = React.memo(({ value }) => {
  const [imgSrc, setImgSrc] = React.useState<string | null>(null);

  React.useEffect(() => {
    if (!value) return;
    const strVal = String(value).trim();
    if (!strVal || strVal === "-") return;

    if (strVal.startsWith("http") || strVal.startsWith("/uploads/") || strVal.startsWith("/api/")) {
      setImgSrc(getFullImageUrl(strVal));
    } else {
      const previewUrl = getFilePreviewUrl(strVal);
      fetch(previewUrl, { credentials: "include" })
        .then((res) => {
          if (!res.ok) throw new Error("fetch failed");
          return res.blob();
        })
        .then((blob) => setImgSrc(URL.createObjectURL(blob)))
        .catch(() => setImgSrc(null));
    }
  }, [value]);

  if (!imgSrc) return <span className="text-muted-foreground text-xs">-</span>;

  return <img src={imgSrc} alt="" className="h-8 w-8 rounded object-cover" onError={() => setImgSrc(null)} />;
});
SplitPanelCellImage.displayName = "SplitPanelCellImage";

/**
 * 커스텀 모드 런타임: 디자이너 좌표를 비례 스케일링하여 렌더링
 */
const ScaledCustomPanel: React.FC<{
  components: PanelInlineComponent[];
  formData: Record<string, any>;
  onFormDataChange: (fieldName: string, value: any) => void;
  tableName?: string;
  menuObjid?: number;
  screenId?: number;
  userId?: string;
  userName?: string;
  companyCode?: string;
  allComponents?: any;
  selectedRowsData?: any[];
  onSelectedRowsChange?: any;
}> = ({ components, formData, onFormDataChange, tableName, ...restProps }) => {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const [containerWidth, setContainerWidth] = React.useState(0);

  React.useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const ro = new ResizeObserver((entries) => {
      const w = entries[0]?.contentRect.width;
      if (w && w > 0) setContainerWidth(w);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const canvasW = Math.max(...components.map((c) => (c.position?.x || 0) + (c.size?.width || 200)), 400);
  const canvasH = Math.max(...components.map((c) => (c.position?.y || 0) + (c.size?.height || 100)), 200);
  return (
    <div ref={containerRef} className="relative w-full" style={{ height: `${canvasH}px` }}>
      {containerWidth > 0 &&
        components.map((comp) => {
          const x = comp.position?.x || 0;
          const y = comp.position?.y || 0;
          const w = comp.size?.width || 200;
          const h = comp.size?.height || 36;

          const componentData = {
            id: comp.id,
            type: "component" as const,
            componentType: comp.componentType,
            label: comp.label,
            position: { x, y },
            size: { width: undefined, height: h },
            componentConfig: comp.componentConfig || {},
            style: { ...(comp.style || {}), width: "100%", height: "100%" },
            tableName: comp.componentConfig?.tableName,
            columnName: comp.componentConfig?.columnName,
            webType: comp.componentConfig?.webType,
            inputType: (comp as any).inputType || comp.componentConfig?.inputType,
          };

          return (
            <div
              key={comp.id}
              className="absolute"
              style={{
                left: `${(x / canvasW) * 100}%`,
                top: `${y}px`,
                width: `${(w / canvasW) * 100}%`,
                minHeight: `${h}px`,
              }}
            >
              <DynamicComponentRenderer
                component={componentData as any}
                isDesignMode={false}
                isInteractive={true}
                formData={formData}
                onFormDataChange={onFormDataChange}
                tableName={tableName}
                menuObjid={restProps.menuObjid}
                screenId={restProps.screenId}
                userId={restProps.userId}
                userName={restProps.userName}
                companyCode={restProps.companyCode}
                allComponents={restProps.allComponents}
                selectedRowsData={restProps.selectedRowsData}
                onSelectedRowsChange={restProps.onSelectedRowsChange}
              />
            </div>
          );
        })}
    </div>
  );
};

/**
 * SplitPanelLayout 컴포넌트
 * 마스터-디테일 패턴의 좌우 분할 레이아웃
 */
export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps> = ({
  component,
  isDesignMode = false,
  isSelected = false,
  isPreview = false,
  onClick,
  onUpdateComponent,
  onSelectPanelComponent,
  selectedPanelComponentId: externalSelectedPanelComponentId,
  ...props
}) => {
  const componentConfig = (component.componentConfig || {}) as SplitPanelLayoutConfig;

  // 🆕 프리뷰용 회사 코드 오버라이드 (최고 관리자만 사용 가능)
  const companyCode = (props as any).companyCode as string | undefined;

  // 기본 설정값
  const splitRatio = componentConfig.splitRatio || 30;
  const resizable = componentConfig.resizable ?? true;
  const minLeftWidth = componentConfig.minLeftWidth || 200;
  const minRightWidth = componentConfig.minRightWidth || 300;

  // 필드 표시 유틸리티 (하드코딩 제거, 동적으로 작동)
  const shouldShowField = (fieldName: string): boolean => {
    const lower = fieldName.toLowerCase();

    // 기본 제외: id, 비밀번호, 토큰, 회사코드
    if (lower === "id" || lower === "company_code" || lower === "company_name") return false;
    if (lower.includes("password") || lower.includes("token")) return false;

    // 나머지는 모두 표시!
    return true;
  };

  // 🆕 엔티티 조인 컬럼명 변환 헬퍼
  // "테이블명.컬럼명" 형식을 "원본컬럼_조인컬럼명" 형식으로 변환하여 데이터 접근
  const getEntityJoinValue = useCallback(
    (item: any, columnName: string, entityColumnMap?: Record<string, string>): any => {
      // 🆕 백엔드가 제공하는 _label 필드 우선 사용
      // 백엔드는 "표시 컬럼"이 설정된 경우 columnName_label을 자동 생성
      const labelKey = `${columnName}_label`;
      if (item[labelKey] !== undefined && item[labelKey] !== "" && item[labelKey] !== null) {
        return item[labelKey];
      }

      // 직접 매칭 시도 (JOIN된 값이 없으면 원본 값 반환)
      if (item[columnName] !== undefined) {
        return item[columnName];
      }

      // "테이블명.컬럼명" 형식인 경우 (예: item_info.item_name)
      if (columnName.includes(".")) {
        const [tableName, fieldName] = columnName.split(".");

        // 🔍 엔티티 조인 컬럼 값 추출
        // 예: item_info.item_name, item_info.standard, item_info.unit

        // 1️⃣ 소스 컬럼 추론 (item_info → item_code, warehouse_info → warehouse_id 등)
        const inferredSourceColumn = tableName.replace("_info", "_code").replace("_mng", "_id");

        // 2️⃣ 정확한 키 매핑 시도: 소스컬럼_필드명
        // 예: item_code_item_name, item_code_standard, item_code_unit
        const exactKey = `${inferredSourceColumn}_${fieldName}`;
        if (item[exactKey] !== undefined) {
          return item[exactKey];
        }

        // 🆕 2-1️⃣ item_id 패턴 시도 (백엔드가 item_id_xxx 형식으로 반환하는 경우)
        // 예: item_info.item_name → item_id_item_name
        const idPatternKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
        if (item[idPatternKey] !== undefined) {
          return item[idPatternKey];
        }

        // 3️⃣ 별칭 패턴: 소스컬럼_name (기본 표시 컬럼용)
        // 예: item_code_name (item_name의 별칭)
        if (fieldName === "item_name" || fieldName === "name") {
          const aliasKey = `${inferredSourceColumn}_name`;
          if (item[aliasKey] !== undefined) {
            return item[aliasKey];
          }
          // 🆕 item_id_name 패턴도 시도
          const idAliasKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_name`;
          if (item[idAliasKey] !== undefined) {
            return item[idAliasKey];
          }
        }

        // 4️⃣ entityColumnMap에서 매핑 찾기 (화면 설정에서 지정된 경우)
        if (entityColumnMap && entityColumnMap[tableName]) {
          const sourceColumn = entityColumnMap[tableName];
          const joinedColumnName = `${sourceColumn}_${fieldName}`;
          if (item[joinedColumnName] !== undefined) {
            return item[joinedColumnName];
          }
        }

        // 5️⃣ 테이블명_컬럼명 형식으로 시도
        const underscoreKey = `${tableName}_${fieldName}`;
        if (item[underscoreKey] !== undefined) {
          return item[underscoreKey];
        }
      }

      return undefined;
    },
    [],
  );

  // TableOptions Context
  const { registerTable, unregisterTable } = useTableOptions();
  const [leftFilters, setLeftFilters] = useState<TableFilter[]>([]);
  const [leftGrouping, setLeftGrouping] = useState<string[]>([]);
  const [leftColumnVisibility, setLeftColumnVisibility] = useState<ColumnVisibility[]>([]);
  const [leftColumnOrder, setLeftColumnOrder] = useState<string[]>([]);
  const [leftGroupSumConfig, setLeftGroupSumConfig] = useState<GroupSumConfig | null>(null);
  // 좌측 패널 컬럼 헤더 드래그
  const [leftDraggedColumnIndex, setLeftDraggedColumnIndex] = useState<number | null>(null);
  const [leftDropTargetColumnIndex, setLeftDropTargetColumnIndex] = useState<number | null>(null);
  const [rightFilters, setRightFilters] = useState<TableFilter[]>([]);
  const [rightGrouping, setRightGrouping] = useState<string[]>([]);
  const [rightColumnVisibility, setRightColumnVisibility] = useState<ColumnVisibility[]>([]);
  // 우측 패널 컬럼 헤더 드래그 (디자인 + 런타임 순서 변경)
  const [rightDraggedColumnIndex, setRightDraggedColumnIndex] = useState<number | null>(null);
  const [rightDropTargetColumnIndex, setRightDropTargetColumnIndex] = useState<number | null>(null);
  const [rightDragSource, setRightDragSource] = useState<"main" | number | null>(null);
  const [runtimeColumnOrder, setRuntimeColumnOrder] = useState<Record<string, string[]>>({});

  // 데이터 상태
  const [leftData, setLeftData] = useState<any[]>([]);
  const [rightData, setRightData] = useState<any[] | any>(null); // 조인 모드는 배열, 상세 모드는 객체
  const [selectedLeftItem, setSelectedLeftItem] = useState<any>(null);
  const [expandedRightItems, setExpandedRightItems] = useState<Set<string | number>>(new Set()); // 확장된 우측 아이템
  const [customLeftSelectedData, setCustomLeftSelectedData] = useState<Record<string, any>>({}); // 커스텀 모드: 좌측 선택 데이터
  // 커스텀 모드: 탭/버튼 간 공유할 selectedRowsData 자체 관리 (항상 로컬 상태 사용)
  const [localSelectedRowsData, setLocalSelectedRowsData] = useState<any[]>([]);
  const handleLocalSelectedRowsChange = useCallback(
    (
      selectedRows: any[],
      selectedRowsDataNew: any[],
      sortBy?: string,
      sortOrder?: "asc" | "desc",
      columnOrder?: string[],
    ) => {
      setLocalSelectedRowsData(selectedRowsDataNew);
      if ((props as any).onSelectedRowsChange) {
        (props as any).onSelectedRowsChange(selectedRows, selectedRowsDataNew, sortBy, sortOrder, columnOrder);
      }
    },
    [(props as any).onSelectedRowsChange],
  );
  const [leftSearchQuery, setLeftSearchQuery] = useState("");
  const [rightSearchQuery, setRightSearchQuery] = useState("");
  const [isLoadingLeft, setIsLoadingLeft] = useState(false);
  const [isLoadingRight, setIsLoadingRight] = useState(false);
  const [rightTableColumns, setRightTableColumns] = useState<any[]>([]); // 우측 테이블 컬럼 정보
  const [columnInputTypes, setColumnInputTypes] = useState<Record<string, string>>({});
  const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들

  // 🆕 페이징 상태
  const [leftCurrentPage, setLeftCurrentPage] = useState(1);
  const [leftTotalPages, setLeftTotalPages] = useState(1);
  const [leftTotal, setLeftTotal] = useState(0);
  const [leftPageSize, setLeftPageSize] = useState(componentConfig.leftPanel?.pagination?.pageSize ?? 20);
  const [rightCurrentPage, setRightCurrentPage] = useState(1);
  const [rightTotalPages, setRightTotalPages] = useState(1);
  const [rightTotal, setRightTotal] = useState(0);
  const [rightPageSize, setRightPageSize] = useState(componentConfig.rightPanel?.pagination?.pageSize ?? 20);
  const [tabsPagination, setTabsPagination] = useState<
    Record<number, { currentPage: number; totalPages: number; total: number; pageSize: number }>
  >({});
  const [leftPageInput, setLeftPageInput] = useState("1");
  const [rightPageInput, setRightPageInput] = useState("1");

  const leftPaginationEnabled = componentConfig.leftPanel?.pagination?.enabled ?? false;
  const rightPaginationEnabled = componentConfig.rightPanel?.pagination?.enabled ?? false;

  // 추가 탭 관련 상태
  const [activeTabIndex, setActiveTabIndex] = useState(0); // 0 = 기본 탭, 1+ = 추가 탭
  const [tabsData, setTabsData] = useState<Record<number, any[]>>({}); // 탭별 데이터
  const [tabsLoading, setTabsLoading] = useState<Record<number, boolean>>({}); // 탭별 로딩 상태
  const [leftColumnLabels, setLeftColumnLabels] = useState<Record<string, string>>({}); // 좌측 컬럼 라벨
  const [rightColumnLabels, setRightColumnLabels] = useState<Record<string, string>>({}); // 우측 컬럼 라벨
  const [leftCategoryMappings, setLeftCategoryMappings] = useState<
    Record<string, Record<string, { label: string; color?: string }>>
  >({}); // 좌측 카테고리 매핑
  const [rightCategoryMappings, setRightCategoryMappings] = useState<
    Record<string, Record<string, { label: string; color?: string }>>
  >({}); // 우측 카테고리 매핑

  // 🆕 커스텀 모드: 드래그/리사이즈 상태
  const [draggingCompId, setDraggingCompId] = useState<string | null>(null);
  const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);
  const [resizingCompId, setResizingCompId] = useState<string | null>(null);
  const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null);
  // 내부 선택 상태 (외부 prop 없을 때 fallback)
  const [internalSelectedCompId, setInternalSelectedCompId] = useState<string | null>(null);
  const selectedPanelComponentId = externalSelectedPanelComponentId ?? internalSelectedCompId;
  // 🆕 커스텀 모드: 분할패널 내 탭 컴포넌트의 선택 상태 관리
  const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState<string | undefined>(undefined);
  const rafRef = useRef<number | null>(null);

  // 🆕 10px 단위 스냅 함수
  const snapTo10 = useCallback((value: number) => Math.round(value / 10) * 10, []);

  // 🆕 커스텀 모드: 컴포넌트 삭제 핸들러
  const handleRemovePanelComponent = useCallback(
    (panelSide: "left" | "right", compId: string) => {
      if (!onUpdateComponent) return;

      const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
      const panelConfig = componentConfig[panelKey] || {};
      const updatedComponents = (panelConfig.components || []).filter((c: PanelInlineComponent) => c.id !== compId);

      onUpdateComponent({
        ...component,
        componentConfig: {
          ...componentConfig,
          [panelKey]: {
            ...panelConfig,
            components: updatedComponents,
          },
        },
      });
    },
    [component, componentConfig, onUpdateComponent],
  );

  // 🆕 중첩된 컴포넌트 업데이트 핸들러 (탭 컴포넌트 내부 위치 변경 등)
  const handleNestedComponentUpdate = useCallback(
    (panelSide: "left" | "right", compId: string, updatedNestedComponent: any) => {
      if (!onUpdateComponent) return;

      const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
      const panelConfig = componentConfig[panelKey] || {};
      const panelComponents = panelConfig.components || [];

      const updatedComponents = panelComponents.map((c: PanelInlineComponent) =>
        c.id === compId ? { ...c, ...updatedNestedComponent, id: c.id } : c,
      );

      onUpdateComponent({
        ...component,
        componentConfig: {
          ...componentConfig,
          [panelKey]: {
            ...panelConfig,
            components: updatedComponents,
          },
        },
      });
    },
    [component, componentConfig, onUpdateComponent],
  );

  // 🆕 커스텀 모드: 드래그 시작 핸들러
  const handlePanelDragStart = useCallback(
    (e: React.MouseEvent, panelSide: "left" | "right", comp: PanelInlineComponent) => {
      e.stopPropagation();
      e.preventDefault();

      const startMouseX = e.clientX;
      const startMouseY = e.clientY;
      const startLeft = comp.position?.x || 0;
      const startTop = comp.position?.y || 0;

      setDraggingCompId(comp.id);
      setDragPosition({ x: startLeft, y: startTop });

      const handleMouseMove = (moveEvent: MouseEvent) => {
        if (rafRef.current) {
          cancelAnimationFrame(rafRef.current);
        }

        rafRef.current = requestAnimationFrame(() => {
          const deltaX = moveEvent.clientX - startMouseX;
          const deltaY = moveEvent.clientY - startMouseY;
          // 10px 단위 스냅 적용
          const newX = snapTo10(Math.max(0, startLeft + deltaX));
          const newY = snapTo10(Math.max(0, startTop + deltaY));
          setDragPosition({ x: newX, y: newY });
        });
      };

      const handleMouseUp = (upEvent: MouseEvent) => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);

        if (rafRef.current) {
          cancelAnimationFrame(rafRef.current);
          rafRef.current = null;
        }

        const deltaX = upEvent.clientX - startMouseX;
        const deltaY = upEvent.clientY - startMouseY;
        // 10px 단위 스냅 적용
        const newX = snapTo10(Math.max(0, startLeft + deltaX));
        const newY = snapTo10(Math.max(0, startTop + deltaY));

        setDraggingCompId(null);
        setDragPosition(null);

        if (onUpdateComponent) {
          const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
          const panelConfig = componentConfig[panelKey] || {};
          const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) =>
            c.id === comp.id ? { ...c, position: { x: newX, y: newY } } : c,
          );

          onUpdateComponent({
            ...component,
            componentConfig: {
              ...componentConfig,
              [panelKey]: {
                ...panelConfig,
                components: updatedComponents,
              },
            },
          });
        }
      };

      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
    },
    [component, componentConfig, onUpdateComponent, snapTo10],
  );

  // 🆕 커스텀 모드: 리사이즈 시작 핸들러
  const handlePanelResizeStart = useCallback(
    (e: React.MouseEvent, panelSide: "left" | "right", comp: PanelInlineComponent, direction: "e" | "s" | "se") => {
      e.stopPropagation();
      e.preventDefault();

      const startMouseX = e.clientX;
      const startMouseY = e.clientY;
      const startWidth = comp.size?.width || 200;
      const startHeight = comp.size?.height || 100;

      setResizingCompId(comp.id);
      setResizeSize({ width: startWidth, height: startHeight });

      const handleMouseMove = (moveEvent: MouseEvent) => {
        if (rafRef.current) {
          cancelAnimationFrame(rafRef.current);
        }

        rafRef.current = requestAnimationFrame(() => {
          const deltaX = moveEvent.clientX - startMouseX;
          const deltaY = moveEvent.clientY - startMouseY;

          let newWidth = startWidth;
          let newHeight = startHeight;

          if (direction === "e" || direction === "se") {
            newWidth = snapTo10(Math.max(50, startWidth + deltaX));
          }
          if (direction === "s" || direction === "se") {
            newHeight = snapTo10(Math.max(30, startHeight + deltaY));
          }

          setResizeSize({ width: newWidth, height: newHeight });
        });
      };

      const handleMouseUp = (upEvent: MouseEvent) => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);

        if (rafRef.current) {
          cancelAnimationFrame(rafRef.current);
          rafRef.current = null;
        }

        const deltaX = upEvent.clientX - startMouseX;
        const deltaY = upEvent.clientY - startMouseY;

        let newWidth = startWidth;
        let newHeight = startHeight;

        if (direction === "e" || direction === "se") {
          newWidth = snapTo10(Math.max(50, startWidth + deltaX));
        }
        if (direction === "s" || direction === "se") {
          newHeight = snapTo10(Math.max(30, startHeight + deltaY));
        }

        setResizingCompId(null);
        setResizeSize(null);

        if (onUpdateComponent) {
          const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel";
          const panelConfig = componentConfig[panelKey] || {};
          const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) =>
            c.id === comp.id ? { ...c, size: { width: newWidth, height: newHeight } } : c,
          );

          onUpdateComponent({
            ...component,
            componentConfig: {
              ...componentConfig,
              [panelKey]: {
                ...panelConfig,
                components: updatedComponents,
              },
            },
          });
        }
      };

      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
    },
    [component, componentConfig, onUpdateComponent, snapTo10],
  );

  const { toast } = useToast();

  // 추가 모달 상태
  const [showAddModal, setShowAddModal] = useState(false);
  const [addModalPanel, setAddModalPanel] = useState<"left" | "right" | "left-item" | null>(null);
  const [addModalFormData, setAddModalFormData] = useState<Record<string, any>>({});
  const [bomExcelUploadOpen, setBomExcelUploadOpen] = useState(false);

  // 수정 모달 상태
  const [showEditModal, setShowEditModal] = useState(false);
  const [editModalPanel, setEditModalPanel] = useState<"left" | "right" | null>(null);
  const [editModalItem, setEditModalItem] = useState<any>(null);
  const [editModalFormData, setEditModalFormData] = useState<Record<string, any>>({});

  // 삭제 확인 모달 상태
  const [showDeleteModal, setShowDeleteModal] = useState(false);
  const [deleteModalPanel, setDeleteModalPanel] = useState<"left" | "right" | null>(null);
  const [deleteModalItem, setDeleteModalItem] = useState<any>(null);
  const [deleteModalTableName, setDeleteModalTableName] = useState<string | null>(null); // 추가 탭 삭제 시 테이블명

  // 리사이저 드래그 상태
  const [isDragging, setIsDragging] = useState(false);
  const [leftWidth, setLeftWidth] = useState(splitRatio);
  const containerRef = React.useRef<HTMLDivElement>(null);

  // 🆕 SplitPanel Resize Context 연동 (버튼 등 외부 컴포넌트와 드래그 리사이즈 상태 공유)
  const splitPanelContext = useSplitPanel();
  const {
    registerSplitPanel: ctxRegisterSplitPanel,
    unregisterSplitPanel: ctxUnregisterSplitPanel,
    updateSplitPanel: ctxUpdateSplitPanel,
  } = splitPanelContext;
  const splitPanelId = `split-panel-${component.id}`;

  // Context에 분할 패널 등록 (좌표 정보 포함) - 마운트 시 1회만 실행
  const ctxRegisterRef = useRef(ctxRegisterSplitPanel);
  const ctxUnregisterRef = useRef(ctxUnregisterSplitPanel);
  ctxRegisterRef.current = ctxRegisterSplitPanel;
  ctxUnregisterRef.current = ctxUnregisterSplitPanel;

  useEffect(() => {
    // 컴포넌트의 위치와 크기 정보
    const panelX = component.position?.x || 0;
    const panelY = component.position?.y || 0;
    const panelWidth = component.size?.width || component.style?.width || 800;
    const panelHeight = component.size?.height || component.style?.height || 600;

    const panelInfo = {
      x: panelX,
      y: panelY,
      width: typeof panelWidth === "number" ? panelWidth : parseInt(String(panelWidth)) || 800,
      height: typeof panelHeight === "number" ? panelHeight : parseInt(String(panelHeight)) || 600,
      leftWidthPercent: splitRatio, // 초기값은 splitRatio 사용
      initialLeftWidthPercent: splitRatio,
      isDragging: false,
    };

    ctxRegisterRef.current(splitPanelId, panelInfo);

    return () => {
      ctxUnregisterRef.current(splitPanelId);
    };
    // 마운트/언마운트 시에만 실행, 위치/크기 변경은 별도 업데이트로 처리
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [splitPanelId]);

  // 위치/크기 변경 시 Context 업데이트 (등록 후)
  const ctxUpdateRef = useRef(ctxUpdateSplitPanel);
  ctxUpdateRef.current = ctxUpdateSplitPanel;

  useEffect(() => {
    const panelX = component.position?.x || 0;
    const panelY = component.position?.y || 0;
    const panelWidth = component.size?.width || component.style?.width || 800;
    const panelHeight = component.size?.height || component.style?.height || 600;

    ctxUpdateRef.current(splitPanelId, {
      x: panelX,
      y: panelY,
      width: typeof panelWidth === "number" ? panelWidth : parseInt(String(panelWidth)) || 800,
      height: typeof panelHeight === "number" ? panelHeight : parseInt(String(panelHeight)) || 600,
    });
  }, [
    splitPanelId,
    component.position?.x,
    component.position?.y,
    component.size?.width,
    component.size?.height,
    component.style?.width,
    component.style?.height,
  ]);

  // leftWidth 변경 시 Context 업데이트
  useEffect(() => {
    ctxUpdateRef.current(splitPanelId, { leftWidthPercent: leftWidth });
  }, [leftWidth, splitPanelId]);

  // 드래그 상태 변경 시 Context 업데이트
  // 이전 드래그 상태를 추적하여 드래그 종료 시점을 감지
  const prevIsDraggingRef = useRef(false);

  useEffect(() => {
    const wasJustDragging = prevIsDraggingRef.current && !isDragging;

    if (isDragging) {
      // 드래그 시작 시: 현재 비율을 초기 비율로 저장
      ctxUpdateRef.current(splitPanelId, {
        isDragging: true,
        initialLeftWidthPercent: leftWidth,
      });
    } else if (wasJustDragging) {
      // 드래그 종료 시: 최종 비율을 초기 비율로 업데이트 (버튼 위치 고정)
      ctxUpdateRef.current(splitPanelId, {
        isDragging: false,
        initialLeftWidthPercent: leftWidth,
      });
    }

    prevIsDraggingRef.current = isDragging;
  }, [isDragging, splitPanelId, leftWidth]);

  // 🆕 그룹별 합산된 데이터 계산
  const summedLeftData = useMemo(() => {
    // 그룹핑이 비활성화되었거나 그룹 기준 컬럼이 없으면 원본 데이터 반환
    if (!leftGroupSumConfig?.enabled || !leftGroupSumConfig?.groupByColumn) {
      return leftData;
    }

    const groupByColumn = leftGroupSumConfig.groupByColumn;
    const groupMap = new Map<string, any>();

    // 조인 컬럼인지 확인하고 실제 키 추론
    const getActualKey = (columnName: string, item: any): string => {
      if (columnName.includes(".")) {
        const [refTable, fieldName] = columnName.split(".");
        const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
        const exactKey = `${inferredSourceColumn}_${fieldName}`;
        if (item[exactKey] !== undefined) return exactKey;
        if (fieldName === "item_name" || fieldName === "name") {
          const aliasKey = `${inferredSourceColumn}_name`;
          if (item[aliasKey] !== undefined) return aliasKey;
        }
      }
      return columnName;
    };

    // 숫자 타입인지 확인하는 함수
    const isNumericValue = (value: any): boolean => {
      if (value === null || value === undefined || value === "") return false;
      const num = parseFloat(String(value));
      return !isNaN(num) && isFinite(num);
    };

    // 그룹핑 수행
    leftData.forEach((item) => {
      const actualKey = getActualKey(groupByColumn, item);
      const groupValue = String(item[actualKey] || item[groupByColumn] || "");

      // 원본 ID 추출 (id, ID, 또는 첫 번째 값)
      const originalId = item.id || item.ID || Object.values(item)[0];

      if (!groupMap.has(groupValue)) {
        // 첫 번째 항목을 기준으로 초기화 + 원본 ID 배열 + 원본 데이터 배열
        groupMap.set(groupValue, {
          ...item,
          _groupCount: 1,
          _originalIds: [originalId],
          _originalItems: [item], // 🆕 원본 데이터 전체 저장
        });
      } else {
        const existing = groupMap.get(groupValue);
        existing._groupCount += 1;
        existing._originalIds.push(originalId);
        existing._originalItems.push(item); // 🆕 원본 데이터 추가

        // 모든 키에 대해 숫자면 합산
        Object.keys(item).forEach((key) => {
          const value = item[key];
          if (isNumericValue(value) && key !== groupByColumn && !key.endsWith("_id") && !key.includes("code")) {
            const numValue = parseFloat(String(value));
            const existingValue = parseFloat(String(existing[key] || 0));
            existing[key] = existingValue + numValue;
          }
        });

        groupMap.set(groupValue, existing);
      }
    });

    return Array.from(groupMap.values());
  }, [leftData, leftGroupSumConfig]);

  // 컴포넌트 스타일
  // height: component.size?.height 우선, 없으면 component.style?.height, 기본 600px
  const getHeightValue = () => {
    const sizeH = component.size?.height;
    if (sizeH && typeof sizeH === "number" && sizeH > 0) return `${sizeH}px`;
    const height = component.style?.height;
    if (!height) return "600px";
    if (typeof height === "string") return height;
    return `${height}px`;
  };

  const componentStyle: React.CSSProperties = isDesignMode
    ? {
        width: "100%",
        height: "100%",
        minHeight: getHeightValue(),
        cursor: "pointer",
        border: isSelected ? "2px solid hsl(var(--primary))" : "1px solid hsl(var(--border))",
      }
    : {
        position: "relative",
        width: "100%",
        height: getHeightValue(),
      };

  // 계층 구조 빌드 함수 (트리 구조 유지)
  const buildHierarchy = useCallback(
    (items: any[]): any[] => {
      if (!items || items.length === 0) return [];

      const itemAddConfig = componentConfig.leftPanel?.itemAddConfig;
      if (!itemAddConfig) return items.map((item) => ({ ...item, children: [] })); // 계층 설정이 없으면 평면 목록

      const { sourceColumn, parentColumn } = itemAddConfig;
      if (!sourceColumn || !parentColumn) return items.map((item) => ({ ...item, children: [] }));

      // ID를 키로 하는 맵 생성
      const itemMap = new Map<any, any>();
      const rootItems: any[] = [];

      // 모든 항목을 맵에 추가하고 children 배열 초기화
      items.forEach((item) => {
        const id = item[sourceColumn];
        itemMap.set(id, { ...item, children: [], level: 0 });
      });

      // 부모-자식 관계 설정
      items.forEach((item) => {
        const id = item[sourceColumn];
        const parentId = item[parentColumn];
        const currentItem = itemMap.get(id);

        if (!currentItem) return;

        if (!parentId || parentId === null || parentId === "") {
          // 최상위 항목
          rootItems.push(currentItem);
        } else {
          // 부모가 있는 항목
          const parentItem = itemMap.get(parentId);
          if (parentItem) {
            currentItem.level = parentItem.level + 1;
            parentItem.children.push(currentItem);
          } else {
            // 부모를 찾을 수 없으면 최상위로 처리
            rootItems.push(currentItem);
          }
        }
      });

      return rootItems;
    },
    [componentConfig.leftPanel?.itemAddConfig],
  );

  // 🔧 사용자 ID 가져오기
  const { userId: currentUserId } = useAuth();

  // 🔄 필터를 searchValues 형식으로 변환
  const searchValues = useMemo(() => {
    if (!leftFilters || leftFilters.length === 0) return {};

    const values: Record<string, any> = {};
    leftFilters.forEach((filter) => {
      if (filter.value !== undefined && filter.value !== null && filter.value !== "") {
        values[filter.columnName] = {
          value: filter.value,
          operator: filter.operator || "contains",
        };
      }
    });
    return values;
  }, [leftFilters]);

  // 🔄 컬럼 가시성 및 순서 처리
  const visibleLeftColumns = useMemo(() => {
    const displayColumns = componentConfig.leftPanel?.columns || [];

    if (displayColumns.length === 0) return [];

    let columns = displayColumns;

    // columnVisibility가 있으면 가시성 + 너비 적용
    if (leftColumnVisibility.length > 0) {
      const visibilityMap = new Map(leftColumnVisibility.map((cv) => [cv.columnName, cv]));
      columns = columns
        .filter((col: any) => {
          const colName = typeof col === "string" ? col : col.name || col.columnName;
          return visibilityMap.get(colName)?.visible !== false;
        })
        .map((col: any) => {
          const colName = typeof col === "string" ? col : col.name || col.columnName;
          const cv = visibilityMap.get(colName);
          if (cv?.width && typeof col === "object") {
            return { ...col, width: cv.width };
          }
          return col;
        });
    }

    // 🔧 컬럼 순서 적용
    if (leftColumnOrder.length > 0) {
      const orderMap = new Map(leftColumnOrder.map((name, index) => [name, index]));
      columns = [...columns].sort((a, b) => {
        const aName = typeof a === "string" ? a : a.name || a.columnName;
        const bName = typeof b === "string" ? b : b.name || b.columnName;
        const aIndex = orderMap.get(aName) ?? 999;
        const bIndex = orderMap.get(bName) ?? 999;
        return aIndex - bIndex;
      });
    }

    return columns;
  }, [componentConfig.leftPanel?.columns, leftColumnVisibility, leftColumnOrder]);

  // 🔄 데이터 그룹화
  const groupedLeftData = useMemo(() => {
    if (!leftGrouping || leftGrouping.length === 0 || leftData.length === 0) return [];

    const grouped = new Map<string, any[]>();

    leftData.forEach((item) => {
      // 각 그룹 컬럼의 값을 조합하여 그룹 키 생성
      const groupKey = leftGrouping
        .map((col) => {
          const value = item[col];
          // null/undefined 처리
          return value === null || value === undefined ? "(비어있음)" : String(value);
        })
        .join(" > ");

      if (!grouped.has(groupKey)) {
        grouped.set(groupKey, []);
      }
      grouped.get(groupKey)!.push(item);
    });

    return Array.from(grouped.entries()).map(([key, items]) => ({
      groupKey: key,
      items,
      count: items.length,
    }));
  }, [leftData, leftGrouping]);

  // 날짜 포맷팅 헬퍼 함수
  const formatDateValue = useCallback((value: any, dateFormat: string): string => {
    if (!value) return "-";
    const date = new Date(value);
    if (isNaN(date.getTime())) return String(value);

    if (dateFormat === "relative") {
      // 상대 시간 (예: 3일 전, 2시간 전)
      const now = new Date();
      const diffMs = now.getTime() - date.getTime();
      const diffSec = Math.floor(diffMs / 1000);
      const diffMin = Math.floor(diffSec / 60);
      const diffHour = Math.floor(diffMin / 60);
      const diffDay = Math.floor(diffHour / 24);
      const diffMonth = Math.floor(diffDay / 30);
      const diffYear = Math.floor(diffMonth / 12);

      if (diffYear > 0) return `${diffYear}년 전`;
      if (diffMonth > 0) return `${diffMonth}개월 전`;
      if (diffDay > 0) return `${diffDay}일 전`;
      if (diffHour > 0) return `${diffHour}시간 전`;
      if (diffMin > 0) return `${diffMin}분 전`;
      return "방금 전";
    }

    // 포맷 문자열 치환
    return dateFormat
      .replace("YYYY", String(date.getFullYear()))
      .replace("MM", String(date.getMonth() + 1).padStart(2, "0"))
      .replace("DD", String(date.getDate()).padStart(2, "0"))
      .replace("HH", String(date.getHours()).padStart(2, "0"))
      .replace("mm", String(date.getMinutes()).padStart(2, "0"))
      .replace("ss", String(date.getSeconds()).padStart(2, "0"));
  }, []);

  // 숫자 포맷팅 헬퍼 함수 (공통 formatNumber 기반)
  const formatNumberValue = useCallback((value: any, format: any): string => {
    if (value === null || value === undefined || value === "") return "-";
    const num = typeof value === "number" ? value : parseFloat(String(value));
    if (isNaN(num)) return String(value);

    let result: string;
    if (format?.thousandSeparator === false) {
      const dec = format?.decimalPlaces ?? 0;
      result = num.toFixed(dec);
    } else {
      result = centralFormatNumber(num, format?.decimalPlaces);
    }

    if (format?.prefix) result = format.prefix + result;
    if (format?.suffix) result = result + format.suffix;
    return result;
  }, []);

  // 프로그레스바 셀 렌더링 (부모 값 대비 자식 값 비율)
  const renderProgressCell = useCallback((col: any, item: any, parentData: any) => {
    const current = Number(item[col.numerator] || 0);
    const max = Number(parentData?.[col.denominator] || item[col.denominator] || 0);
    const percentage = max > 0 ? Math.round((current / max) * 100) : 0;
    const barWidth = Math.min(percentage, 100);
    const barColor =
      percentage > 100
        ? "bg-destructive"
        : percentage >= 90
          ? "bg-destructive"
          : percentage >= 70
            ? "bg-warning"
            : "bg-success";

    return (
      <div className="flex min-w-[120px] items-center gap-2">
        <div className="flex-1">
          <div className="bg-muted h-2 w-full rounded-full">
            <div className={`h-2 rounded-full transition-all ${barColor}`} style={{ width: `${barWidth}%` }} />
          </div>
          <div className="text-muted-foreground mt-0.5 text-[10px]">
            {current.toLocaleString()} / {max.toLocaleString()}
          </div>
        </div>
        <span className="shrink-0 text-xs font-medium">{percentage}%</span>
      </div>
    );
  }, []);

  // 셀 값 포맷팅 함수 (카테고리 타입 처리 + 날짜/숫자 포맷)
  const formatCellValue = useCallback(
    (
      columnName: string,
      value: any,
      categoryMappings: Record<string, Record<string, { label: string; color?: string }>>,
      format?: {
        type?: "number" | "currency" | "date" | "text";
        thousandSeparator?: boolean;
        decimalPlaces?: number;
        prefix?: string;
        suffix?: string;
        dateFormat?: string;
      },
    ) => {
      if (value === null || value === undefined) return "-";

      // 이미지 타입 컬럼 처리
      const colInputType = columnInputTypes[columnName];
      if (colInputType === "image" && value) {
        return <SplitPanelCellImage value={String(value)} />;
      }

      // 🆕 날짜 포맷 적용
      if (format?.type === "date" || format?.dateFormat) {
        return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD");
      }

      // 숫자 포맷 적용 (format 설정이 있거나 input_type이 number/decimal이면 자동 적용)
      const isNumericByInputType = colInputType === "number" || colInputType === "decimal";
      if (
        format?.type === "number" ||
        format?.type === "currency" ||
        format?.thousandSeparator ||
        format?.decimalPlaces !== undefined ||
        isNumericByInputType
      ) {
        return formatNumberValue(value, format || { thousandSeparator: true });
      }

      // 카테고리 매핑 찾기 (여러 키 형태 시도)
      // 1. 전체 컬럼명 (예: "item_info.material")
      // 2. 컬럼명만 (예: "material")
      // 3. 전역 폴백: 모든 매핑에서 value 검색
      let mapping = categoryMappings[columnName];

      if (!mapping && columnName.includes(".")) {
        const simpleColumnName = columnName.split(".").pop() || columnName;
        mapping = categoryMappings[simpleColumnName];
      }

      const strValue = String(value);

      if (mapping && mapping[strValue]) {
        const categoryData = mapping[strValue];
        const displayLabel = categoryData.label || strValue;
        const displayColor = categoryData.color || "#64748b";

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

      // 전역 폴백: 컬럼명으로 매핑을 못 찾았을 때, 전체 매핑에서 값 검색
      if (!mapping && (strValue.startsWith("CAT_") || strValue.startsWith("CATEGORY_"))) {
        for (const key of Object.keys(categoryMappings)) {
          const m = categoryMappings[key];
          if (m && m[strValue]) {
            const categoryData = m[strValue];
            const displayLabel = categoryData.label || strValue;
            const displayColor = categoryData.color || "#64748b";
            return (
              <Badge
                style={{
                  backgroundColor: displayColor,
                  borderColor: displayColor,
                }}
                className="text-white"
              >
                {displayLabel}
              </Badge>
            );
          }
        }
      }

      // 🆕 자동 날짜 감지 (ISO 8601 형식 또는 Date 객체)
      if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) {
        return formatDateValue(value, "YYYY-MM-DD");
      }

      // 🆕 자동 숫자 감지 (숫자 또는 숫자 문자열) - 소수점 있으면 정수로 변환
      if (typeof value === "number") {
        // 숫자인 경우 정수로 표시 (소수점 제거)
        return Number.isInteger(value) ? String(value) : String(Math.round(value * 100) / 100);
      }
      if (typeof value === "string" && /^-?\d+\.?\d*$/.test(value.trim())) {
        // 숫자 문자열인 경우 (예: "5.00" → "5")
        const num = parseFloat(value);
        if (!isNaN(num)) {
          return Number.isInteger(num) ? String(num) : String(Math.round(num * 100) / 100);
        }
      }

      // 일반 값
      return String(value);
    },
    [formatDateValue, formatNumberValue, columnInputTypes],
  );

  // 🆕 패널 config의 columns에서 additionalJoinColumns 추출하는 헬퍼
  const extractAdditionalJoinColumns = useCallback((columns: any[] | undefined, tableName: string) => {
    if (!columns || columns.length === 0) return undefined;

    const joinColumns: Array<{
      sourceTable: string;
      sourceColumn: string;
      referenceTable: string;
      joinAlias: string;
    }> = [];

    columns.forEach((col: any) => {
      // 방법 1: isEntityJoin 플래그가 있는 경우 (설정 패널에서 Entity 조인 컬럼으로 추가한 경우)
      if (col.isEntityJoin && col.joinInfo) {
        const existing = joinColumns.find(
          (j) => j.referenceTable === col.joinInfo.referenceTable && j.joinAlias === col.joinInfo.joinAlias,
        );
        if (!existing) {
          joinColumns.push({
            sourceTable: col.joinInfo.sourceTable || tableName,
            sourceColumn: col.joinInfo.sourceColumn,
            referenceTable: col.joinInfo.referenceTable,
            joinAlias: col.joinInfo.joinAlias,
          });
        }
        return;
      }

      // 방법 2: "테이블명.컬럼명" 형식 (기존 좌측 패널 방식)
      const colName = typeof col === "string" ? col : col.name || col.columnName;
      if (colName && colName.includes(".")) {
        const [refTable, refColumn] = colName.split(".");
        const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
        const existing = joinColumns.find(
          (j) => j.referenceTable === refTable && j.sourceColumn === inferredSourceColumn,
        );
        if (!existing) {
          joinColumns.push({
            sourceTable: tableName,
            sourceColumn: inferredSourceColumn,
            referenceTable: refTable,
            joinAlias: `${inferredSourceColumn}_${refColumn}`,
          });
        } else {
          // 이미 추가된 테이블이면 별칭만 추가
          const newAlias = `${inferredSourceColumn}_${refColumn}`;
          if (!joinColumns.find((j) => j.joinAlias === newAlias)) {
            joinColumns.push({
              sourceTable: tableName,
              sourceColumn: inferredSourceColumn,
              referenceTable: refTable,
              joinAlias: newAlias,
            });
          }
        }
      }
    });

    return joinColumns.length > 0 ? joinColumns : undefined;
  }, []);

  // 좌측 데이터 로드 (페이징 ON: page 파라미터 사용, OFF: 전체 로드)
  const loadLeftData = useCallback(
    async (page?: number, pageSizeOverride?: number) => {
      const leftTableName = componentConfig.leftPanel?.tableName;
      if (!leftTableName || isDesignMode) return;

      setIsLoadingLeft(true);
      try {
        const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined;
        const leftJoinColumns = extractAdditionalJoinColumns(componentConfig.leftPanel?.columns, leftTableName);

        if (leftPaginationEnabled) {
          const currentPageToLoad = page ?? leftCurrentPage;
          const effectivePageSize = pageSizeOverride ?? leftPageSize;
          const result = await entityJoinApi.getTableDataWithJoins(leftTableName, {
            page: currentPageToLoad,
            size: effectivePageSize,
            search: filters,
            enableEntityJoin: true,
            dataFilter: componentConfig.leftPanel?.dataFilter,
            additionalJoinColumns: leftJoinColumns,
            companyCodeOverride: companyCode,
          });

          setLeftData(result.data || []);
          setLeftCurrentPage(result.page || currentPageToLoad);
          setLeftTotalPages(result.totalPages || 1);
          setLeftTotal(result.total || 0);
          setLeftPageInput(String(result.page || currentPageToLoad));
        } else {
          const result = await entityJoinApi.getTableDataWithJoins(leftTableName, {
            page: 1,
            size: MAX_LOAD_ALL_SIZE,
            search: filters,
            enableEntityJoin: true,
            dataFilter: componentConfig.leftPanel?.dataFilter,
            additionalJoinColumns: leftJoinColumns,
            companyCodeOverride: companyCode,
          });

          const filteredLeftData = applyClientSideFilter(result.data || [], componentConfig.leftPanel?.dataFilter);

          const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
          if (leftColumn && filteredLeftData.length > 0) {
            filteredLeftData.sort((a, b) => {
              const aValue = String(a[leftColumn] || "");
              const bValue = String(b[leftColumn] || "");
              return aValue.localeCompare(bValue, "ko-KR");
            });
          }

          const hierarchicalData = buildHierarchy(filteredLeftData);
          setLeftData(hierarchicalData);
        }
      } catch (error) {
        console.error("좌측 데이터 로드 실패:", error);
        toast({
          title: "데이터 로드 실패",
          description: "좌측 패널 데이터를 불러올 수 없습니다.",
          variant: "destructive",
        });
      } finally {
        setIsLoadingLeft(false);
      }
    },
    [
      componentConfig.leftPanel?.tableName,
      componentConfig.leftPanel?.columns,
      componentConfig.leftPanel?.dataFilter,
      componentConfig.rightPanel?.relation?.leftColumn,
      leftPaginationEnabled,
      leftCurrentPage,
      leftPageSize,
      isDesignMode,
      toast,
      buildHierarchy,
      searchValues,
    ],
  );

  const updateRightPaginationState = useCallback((result: any, fallbackPage: number) => {
    setRightCurrentPage(result.page || fallbackPage);
    setRightTotalPages(result.totalPages || 1);
    setRightTotal(result.total || 0);
    setRightPageInput(String(result.page || fallbackPage));
  }, []);

  // 우측 데이터 로드 (leftItem이 null이면 전체 데이터 로드, page: 서버 페이징용)
  const loadRightData = useCallback(
    async (leftItem: any, page?: number, pageSizeOverride?: number) => {
      const relationshipType = componentConfig.rightPanel?.relation?.type || "detail";
      const rightTableName = componentConfig.rightPanel?.tableName;

      if (!rightTableName || isDesignMode) return;

      // 좌측 미선택 시: 전체 데이터 로드 (dataFilter 적용)
      if (!leftItem && relationshipType === "join") {
        setIsLoadingRight(true);
        try {
          const rightJoinColumns = extractAdditionalJoinColumns(componentConfig.rightPanel?.columns, rightTableName);
          const effectivePageSize = pageSizeOverride ?? rightPageSize;

          if (rightPaginationEnabled) {
            const currentPageToLoad = page ?? rightCurrentPage;
            const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
              page: currentPageToLoad,
              size: effectivePageSize,
              enableEntityJoin: true,
              companyCodeOverride: companyCode,
              additionalJoinColumns: rightJoinColumns,
              dataFilter: componentConfig.rightPanel?.dataFilter,
            });

            setRightData(result.data || []);
            updateRightPaginationState(result, currentPageToLoad);
          } else {
            const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
              enableEntityJoin: true,
              size: MAX_LOAD_ALL_SIZE,
              companyCodeOverride: companyCode,
              additionalJoinColumns: rightJoinColumns,
              dataFilter: componentConfig.rightPanel?.dataFilter,
            });

            const filteredData = applyClientSideFilter(result.data || [], componentConfig.rightPanel?.dataFilter);
            setRightData(filteredData);
          }
        } catch (error) {
          console.error("우측 전체 데이터 로드 실패:", error);
        } finally {
          setIsLoadingRight(false);
        }
        return;
      }

      // leftItem이 null이면 join 모드 이외에는 데이터 로드 불가
      // detail 모드: 선택 안 하면 아무것도 안 뜸, 선택하면 필터링
      // join 모드: 선택 안 하면 전체, 선택하면 필터링
      if (!leftItem) return;

      setIsLoadingRight(true);
      setRightData([]);
      try {
        // detail / join 모두 동일한 필터링 로직 사용
        // (차이점: 초기 로드 여부만 다름 - detail은 초기 로드 안 함)
        {
          // 조인 모드: 다른 테이블의 관련 데이터 (여러 개)
          const keys = componentConfig.rightPanel?.relation?.keys;
          const leftTable = componentConfig.leftPanel?.tableName;

          // 🆕 그룹 합산된 항목인 경우: 원본 데이터들로 우측 패널 표시
          if (leftItem._originalItems && leftItem._originalItems.length > 0) {
            // 정렬 기준 컬럼 (복합키의 leftColumn들)
            const sortColumns = keys?.map((k: any) => k.leftColumn).filter(Boolean) || [];

            // 정렬 함수
            const sortByKeys = (data: any[]) => {
              if (sortColumns.length === 0) return data;
              return [...data].sort((a, b) => {
                for (const col of sortColumns) {
                  const aVal = String(a[col] || "");
                  const bVal = String(b[col] || "");
                  const cmp = aVal.localeCompare(bVal, "ko-KR");
                  if (cmp !== 0) return cmp;
                }
                return 0;
              });
            };

            // 원본 데이터를 그대로 우측 패널에 표시 (이력 테이블과 동일 테이블인 경우)
            if (leftTable === rightTableName) {
              const sortedData = sortByKeys(leftItem._originalItems);
              setRightData(sortedData);
              return;
            }

            // 다른 테이블인 경우: 원본 ID들로 조회
            const { entityJoinApi } = await import("@/lib/api/entityJoin");
            const allResults: any[] = [];

            // 🆕 우측 패널 Entity 조인 컬럼 추출 (그룹 합산용)
            const rightJoinColumnsForGroup = extractAdditionalJoinColumns(
              componentConfig.rightPanel?.columns,
              rightTableName,
            );

            // 각 원본 항목에 대해 조회
            for (const originalItem of leftItem._originalItems) {
              const searchConditions: Record<string, any> = {};
              keys?.forEach((key: any) => {
                if (key.leftColumn && key.rightColumn && originalItem[key.leftColumn] !== undefined) {
                  searchConditions[key.rightColumn] = { value: originalItem[key.leftColumn], operator: "equals" };
                }
              });

              if (Object.keys(searchConditions).length > 0) {
                const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
                  search: searchConditions,
                  enableEntityJoin: true,
                  size: MAX_LOAD_ALL_SIZE,
                  companyCodeOverride: companyCode,
                  additionalJoinColumns: rightJoinColumnsForGroup,
                });
                if (result.data) {
                  allResults.push(...result.data);
                }
              }
            }

            const sortedResults = sortByKeys(allResults);
            setRightData(sortedResults);
            return;
          }

          // 🆕 복합키 지원
          if (keys && keys.length > 0 && leftTable) {
            // 복합키: 여러 조건으로 필터링
            const { entityJoinApi } = await import("@/lib/api/entityJoin");

            // 복합키 조건 생성 (FK 필터링이므로 equals 연산자 사용)
            const searchConditions: Record<string, any> = {};
            keys.forEach((key) => {
              if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) {
                searchConditions[key.rightColumn] = { value: leftItem[key.leftColumn], operator: "equals" };
              }
            });

            // 🆕 우측 패널 config의 Entity 조인 컬럼 추출
            const rightJoinColumns = extractAdditionalJoinColumns(componentConfig.rightPanel?.columns, rightTableName);
            if (rightJoinColumns) {
              console.log("🔗 [분할패널] 우측 패널 additionalJoinColumns:", rightJoinColumns);
            }

            const effectivePageSize = pageSizeOverride ?? rightPageSize;
            const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
              search: searchConditions,
              enableEntityJoin: true,
              size: rightPaginationEnabled ? effectivePageSize : MAX_LOAD_ALL_SIZE,
              page: rightPaginationEnabled ? (page ?? rightCurrentPage) : undefined,
              companyCodeOverride: companyCode,
              additionalJoinColumns: rightJoinColumns,
            });

            if (rightPaginationEnabled) {
              updateRightPaginationState(result, page ?? rightCurrentPage);
            }

            setRightData(result.data || []);
          } else {
            // 단일키 (하위 호환성) → entityJoinApi 사용으로 전환 (entity 조인 컬럼 지원)
            const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
            const rightColumn = componentConfig.rightPanel?.relation?.foreignKey;

            if (leftColumn && rightColumn && leftTable) {
              const leftValue = leftItem[leftColumn];
              const { entityJoinApi } = await import("@/lib/api/entityJoin");

              // 단일키를 복합키 형식으로 변환 (entity 컬럼이므로 equals 연산자 필수)
              const searchConditions: Record<string, any> = {};
              searchConditions[rightColumn] = { value: leftValue, operator: "equals" };

              // Entity 조인 컬럼 추출
              const rightJoinColumnsLegacy = extractAdditionalJoinColumns(
                componentConfig.rightPanel?.columns,
                rightTableName,
              );
              const effectivePageSizeLegacy = pageSizeOverride ?? rightPageSize;
              const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
                search: searchConditions,
                enableEntityJoin: true,
                size: rightPaginationEnabled ? effectivePageSizeLegacy : MAX_LOAD_ALL_SIZE,
                page: rightPaginationEnabled ? (page ?? rightCurrentPage) : undefined,
                companyCodeOverride: companyCode,
                additionalJoinColumns: rightJoinColumnsLegacy,
              });

              if (rightPaginationEnabled) {
                updateRightPaginationState(result, page ?? rightCurrentPage);
              }

              setRightData(result.data || []);
            }
          }
        }
      } catch (error) {
        console.error("우측 데이터 로드 실패:", error);
        setRightData([]);
        toast({
          title: "데이터 로드 실패",
          description: "우측 패널 데이터를 불러올 수 없습니다.",
          variant: "destructive",
        });
      } finally {
        setIsLoadingRight(false);
      }
    },
    [
      componentConfig.rightPanel?.tableName,
      componentConfig.rightPanel?.relation,
      componentConfig.leftPanel?.tableName,
      rightPaginationEnabled,
      rightCurrentPage,
      rightPageSize,
      isDesignMode,
      toast,
      updateRightPaginationState,
    ],
  );

  // 추가 탭 데이터 로딩 함수 (leftItem이 null이면 전체 데이터 로드, page: 서버 페이징용)
  const loadTabData = useCallback(
    async (tabIndex: number, leftItem: any, page?: number, pageSizeOverride?: number) => {
      const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1];
      if (!tabConfig || isDesignMode) return;

      const tabTableName = tabConfig.tableName;
      if (!tabTableName) return;

      setTabsLoading((prev) => ({ ...prev, [tabIndex]: true }));
      try {
        const keys = tabConfig.relation?.keys;
        const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn;
        const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn;
        const tabJoinColumns = extractAdditionalJoinColumns(tabConfig.columns, tabTableName);
        let resultData: any[] = [];
        let apiResult: any = null;
        const tabDataFilterForApi = (tabConfig as any).dataFilter;
        const tabRelationType = tabConfig.relation?.type || "join";

        const tabPagState = tabsPagination[tabIndex];
        const currentTabPage = page ?? tabPagState?.currentPage ?? 1;
        const currentTabPageSize = pageSizeOverride ?? tabPagState?.pageSize ?? rightPageSize;
        const apiSize = rightPaginationEnabled ? currentTabPageSize : MAX_LOAD_ALL_SIZE;
        const apiPage = rightPaginationEnabled ? currentTabPage : undefined;

        const commonApiParams = {
          enableEntityJoin: true,
          size: apiSize,
          page: apiPage,
          companyCodeOverride: companyCode,
          additionalJoinColumns: tabJoinColumns,
          dataFilter: tabDataFilterForApi,
        };

        if (!leftItem) {
          if (tabRelationType !== "detail") {
            apiResult = await entityJoinApi.getTableDataWithJoins(tabTableName, commonApiParams);
            resultData = apiResult.data || [];
          }
        } else if (leftColumn && rightColumn) {
          const searchConditions: Record<string, any> = {};
          if (keys && keys.length > 0) {
            keys.forEach((key: any) => {
              if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) {
                searchConditions[key.rightColumn] = { value: leftItem[key.leftColumn], operator: "equals" };
              }
            });
          } else {
            const leftValue = leftItem[leftColumn];
            if (leftValue !== undefined) {
              searchConditions[rightColumn] = { value: leftValue, operator: "equals" };
            }
          }

          apiResult = await entityJoinApi.getTableDataWithJoins(tabTableName, {
            search: searchConditions,
            ...commonApiParams,
          });
          resultData = apiResult.data || [];
        } else {
          apiResult = await entityJoinApi.getTableDataWithJoins(tabTableName, commonApiParams);
          resultData = apiResult.data || [];
        }

        // 공통 페이징 상태 업데이트
        if (rightPaginationEnabled && apiResult) {
          setTabsPagination((prev) => ({
            ...prev,
            [tabIndex]: {
              currentPage: apiResult.page || currentTabPage,
              totalPages: apiResult.totalPages || 1,
              total: apiResult.total || 0,
              pageSize: currentTabPageSize,
            },
          }));
        }

        if (!rightPaginationEnabled) {
          resultData = applyClientSideFilter(resultData, (tabConfig as any).dataFilter);
        }

        setTabsData((prev) => ({ ...prev, [tabIndex]: resultData }));
      } catch (error) {
        console.error(`추가탭 ${tabIndex} 데이터 로드 실패:`, error);
        toast({
          title: "데이터 로드 실패",
          description: "탭 데이터를 불러올 수 없습니다.",
          variant: "destructive",
        });
      } finally {
        setTabsLoading((prev) => ({ ...prev, [tabIndex]: false }));
      }
    },
    [
      componentConfig.rightPanel?.additionalTabs,
      rightPaginationEnabled,
      rightPageSize,
      tabsPagination,
      isDesignMode,
      toast,
    ],
  );

  // 🆕 좌측 페이지 변경 핸들러
  const handleLeftPageChange = useCallback(
    (newPage: number) => {
      if (newPage < 1 || newPage > leftTotalPages) return;
      setLeftCurrentPage(newPage);
      setLeftPageInput(String(newPage));
      loadLeftData(newPage);
    },
    [leftTotalPages, loadLeftData],
  );

  const commitLeftPageInput = useCallback(() => {
    const parsed = parseInt(leftPageInput, 10);
    if (!isNaN(parsed) && parsed >= 1 && parsed <= leftTotalPages) {
      handleLeftPageChange(parsed);
    } else {
      setLeftPageInput(String(leftCurrentPage));
    }
  }, [leftPageInput, leftTotalPages, leftCurrentPage, handleLeftPageChange]);

  // 🆕 좌측 페이지 크기 변경
  const handleLeftPageSizeChange = useCallback(
    (newSize: number) => {
      setLeftPageSize(newSize);
      setLeftCurrentPage(1);
      setLeftPageInput("1");
      loadLeftData(1, newSize);
    },
    [loadLeftData],
  );

  // 🆕 우측 페이지 변경 핸들러
  const handleRightPageChange = useCallback(
    (newPage: number) => {
      if (newPage < 1 || newPage > rightTotalPages) return;
      setRightCurrentPage(newPage);
      setRightPageInput(String(newPage));
      if (activeTabIndex === 0) {
        loadRightData(selectedLeftItem, newPage);
      } else {
        loadTabData(activeTabIndex, selectedLeftItem, newPage);
      }
    },
    [rightTotalPages, activeTabIndex, selectedLeftItem, loadRightData, loadTabData],
  );

  const commitRightPageInput = useCallback(() => {
    const parsed = parseInt(rightPageInput, 10);
    const tp = activeTabIndex === 0 ? rightTotalPages : (tabsPagination[activeTabIndex]?.totalPages ?? 1);
    if (!isNaN(parsed) && parsed >= 1 && parsed <= tp) {
      handleRightPageChange(parsed);
    } else {
      const cp = activeTabIndex === 0 ? rightCurrentPage : (tabsPagination[activeTabIndex]?.currentPage ?? 1);
      setRightPageInput(String(cp));
    }
  }, [rightPageInput, rightTotalPages, rightCurrentPage, activeTabIndex, tabsPagination, handleRightPageChange]);

  // 🆕 우측 페이지 크기 변경
  const handleRightPageSizeChange = useCallback(
    (newSize: number) => {
      setRightPageSize(newSize);
      setRightCurrentPage(1);
      setRightPageInput("1");
      setTabsPagination({});
      if (activeTabIndex === 0) {
        loadRightData(selectedLeftItem, 1, newSize);
      } else {
        loadTabData(activeTabIndex, selectedLeftItem, 1, newSize);
      }
    },
    [activeTabIndex, selectedLeftItem, loadRightData, loadTabData],
  );

  // 🆕 페이징 UI 컴포넌트 (공통)
  const renderPaginationBar = useCallback(
    (params: {
      currentPage: number;
      totalPages: number;
      total: number;
      pageSize: number;
      pageInput: string;
      setPageInput: (v: string) => void;
      onPageChange: (p: number) => void;
      onPageSizeChange: (s: number) => void;
      commitPageInput: () => void;
      loading: boolean;
    }) => {
      const {
        currentPage,
        totalPages,
        total,
        pageSize,
        pageInput,
        setPageInput,
        onPageChange,
        onPageSizeChange,
        commitPageInput: commitFn,
        loading,
      } = params;
      return (
        <div className="border-border bg-background relative flex h-10 w-full flex-shrink-0 items-center justify-center border-t px-2">
          <div className="absolute left-2 flex items-center gap-1">
            <span className="text-muted-foreground text-[10px]">표시:</span>
            <input
              type="number"
              min={1}
              max={MAX_LOAD_ALL_SIZE}
              value={pageSize}
              onChange={(e) => {
                const v = Math.min(MAX_LOAD_ALL_SIZE, Math.max(1, Number(e.target.value) || 1));
                onPageSizeChange(v);
              }}
              className="border-input bg-background focus:ring-ring h-6 w-12 rounded border px-1 text-center text-[10px] focus:ring-1 focus:outline-none"
            />
            <span className="text-muted-foreground text-[10px]">/ {total}건</span>
          </div>

          <div className="flex items-center gap-1">
            <Button
              variant="outline"
              size="sm"
              onClick={() => onPageChange(1)}
              disabled={currentPage === 1 || loading}
              className="h-6 w-6 p-0"
            >
              <ChevronsLeft className="h-3 w-3" />
            </Button>
            <Button
              variant="outline"
              size="sm"
              onClick={() => onPageChange(currentPage - 1)}
              disabled={currentPage === 1 || loading}
              className="h-6 w-6 p-0"
            >
              <ChevronLeft className="h-3 w-3" />
            </Button>
            <div className="flex items-center gap-1">
              <input
                type="text"
                inputMode="numeric"
                value={pageInput}
                onChange={(e) => setPageInput(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter") {
                    commitFn();
                    (e.target as HTMLInputElement).blur();
                  }
                }}
                onBlur={commitFn}
                onFocus={(e) => e.target.select()}
                disabled={loading}
                className="border-input bg-background focus:ring-ring h-6 w-8 rounded border px-1 text-center text-[10px] font-medium focus:ring-1 focus:outline-none"
              />
              <span className="text-muted-foreground text-[10px]">/</span>
              <span className="text-foreground text-[10px] font-medium">{totalPages || 1}</span>
            </div>
            <Button
              variant="outline"
              size="sm"
              onClick={() => onPageChange(currentPage + 1)}
              disabled={currentPage >= totalPages || loading}
              className="h-6 w-6 p-0"
            >
              <ChevronRight className="h-3 w-3" />
            </Button>
            <Button
              variant="outline"
              size="sm"
              onClick={() => onPageChange(totalPages)}
              disabled={currentPage >= totalPages || loading}
              className="h-6 w-6 p-0"
            >
              <ChevronsRight className="h-3 w-3" />
            </Button>
          </div>
        </div>
      );
    },
    [],
  );

  // 우측/탭 페이징 상태 (IIFE 대신 useMemo로 사전 계산)
  const rightPagState = useMemo(() => {
    const isTab = activeTabIndex > 0;
    const tabPag = isTab ? tabsPagination[activeTabIndex] : null;
    return {
      isTab,
      currentPage: isTab ? (tabPag?.currentPage ?? 1) : rightCurrentPage,
      totalPages: isTab ? (tabPag?.totalPages ?? 1) : rightTotalPages,
      total: isTab ? (tabPag?.total ?? 0) : rightTotal,
      pageSize: isTab ? (tabPag?.pageSize ?? rightPageSize) : rightPageSize,
    };
  }, [activeTabIndex, tabsPagination, rightCurrentPage, rightTotalPages, rightTotal, rightPageSize]);

  // 탭 변경 핸들러
  const handleTabChange = useCallback(
    (newTabIndex: number) => {
      setActiveTabIndex(newTabIndex);

      // 메인 패널이 "detail"(선택 시 표시)이면 좌측 미선택 시 데이터 로드하지 않음
      const mainRelationType = componentConfig.rightPanel?.relation?.type || "detail";
      const requireSelection = mainRelationType === "detail";

      if (newTabIndex === 0) {
        if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) {
          if (!requireSelection || selectedLeftItem) {
            loadRightData(selectedLeftItem);
          }
        }
      } else {
        if (!tabsData[newTabIndex]) {
          if (!requireSelection || selectedLeftItem) {
            loadTabData(newTabIndex, selectedLeftItem);
          }
        }
      }
    },
    [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData, componentConfig.rightPanel?.relation?.type],
  );

  // 좌측 항목 선택 핸들러 (동일 항목 재클릭 시 선택 해제 → 전체 데이터 표시)
  const handleLeftItemSelect = useCallback(
    (item: any) => {
      // 동일 항목 클릭 시 선택 해제 (전체 보기로 복귀)
      const leftPk =
        componentConfig.rightPanel?.relation?.leftColumn || componentConfig.rightPanel?.relation?.keys?.[0]?.leftColumn;
      const isSameItem = selectedLeftItem && leftPk && selectedLeftItem[leftPk] === item[leftPk];

      if (isSameItem) {
        setSelectedLeftItem(null);
        setCustomLeftSelectedData({});
        setExpandedRightItems(new Set());
        setTabsData({});

        // 우측/탭 페이지 리셋
        if (rightPaginationEnabled) {
          setRightCurrentPage(1);
          setRightPageInput("1");
          setTabsPagination({});
        }

        const mainRelationType = componentConfig.rightPanel?.relation?.type || "detail";
        if (mainRelationType === "detail") {
          // "선택 시 표시" 모드: 선택 해제 시 데이터 비움
          setRightData(null);
        } else {
          // "연관 목록" 모드: 선택 해제 시 전체 데이터 로드
          if (activeTabIndex === 0) {
            loadRightData(null);
          } else {
            loadTabData(activeTabIndex, null);
          }
          const tabs = componentConfig.rightPanel?.additionalTabs;
          if (tabs && tabs.length > 0) {
            tabs.forEach((_: any, idx: number) => {
              if (idx + 1 !== activeTabIndex) {
                loadTabData(idx + 1, null);
              }
            });
          }
        }
        return;
      }

      setSelectedLeftItem(item);
      setCustomLeftSelectedData(item);
      setExpandedRightItems(new Set());
      setTabsData({});

      // 우측/탭 페이지 리셋
      if (rightPaginationEnabled) {
        setRightCurrentPage(1);
        setRightPageInput("1");
        setTabsPagination({});
      }

      if (activeTabIndex === 0) {
        loadRightData(item, 1);
      } else {
        loadTabData(activeTabIndex, item, 1);
      }

      // modalDataStore에 선택된 좌측 항목 저장 (단일 선택)
      const leftTableName = componentConfig.leftPanel?.tableName;
      if (leftTableName && !isDesignMode) {
        import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
          useModalDataStore.getState().setData(leftTableName, [item]);
        });
      }
    },
    [
      loadRightData,
      loadTabData,
      activeTabIndex,
      componentConfig.leftPanel?.tableName,
      componentConfig.rightPanel?.relation,
      componentConfig.rightPanel?.additionalTabs,
      isDesignMode,
      selectedLeftItem,
      rightPaginationEnabled,
    ],
  );

  // 우측 항목 확장/축소 토글
  const toggleRightItemExpansion = useCallback((itemId: string | number) => {
    setExpandedRightItems((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(itemId)) {
        newSet.delete(itemId);
      } else {
        newSet.add(itemId);
      }
      return newSet;
    });
  }, []);

  // 컬럼명을 라벨로 변환하는 함수
  const getColumnLabel = useCallback(
    (columnName: string) => {
      const column = rightTableColumns.find((col) => col.columnName === columnName || col.column_name === columnName);
      return column?.columnLabel || column?.column_label || column?.displayName || columnName;
    },
    [rightTableColumns],
  );

  // 🔧 컬럼의 고유값 가져오기 함수
  const getLeftColumnUniqueValues = useCallback(
    async (columnName: string) => {
      const leftTableName = componentConfig.leftPanel?.tableName;
      if (!leftTableName) return [];

      // 1단계: 카테고리 API 시도 (DB에서 라벨 조회)
      try {
        const { apiClient } = await import("@/lib/api/client");
        const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`);
        if (response.data.success && response.data.data && response.data.data.length > 0) {
          return response.data.data.map((item: any) => ({
            value: item.valueCode,
            label: item.valueLabel,
          }));
        }
      } catch {
        // 카테고리 API 실패 시 다음 단계로
      }

      // 2단계: DISTINCT API (백엔드 라벨 변환 포함)
      try {
        const { apiClient } = await import("@/lib/api/client");
        const response = await apiClient.get(`/entity/${leftTableName}/distinct/${columnName}`);
        if (response.data.success && response.data.data && response.data.data.length > 0) {
          return response.data.data.map((item: any) => ({
            value: String(item.value),
            label: String(item.label),
          }));
        }
      } catch {
        // DISTINCT API 실패 시 다음 단계로
      }

      // 3단계: 로컬 데이터에서 고유값 추출 (최종 fallback)
      if (leftData.length === 0) return [];

      const uniqueValuesMap = new Map<string, string>();

      leftData.forEach((item) => {
        let value: any;

        if (columnName.includes(".")) {
          const [refTable, fieldName] = columnName.split(".");
          const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");

          const exactKey = `${inferredSourceColumn}_${fieldName}`;
          value = item[exactKey];

          if (value === undefined) {
            const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
            value = item[idPatternKey];
          }

          if (value === undefined && (fieldName === "item_name" || fieldName === "name")) {
            const aliasKey = `${inferredSourceColumn}_name`;
            value = item[aliasKey];
            if (value === undefined) {
              const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`;
              value = item[idAliasKey];
            }
          }
        } else {
          value = item[columnName];
        }

        if (value !== null && value !== undefined && value !== "") {
          const strValue = String(value);
          const nameField = item[`${columnName}_name`];
          const label = nameField || strValue;
          uniqueValuesMap.set(strValue, label);
        }
      });

      return Array.from(uniqueValuesMap.entries())
        .map(([value, label]) => ({ value, label }))
        .sort((a, b) => a.label.localeCompare(b.label));
    },
    [componentConfig.leftPanel?.tableName, leftData],
  );

  // 좌측 테이블 등록 (Context에 등록)
  useEffect(() => {
    const leftTableName = componentConfig.leftPanel?.tableName;
    if (!leftTableName || isDesignMode) return;

    const leftTableId = `split-panel-left-${component.id}`;
    // 🔧 화면에 표시되는 컬럼 사용 (columns 속성)
    const configuredColumns = componentConfig.leftPanel?.columns || [];

    // 🆕 설정에서 지정한 라벨 맵 생성
    const configuredLabels: Record<string, string> = {};
    configuredColumns.forEach((col: any) => {
      if (typeof col === "object" && col.name && col.label) {
        configuredLabels[col.name] = col.label;
      }
    });

    const displayColumns = configuredColumns
      .map((col: any) => {
        if (typeof col === "string") return col;
        return col.columnName || col.name || col;
      })
      .filter(Boolean);

    // 화면에 설정된 컬럼이 없으면 등록하지 않음
    if (displayColumns.length === 0) return;

    // 테이블명이 있으면 등록
    registerTable({
      tableId: leftTableId,
      label: `${component.title || "분할 패널"} (좌측)`,
      tableName: leftTableName,
      columns: displayColumns.map((col: string) => ({
        columnName: col,
        // 🆕 우선순위: 1) 설정에서 지정한 라벨 2) DB 라벨 3) 컬럼명
        columnLabel: configuredLabels[col] || leftColumnLabels[col] || col,
        inputType: "text",
        visible: true,
        width: 150,
        sortable: true,
        filterable: true,
      })),
      onFilterChange: setLeftFilters,
      onGroupChange: setLeftGrouping,
      onColumnVisibilityChange: setLeftColumnVisibility,
      onColumnOrderChange: setLeftColumnOrder, // 🔧 컬럼 순서 변경 콜백 추가
      getColumnUniqueValues: getLeftColumnUniqueValues, // 🔧 고유값 가져오기 함수 추가
      onGroupSumChange: setLeftGroupSumConfig, // 🆕 그룹별 합산 설정 콜백
    });

    return () => unregisterTable(leftTableId);
  }, [
    component.id,
    componentConfig.leftPanel?.tableName,
    componentConfig.leftPanel?.columns,
    leftColumnLabels,
    component.title,
    isDesignMode,
    getLeftColumnUniqueValues,
  ]);

  // 우측 테이블은 검색 컴포넌트 등록 제외 (좌측 마스터 테이블만 검색 가능)
  // useEffect(() => {
  //   const rightTableName = componentConfig.rightPanel?.tableName;
  //   if (!rightTableName || isDesignMode) return;
  //
  //   const rightTableId = `split-panel-right-${component.id}`;
  //   // 🔧 화면에 표시되는 컬럼만 등록 (displayColumns 또는 columns)
  //   const displayColumns = componentConfig.rightPanel?.columns || [];
  //   const rightColumns = displayColumns.map((col: any) => col.columnName || col.name || col).filter(Boolean);
  //
  //   if (rightColumns.length > 0) {
  //     registerTable({
  //       tableId: rightTableId,
  //       label: `${component.title || "분할 패널"} (우측)`,
  //       tableName: rightTableName,
  //       columns: rightColumns.map((col: string) => ({
  //         columnName: col,
  //         columnLabel: rightColumnLabels[col] || col,
  //         inputType: "text",
  //         visible: true,
  //         width: 150,
  //         sortable: true,
  //         filterable: true,
  //       })),
  //       onFilterChange: setRightFilters,
  //       onGroupChange: setRightGrouping,
  //       onColumnVisibilityChange: setRightColumnVisibility,
  //     });
  //
  //     return () => unregisterTable(rightTableId);
  //   }
  // }, [component.id, componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.columns, rightColumnLabels, component.title, isDesignMode]);

  // 좌측 테이블 컬럼 라벨 로드
  useEffect(() => {
    const loadLeftColumnLabels = async () => {
      const leftTableName = componentConfig.leftPanel?.tableName;
      if (!leftTableName || isDesignMode) return;

      try {
        const columnsResponse = await tableTypeApi.getColumns(leftTableName);
        const labels: Record<string, string> = {};
        columnsResponse.forEach((col: any) => {
          const columnName = col.columnName || col.column_name;
          const label = col.columnLabel || col.column_label || col.displayName || columnName;
          if (columnName) {
            labels[columnName] = label;
          }
        });
        setLeftColumnLabels(labels);
      } catch (error) {
        console.error("좌측 테이블 컬럼 라벨 로드 실패:", error);
      }
    };

    loadLeftColumnLabels();
  }, [componentConfig.leftPanel?.tableName, isDesignMode]);

  // 우측 테이블 컬럼 정보 로드
  useEffect(() => {
    const loadRightTableColumns = async () => {
      const rightTableName = componentConfig.rightPanel?.tableName;
      if (!rightTableName || isDesignMode) return;

      try {
        const columnsResponse = await tableTypeApi.getColumns(rightTableName);
        setRightTableColumns(columnsResponse || []);

        // 우측 컬럼 라벨도 함께 로드
        const labels: Record<string, string> = {};
        columnsResponse.forEach((col: any) => {
          const columnName = col.columnName || col.column_name;
          const label = col.columnLabel || col.column_label || col.displayName || columnName;
          if (columnName) {
            labels[columnName] = label;
          }
        });
        setRightColumnLabels(labels);

        // 컬럼 inputType 로드 (이미지 등 특수 렌더링을 위해)
        const tablesToLoad = new Set<string>([rightTableName]);
        const additionalTabs = componentConfig.rightPanel?.additionalTabs || [];
        additionalTabs.forEach((tab: any) => {
          if (tab.tableName) tablesToLoad.add(tab.tableName);
        });

        const inputTypes: Record<string, string> = {};
        for (const tbl of tablesToLoad) {
          try {
            const inputTypesResponse = await tableTypeApi.getColumnInputTypes(tbl);
            inputTypesResponse.forEach((col: any) => {
              const colName = col.columnName || col.column_name;
              if (colName) {
                inputTypes[colName] = col.inputType || "text";
              }
            });
          } catch {
            // ignore
          }
        }
        setColumnInputTypes(inputTypes);
      } catch (error) {
        console.error("우측 테이블 컬럼 정보 로드 실패:", error);
      }
    };

    loadRightTableColumns();
  }, [componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.additionalTabs, isDesignMode]);

  // 좌측 테이블 카테고리 매핑 로드 (조인된 테이블 포함)
  useEffect(() => {
    const loadLeftCategoryMappings = async () => {
      const leftTableName = componentConfig.leftPanel?.tableName;
      if (!leftTableName || isDesignMode) return;

      try {
        const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
        const tablesToLoad = new Set<string>([leftTableName]);

        // 좌측 패널 컬럼 설정에서 조인된 테이블 추출
        const leftColumns = componentConfig.leftPanel?.columns || [];
        leftColumns.forEach((col: any) => {
          const colName = col.name || col.columnName;
          if (colName && colName.includes(".")) {
            const joinTableName = colName.split(".")[0];
            tablesToLoad.add(joinTableName);
          }
        });

        // 각 테이블에 대해 카테고리 매핑 로드
        for (const tableName of tablesToLoad) {
          try {
            const columnsResponse = await tableTypeApi.getColumns(tableName);
            const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");

            for (const col of categoryColumns) {
              const columnName = col.columnName || col.column_name;
              try {
                const response = await apiClient.get(
                  `/table-categories/${tableName}/${columnName}/values?includeInactive=true`,
                );

                if (response.data.success && response.data.data) {
                  const valueMap: Record<string, { label: string; color?: string }> = {};
                  response.data.data.forEach((item: any) => {
                    valueMap[item.value_code || item.valueCode] = {
                      label: item.value_label || item.valueLabel,
                      color: item.color,
                    };
                  });

                  // 조인된 테이블은 "테이블명.컬럼명" 형태로도 저장
                  const mappingKey = tableName === leftTableName ? columnName : `${tableName}.${columnName}`;
                  mappings[mappingKey] = valueMap;

                  // 컬럼명만으로도 접근 가능하도록 추가 저장
                  mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap };
                }
              } catch (error) {
                console.error(`좌측 카테고리 값 조회 실패 [${tableName}.${columnName}]:`, error);
              }
            }
          } catch (error) {
            console.error(`좌측 카테고리 테이블 컬럼 조회 실패 [${tableName}]:`, error);
          }
        }

        setLeftCategoryMappings(mappings);
      } catch (error) {
        console.error("좌측 카테고리 매핑 로드 실패:", error);
      }
    };

    loadLeftCategoryMappings();
  }, [componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, isDesignMode]);

  // 우측 테이블 카테고리 매핑 로드 (조인된 테이블 포함)
  useEffect(() => {
    const loadRightCategoryMappings = async () => {
      const rightTableName = componentConfig.rightPanel?.tableName;
      if (!rightTableName || isDesignMode) return;

      try {
        const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};

        // 🆕 우측 패널 컬럼 설정에서 조인된 테이블 추출
        const rightColumns = componentConfig.rightPanel?.columns || [];
        const tablesToLoad = new Set<string>([rightTableName]);

        // 컬럼명에서 테이블명 추출 (예: "item_info.material" -> "item_info")
        rightColumns.forEach((col: any) => {
          const colName = col.name || col.columnName;
          if (colName && colName.includes(".")) {
            const joinTableName = colName.split(".")[0];
            tablesToLoad.add(joinTableName);
          }
        });

        // 🆕 추가 탭의 테이블도 카테고리 로드 대상에 포함
        const additionalTabs = componentConfig.rightPanel?.additionalTabs || [];
        additionalTabs.forEach((tab: any) => {
          if (tab.tableName) {
            tablesToLoad.add(tab.tableName);
          }
          // 추가 탭 컬럼에서 조인된 테이블 추출
          (tab.columns || []).forEach((col: any) => {
            const colName = col.name || col.columnName;
            if (colName && colName.includes(".")) {
              const joinTableName = colName.split(".")[0];
              tablesToLoad.add(joinTableName);
            }
          });
        });

        // 각 테이블에 대해 카테고리 매핑 로드
        for (const tableName of tablesToLoad) {
          try {
            // 1. 컬럼 메타 정보 조회
            const columnsResponse = await tableTypeApi.getColumns(tableName);
            const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");

            // 2. 각 카테고리 컬럼에 대한 값 조회
            for (const col of categoryColumns) {
              const columnName = col.columnName || col.column_name;
              try {
                const response = await apiClient.get(
                  `/table-categories/${tableName}/${columnName}/values?includeInactive=true`,
                );

                if (response.data.success && response.data.data) {
                  const valueMap: Record<string, { label: string; color?: string }> = {};
                  response.data.data.forEach((item: any) => {
                    valueMap[item.value_code || item.valueCode] = {
                      label: item.value_label || item.valueLabel,
                      color: item.color,
                    };
                  });

                  // 조인된 테이블의 경우 "테이블명.컬럼명" 형태로 저장
                  const mappingKey = tableName === rightTableName ? columnName : `${tableName}.${columnName}`;
                  mappings[mappingKey] = valueMap;

                  // 🆕 컬럼명만으로도 접근할 수 있도록 추가 저장 (모든 테이블)
                  // 기존 매핑이 있으면 병합, 없으면 새로 생성
                  mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap };
                }
              } catch (error) {
                console.error(`우측 카테고리 값 조회 실패 [${tableName}.${columnName}]:`, error);
              }
            }
          } catch (error) {
            console.error(`테이블 ${tableName} 컬럼 정보 조회 실패:`, error);
          }
        }

        setRightCategoryMappings(mappings);
      } catch (error) {
        console.error("우측 카테고리 매핑 로드 실패:", error);
      }
    };

    loadRightCategoryMappings();
  }, [
    componentConfig.rightPanel?.tableName,
    componentConfig.rightPanel?.columns,
    componentConfig.rightPanel?.additionalTabs,
    isDesignMode,
  ]);

  // 항목 펼치기/접기 토글
  const toggleExpand = useCallback((itemId: any) => {
    setExpandedItems((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(itemId)) {
        newSet.delete(itemId);
      } else {
        newSet.add(itemId);
      }
      return newSet;
    });
  }, []);

  // 추가 버튼 핸들러
  const handleAddClick = useCallback(
    (panel: "left" | "right") => {
      // 좌측 패널 추가 시, addButton 모달 모드 확인
      if (panel === "left") {
        const addButtonConfig = componentConfig.leftPanel?.addButton;
        if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
          const leftTableName = componentConfig.leftPanel?.tableName || "";

          // ScreenModal 열기 이벤트 발생
          window.dispatchEvent(
            new CustomEvent("openScreenModal", {
              detail: {
                screenId: addButtonConfig.modalScreenId,
                urlParams: {
                  mode: "add",
                  tableName: leftTableName,
                },
              },
            }),
          );

          return;
        }
      }

      // 우측 패널 추가 시, addButton 모달 모드 확인
      if (panel === "right") {
        const addButtonConfig =
          activeTabIndex === 0
            ? componentConfig.rightPanel?.addButton
            : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addButton;

        if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
          if (!selectedLeftItem) {
            toast({
              title: "항목을 선택해주세요",
              description: "좌측 패널에서 항목을 먼저 선택한 후 추가해주세요.",
              variant: "destructive",
            });
            return;
          }

          const currentTableName =
            activeTabIndex === 0
              ? componentConfig.rightPanel?.tableName || ""
              : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || "";

          // 좌측 선택 데이터를 modalDataStore에 저장
          if (selectedLeftItem && componentConfig.leftPanel?.tableName) {
            import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
              useModalDataStore.getState().setData(componentConfig.leftPanel!.tableName!, [selectedLeftItem]);
            });
          }

          // relation.keys에서 FK 데이터 추출
          const parentData: Record<string, any> = {};
          const relation =
            activeTabIndex === 0
              ? componentConfig.rightPanel?.relation
              : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.relation;

          if (relation?.keys && Array.isArray(relation.keys)) {
            for (const key of relation.keys) {
              if (key.leftColumn && key.rightColumn && selectedLeftItem[key.leftColumn] != null) {
                parentData[key.rightColumn] = selectedLeftItem[key.leftColumn];
              }
            }
          } else if (relation) {
            const leftColumn = relation.leftColumn;
            const rightColumn = relation.foreignKey || relation.rightColumn;
            if (leftColumn && rightColumn && selectedLeftItem[leftColumn] != null) {
              parentData[rightColumn] = selectedLeftItem[leftColumn];
            }
          }

          window.dispatchEvent(
            new CustomEvent("openScreenModal", {
              detail: {
                screenId: addButtonConfig.modalScreenId,
                urlParams: {
                  mode: "add",
                  tableName: currentTableName,
                },
                splitPanelParentData: parentData,
              },
            }),
          );

          return;
        }
      }

      // 기존 내장 추가 모달 로직
      setAddModalPanel(panel);

      // 우측 패널 추가 시, 좌측에서 선택된 항목의 조인 컬럼 값을 자동으로 채움
      if (
        panel === "right" &&
        selectedLeftItem &&
        componentConfig.leftPanel?.leftColumn &&
        componentConfig.rightPanel?.rightColumn
      ) {
        const leftColumnValue = selectedLeftItem[componentConfig.leftPanel.leftColumn];
        setAddModalFormData({
          [componentConfig.rightPanel.rightColumn]: leftColumnValue,
        });
      } else {
        setAddModalFormData({});
      }

      setShowAddModal(true);
    },
    [selectedLeftItem, componentConfig, activeTabIndex],
  );

  // 수정 버튼 핸들러
  const handleEditClick = useCallback(
    (panel: "left" | "right", item: any) => {
      // 좌측 패널 수정 버튼 설정 확인 (모달 모드)
      if (panel === "left") {
        const editButtonConfig = componentConfig.leftPanel?.editButton;
        if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) {
          const leftTableName = componentConfig.leftPanel?.tableName || "";

          // Primary Key 찾기 - 실제 DB의 id 컬럼 값을 우선 사용
          let primaryKeyValue = item.id || item.ID;

          if (primaryKeyValue === undefined || primaryKeyValue === null) {
            // id가 없으면 sourceColumn 시도, 마지막으로 첫 번째 키
            const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
            primaryKeyValue = item[sourceColumn];
            if (primaryKeyValue === undefined || primaryKeyValue === null) {
              const firstKey = Object.keys(item)[0];
              primaryKeyValue = item[firstKey];
            }
          }

          // modalDataStore에 저장
          import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
            useModalDataStore.getState().setData(leftTableName, [item]);
          });

          // ScreenModal 열기 이벤트 발생
          window.dispatchEvent(
            new CustomEvent("openScreenModal", {
              detail: {
                screenId: editButtonConfig.modalScreenId,
                urlParams: {
                  mode: "edit",
                  editId: primaryKeyValue,
                  tableName: leftTableName,
                },
              },
            }),
          );

          return;
        }
      }

      // 우측 패널 수정 버튼 설정 확인 (탭별 설정 지원)
      if (panel === "right") {
        const editButtonConfig =
          activeTabIndex === 0
            ? componentConfig.rightPanel?.editButton
            : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.editButton;

        const currentTableName =
          activeTabIndex === 0
            ? componentConfig.rightPanel?.tableName || ""
            : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || "";

        if (editButtonConfig?.mode === "modal") {
          const modalScreenId = editButtonConfig?.modalScreenId;

          if (modalScreenId) {
            // 커스텀 모달 화면 열기
            const rightTableName = currentTableName;

            // Primary Key 찾기 (우선순위: id > ID > user_id > {table}_id > 첫 번째 필드)
            let primaryKeyName = "id";
            let primaryKeyValue: any;

            if (item.id !== undefined && item.id !== null) {
              primaryKeyName = "id";
              primaryKeyValue = item.id;
            } else if (item.ID !== undefined && item.ID !== null) {
              primaryKeyName = "ID";
              primaryKeyValue = item.ID;
            } else if (item.user_id !== undefined && item.user_id !== null) {
              // user_info 테이블 등 user_id를 Primary Key로 사용하는 경우
              primaryKeyName = "user_id";
              primaryKeyValue = item.user_id;
            } else {
              // 테이블명_id 패턴 확인 (예: dept_id, item_id 등)
              const tableIdKey = rightTableName ? `${rightTableName.replace(/_info$/, "")}_id` : "";
              if (tableIdKey && item[tableIdKey] !== undefined && item[tableIdKey] !== null) {
                primaryKeyName = tableIdKey;
                primaryKeyValue = item[tableIdKey];
              } else {
                // 마지막으로 첫 번째 필드를 Primary Key로 간주
                const firstKey = Object.keys(item)[0];
                primaryKeyName = firstKey;
                primaryKeyValue = item[firstKey];
              }
            }

            // modalDataStore에도 저장 (호환성 유지)
            import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
              useModalDataStore.getState().setData(rightTableName, [item]);
            });

            // 🆕 groupByColumns 추출
            const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || [];

            // ScreenModal 열기 이벤트 발생 (URL 파라미터로 ID + groupByColumns 전달)
            window.dispatchEvent(
              new CustomEvent("openScreenModal", {
                detail: {
                  screenId: modalScreenId,
                  urlParams: {
                    mode: "edit",
                    editId: primaryKeyValue,
                    tableName: rightTableName,
                    ...(groupByColumns.length > 0 && {
                      groupByColumns: JSON.stringify(groupByColumns),
                    }),
                  },
                },
              }),
            );

            return;
          }
        }
      }

      // 기존 자동 편집 모드 (인라인 편집 모달)
      setEditModalPanel(panel);
      setEditModalItem(item);
      setEditModalFormData({ ...item });
      setShowEditModal(true);
    },
    [componentConfig, activeTabIndex],
  );

  // 커스텀 모드 우측 패널 저장 (인라인 편집 데이터)
  const handleCustomRightSave = useCallback(async () => {
    if (!selectedLeftItem || !customLeftSelectedData || Object.keys(customLeftSelectedData).length === 0) {
      toast({
        title: "저장 실패",
        description: "저장할 데이터가 없습니다. 좌측에서 항목을 선택해주세요.",
        variant: "destructive",
      });
      return;
    }

    const tableName = componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName;
    if (!tableName) {
      toast({
        title: "저장 실패",
        description: "테이블 정보가 없습니다.",
        variant: "destructive",
      });
      return;
    }

    // Primary Key 찾기 - 실제 DB의 id 컬럼 값을 사용 (sourceColumn은 관계 연결용이므로 PK로 사용하지 않음)
    const primaryKey = selectedLeftItem.id || selectedLeftItem.ID;

    if (!primaryKey) {
      toast({
        title: "저장 실패",
        description: "Primary Key를 찾을 수 없습니다.",
        variant: "destructive",
      });
      return;
    }

    try {
      // 프론트엔드 전용 필드 제거
      const cleanData = { ...customLeftSelectedData };
      delete cleanData.children;
      delete cleanData.level;
      delete cleanData._originalItems;

      // company_code 자동 추가
      if (companyCode && !cleanData.company_code) {
        cleanData.company_code = companyCode;
      }

      const response = await dataApi.updateRecord(tableName, primaryKey, cleanData);

      if (response.success) {
        toast({
          title: "저장 완료",
          description: "데이터가 저장되었습니다.",
        });
        // 좌측 데이터 새로고침 (변경된 항목 반영)
        loadLeftData();
        // selectedLeftItem도 업데이트
        setSelectedLeftItem(customLeftSelectedData);
      } else {
        toast({
          title: "저장 실패",
          description: response.error || "데이터 저장에 실패했습니다.",
          variant: "destructive",
        });
      }
    } catch (error) {
      console.error("커스텀 우측 패널 저장 오류:", error);
      toast({
        title: "저장 오류",
        description: "데이터 저장 중 오류가 발생했습니다.",
        variant: "destructive",
      });
    }
  }, [selectedLeftItem, customLeftSelectedData, componentConfig, companyCode, toast, loadLeftData]);

  // 좌측 패널 컬럼 헤더 드래그
  const handleLeftColumnDragStart = useCallback((e: React.DragEvent, columnIndex: number) => {
    setLeftDraggedColumnIndex(columnIndex);
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/plain", `left-col-${columnIndex}`);
  }, []);
  const handleLeftColumnDragOver = useCallback((e: React.DragEvent, columnIndex: number) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    setLeftDropTargetColumnIndex(columnIndex);
  }, []);
  const handleLeftColumnDragEnd = useCallback(() => {
    setLeftDraggedColumnIndex(null);
    setLeftDropTargetColumnIndex(null);
  }, []);
  const handleLeftColumnDrop = useCallback(
    (e: React.DragEvent, targetIndex: number) => {
      e.preventDefault();
      const fromIdx = leftDraggedColumnIndex;
      if (fromIdx === null || fromIdx === targetIndex) {
        handleLeftColumnDragEnd();
        return;
      }
      const leftColumns = componentConfig.leftPanel?.columns || [];
      const colNames = leftColumns
        .filter((c: any) => typeof c === "string" || c.name || c.columnName)
        .map((c: any) => (typeof c === "string" ? c : c.name || c.columnName));
      if (
        colNames.length > 0 &&
        fromIdx >= 0 &&
        fromIdx < colNames.length &&
        targetIndex >= 0 &&
        targetIndex < colNames.length
      ) {
        const reordered = [...colNames];
        const [removed] = reordered.splice(fromIdx, 1);
        reordered.splice(targetIndex, 0, removed);
        setLeftColumnOrder(reordered);
      }
      handleLeftColumnDragEnd();
    },
    [leftDraggedColumnIndex, componentConfig, handleLeftColumnDragEnd],
  );

  // 우측 패널 컬럼 헤더 드래그 (디자인 모드에서 컬럼 순서 변경)
  const handleRightColumnDragStart = useCallback((e: React.DragEvent, columnIndex: number, source: "main" | number) => {
    setRightDraggedColumnIndex(columnIndex);
    setRightDragSource(source);
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/plain", `col-${source}-${columnIndex}`);
  }, []);
  const handleRightColumnDragOver = useCallback((e: React.DragEvent, columnIndex: number) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    setRightDropTargetColumnIndex(columnIndex);
  }, []);
  const handleRightColumnDragEnd = useCallback(() => {
    setRightDraggedColumnIndex(null);
    setRightDropTargetColumnIndex(null);
    setRightDragSource(null);
  }, []);
  const handleRightColumnDrop = useCallback(
    (e: React.DragEvent, targetIndex: number, source: "main" | number) => {
      e.preventDefault();
      const fromIdx = rightDraggedColumnIndex;
      if (fromIdx === null || rightDragSource !== source || fromIdx === targetIndex) {
        handleRightColumnDragEnd();
        return;
      }

      if (isDesignMode && onUpdateComponent) {
        // 디자인 모드: config에 영구 저장
        const rightPanel = componentConfig.rightPanel || {};
        if (source === "main") {
          const allColumns = rightPanel.columns || [];
          const visibleColumns = allColumns.filter((c: any) => c.showInSummary !== false);
          const hiddenColumns = allColumns.filter((c: any) => c.showInSummary === false);
          if (
            fromIdx < 0 ||
            fromIdx >= visibleColumns.length ||
            targetIndex < 0 ||
            targetIndex >= visibleColumns.length
          ) {
            handleRightColumnDragEnd();
            return;
          }
          const reordered = [...visibleColumns];
          const [removed] = reordered.splice(fromIdx, 1);
          reordered.splice(targetIndex, 0, removed);
          const columns = [...reordered, ...hiddenColumns];
          onUpdateComponent({
            ...component,
            componentConfig: {
              ...componentConfig,
              rightPanel: { ...rightPanel, columns },
            },
          });
        } else {
          const tabs = [...(rightPanel.additionalTabs || [])];
          const tabConfig = tabs[source];
          if (!tabConfig || !Array.isArray(tabConfig.columns)) {
            handleRightColumnDragEnd();
            return;
          }
          const allTabCols = tabConfig.columns;
          const visibleTabCols = allTabCols.filter((c: any) => c.showInSummary !== false);
          const hiddenTabCols = allTabCols.filter((c: any) => c.showInSummary === false);
          if (
            fromIdx < 0 ||
            fromIdx >= visibleTabCols.length ||
            targetIndex < 0 ||
            targetIndex >= visibleTabCols.length
          ) {
            handleRightColumnDragEnd();
            return;
          }
          const reordered = [...visibleTabCols];
          const [removed] = reordered.splice(fromIdx, 1);
          reordered.splice(targetIndex, 0, removed);
          const columns = [...reordered, ...hiddenTabCols];
          const newTabs = tabs.map((t, i) => (i === source ? { ...t, columns } : t));
          onUpdateComponent({
            ...component,
            componentConfig: {
              ...componentConfig,
              rightPanel: { ...rightPanel, additionalTabs: newTabs },
            },
          });
        }
      } else {
        // 런타임 모드: 컬럼 이름 순서로 로컬 상태 관리
        const key = String(source);
        const rightPanel = componentConfig.rightPanel || {};
        let colNames: string[] = [];

        if (source === "main") {
          const allColumns = rightPanel.columns || [];
          colNames = allColumns.filter((c: any) => c.showInSummary !== false).map((c: any) => c.name);
        } else if (typeof source === "number") {
          const tabs = rightPanel.additionalTabs || [];
          const tabConfig = tabs[source];
          if (tabConfig?.columns) {
            colNames = tabConfig.columns.filter((c: any) => c.showInSummary !== false).map((c: any) => c.name);
          }
        }

        if (colNames.length > 0) {
          setRuntimeColumnOrder((prev) => {
            const currentOrder = prev[key] || colNames;
            const reordered = [...currentOrder];
            if (fromIdx >= 0 && fromIdx < reordered.length && targetIndex >= 0 && targetIndex < reordered.length) {
              const [removed] = reordered.splice(fromIdx, 1);
              reordered.splice(targetIndex, 0, removed);
            }
            return { ...prev, [key]: reordered };
          });
        }
      }
      handleRightColumnDragEnd();
    },
    [
      rightDraggedColumnIndex,
      rightDragSource,
      componentConfig,
      component,
      onUpdateComponent,
      isDesignMode,
      handleRightColumnDragEnd,
      setRuntimeColumnOrder,
    ],
  );

  // 런타임 컬럼 순서 적용 헬퍼 (이름 기반)
  const applyRuntimeOrder = useCallback(
    (columns: any[], source: "main" | number): any[] => {
      const key = String(source);
      const order = runtimeColumnOrder[key];
      if (!order || order.length === 0) return columns;
      const colMap = new Map(columns.map((col) => [col.name, col]));
      const result: any[] = [];
      for (const name of order) {
        const col = colMap.get(name);
        if (col) {
          result.push(col);
          colMap.delete(name);
        }
      }
      // order에 없는 나머지 컬럼 뒤에 추가
      for (const col of colMap.values()) {
        result.push(col);
      }
      return result;
    },
    [runtimeColumnOrder],
  );

  // 수정 모달 저장
  const handleEditModalSave = useCallback(async () => {
    const tableName =
      editModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName;

    const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
    const primaryKey = editModalItem[sourceColumn] || editModalItem.id || editModalItem.ID;

    if (!tableName || !primaryKey) {
      toast({
        title: "수정 오류",
        description: "테이블명 또는 Primary Key가 없습니다.",
        variant: "destructive",
      });
      return;
    }

    try {
      // 프론트엔드 전용 필드 제거 (children, level 등)
      const cleanData = { ...editModalFormData };
      delete cleanData.children;
      delete cleanData.level;

      // 좌측 패널 수정 시, 조인 관계 정보 포함
      const updatePayload: any = cleanData;

      if (editModalPanel === "left" && componentConfig.rightPanel?.relation?.type === "join") {
        // 조인 관계가 있는 경우, 관계 정보를 페이로드에 추가
        updatePayload._relationInfo = {
          rightTable: componentConfig.rightPanel.tableName,
          leftColumn: componentConfig.rightPanel.relation.leftColumn,
          rightColumn: componentConfig.rightPanel.relation.rightColumn,
          oldLeftValue: editModalItem[componentConfig.rightPanel.relation.leftColumn],
        };
      }

      const result = await dataApi.updateRecord(tableName, primaryKey, updatePayload);

      if (result.success) {
        toast({
          title: "성공",
          description: "데이터가 성공적으로 수정되었습니다.",
        });

        // 모달 닫기
        setShowEditModal(false);
        setEditModalFormData({});
        setEditModalItem(null);

        // 데이터 새로고침
        if (editModalPanel === "left") {
          loadLeftData();
          // 우측 패널도 새로고침 (FK가 변경되었을 수 있음)
          loadRightData(selectedLeftItem);
        } else if (editModalPanel === "right") {
          loadRightData(selectedLeftItem);
        }
      } else {
        toast({
          title: "수정 실패",
          description: result.message || "데이터 수정에 실패했습니다.",
          variant: "destructive",
        });
      }
    } catch (error: any) {
      console.error("데이터 수정 오류:", error);
      toast({
        title: "오류",
        description: error?.response?.data?.message || "데이터 수정 중 오류가 발생했습니다.",
        variant: "destructive",
      });
    }
  }, [
    editModalPanel,
    componentConfig,
    editModalItem,
    editModalFormData,
    toast,
    selectedLeftItem,
    loadLeftData,
    loadRightData,
  ]);

  // 삭제 버튼 핸들러 (tableName: 추가 탭 등 특정 테이블 지정 시 사용)
  const handleDeleteClick = useCallback((panel: "left" | "right", item: any, tableName?: string) => {
    setDeleteModalPanel(panel);
    setDeleteModalItem(item);
    setDeleteModalTableName(tableName || null);
    setShowDeleteModal(true);
  }, []);

  // 삭제 확인
  const handleDeleteConfirm = useCallback(async () => {
    // 1. 테이블명 결정: deleteModalTableName이 있으면 우선 사용 (추가 탭 등)
    let tableName = deleteModalTableName;

    if (!tableName) {
      tableName =
        deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName;

      // 우측 패널 + 중계 테이블 모드인 경우
      if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) {
        tableName = componentConfig.rightPanel.addConfig.targetTable;
      }
    }

    // 2. Primary Key 추출: id 필드를 우선 사용, 없으면 전체 객체 전달 (복합키)
    let primaryKey: any = deleteModalItem?.id || deleteModalItem?.ID;

    if (!primaryKey && deleteModalItem && typeof deleteModalItem === "object") {
      // id가 없는 경우에만 전체 객체 전달 (복합키 테이블)
      primaryKey = deleteModalItem;
    }

    if (!tableName || !primaryKey) {
      toast({
        title: "삭제 오류",
        description: "테이블명 또는 Primary Key가 없습니다.",
        variant: "destructive",
      });
      return;
    }

    try {
      let result;

      // 🔧 중복 제거가 활성화된 경우, groupByColumn 기준으로 모든 관련 레코드 삭제
      if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) {
        const deduplication = componentConfig.rightPanel.dataFilter.deduplication;
        const groupByColumn = deduplication.groupByColumn;

        if (groupByColumn && deleteModalItem[groupByColumn]) {
          const groupValue = deleteModalItem[groupByColumn];

          // groupByColumn 값으로 필터링하여 삭제
          const filterConditions: Record<string, any> = {
            [groupByColumn]: groupValue,
          };

          // 좌측 패널의 선택된 항목 정보도 포함 (customer_id 등)
          if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") {
            const leftColumn = componentConfig.rightPanel.join.leftColumn;
            const rightColumn = componentConfig.rightPanel.join.rightColumn;
            filterConditions[rightColumn] = selectedLeftItem[leftColumn];
          }

          // 그룹 삭제 API 호출
          result = await dataApi.deleteGroupRecords(tableName, filterConditions);
        } else {
          // 단일 레코드 삭제
          result = await dataApi.deleteRecord(tableName, primaryKey);
        }
      } else {
        // 단일 레코드 삭제
        result = await dataApi.deleteRecord(tableName, primaryKey);
      }

      if (result.success) {
        toast({
          title: "성공",
          description: "데이터가 성공적으로 삭제되었습니다.",
        });

        // 모달 닫기
        setShowDeleteModal(false);
        setDeleteModalItem(null);
        setDeleteModalTableName(null);

        // 데이터 새로고침
        if (deleteModalPanel === "left") {
          loadLeftData();
          // 삭제된 항목이 선택되어 있었으면 선택 해제
          const deletedId = deleteModalItem?.id || deleteModalItem?.ID;
          if (selectedLeftItem && (selectedLeftItem.id === deletedId || selectedLeftItem.ID === deletedId)) {
            setSelectedLeftItem(null);
            setRightData(null);
          }
        } else if (deleteModalPanel === "right") {
          // 추가 탭에서 삭제한 경우 해당 탭 데이터 리로드
          if (deleteModalTableName && activeTabIndex > 0) {
            loadTabData(activeTabIndex, selectedLeftItem);
          } else {
            loadRightData(selectedLeftItem);
          }
        }
      } else {
        toast({
          title: "삭제 실패",
          description: result.message || "데이터 삭제에 실패했습니다.",
          variant: "destructive",
        });
      }
    } catch (error: any) {
      console.error("데이터 삭제 오류:", error);

      // 외래키 제약조건 에러 처리
      let errorMessage = "데이터 삭제 중 오류가 발생했습니다.";
      if (error?.response?.data?.error?.includes("foreign key")) {
        errorMessage = "이 데이터를 참조하는 다른 데이터가 있어 삭제할 수 없습니다.";
      }

      toast({
        title: "오류",
        description: errorMessage,
        variant: "destructive",
      });
    }
  }, [
    deleteModalPanel,
    deleteModalTableName,
    componentConfig,
    deleteModalItem,
    toast,
    selectedLeftItem,
    loadLeftData,
    loadRightData,
    loadTabData,
    activeTabIndex,
  ]);

  // 항목별 추가 버튼 핸들러 (좌측 항목의 + 버튼 - 하위 항목 추가)
  const handleItemAddClick = useCallback(
    (item: any) => {
      const itemAddConfig = componentConfig.leftPanel?.itemAddConfig;

      if (!itemAddConfig) {
        toast({
          title: "설정 오류",
          description: "하위 항목 추가 설정이 없습니다.",
          variant: "destructive",
        });
        return;
      }

      const { sourceColumn, parentColumn } = itemAddConfig;

      if (!sourceColumn || !parentColumn) {
        toast({
          title: "설정 오류",
          description: "현재 항목 ID 컬럼과 상위 항목 저장 컬럼을 설정해주세요.",
          variant: "destructive",
        });
        return;
      }

      // 선택된 항목의 sourceColumn 값을 가져와서 parentColumn에 매핑
      const sourceValue = item[sourceColumn];

      if (!sourceValue) {
        toast({
          title: "데이터 오류",
          description: `선택한 항목의 ${sourceColumn} 값이 없습니다.`,
          variant: "destructive",
        });
        return;
      }

      // 좌측 패널 추가 모달 열기 (parentColumn 값 미리 채우기)
      setAddModalPanel("left-item");
      setAddModalFormData({ [parentColumn]: sourceValue });
      setShowAddModal(true);
    },
    [componentConfig, toast],
  );

  // 추가 모달 저장
  const handleAddModalSave = useCallback(async () => {
    // 테이블명과 모달 컬럼 결정
    let tableName: string | undefined;
    let modalColumns: Array<{ name: string; label: string; required?: boolean }> | undefined;
    const finalData = { ...addModalFormData };

    if (addModalPanel === "left") {
      tableName = componentConfig.leftPanel?.tableName;
      modalColumns = componentConfig.leftPanel?.addModalColumns;
    } else if (addModalPanel === "right") {
      // 우측 패널: 중계 테이블 설정이 있는지 확인
      const addConfig = componentConfig.rightPanel?.addConfig;

      if (addConfig?.targetTable) {
        // 중계 테이블 모드
        tableName = addConfig.targetTable;
        modalColumns = componentConfig.rightPanel?.addModalColumns;

        // 좌측 패널에서 선택된 값 자동 채우기
        if (addConfig.leftPanelColumn && addConfig.targetColumn && selectedLeftItem) {
          const leftValue = selectedLeftItem[addConfig.leftPanelColumn];
          finalData[addConfig.targetColumn] = leftValue;
        }

        // 자동 채움 컬럼 추가
        if (addConfig.autoFillColumns) {
          Object.entries(addConfig.autoFillColumns).forEach(([key, value]) => {
            finalData[key] = value;
          });
        }
      } else {
        // 일반 테이블 모드
        tableName = componentConfig.rightPanel?.tableName;
        modalColumns = componentConfig.rightPanel?.addModalColumns;
      }
    } else if (addModalPanel === "left-item") {
      // 하위 항목 추가 (좌측 테이블에 추가)
      tableName = componentConfig.leftPanel?.tableName;
      modalColumns = componentConfig.leftPanel?.itemAddConfig?.addModalColumns;
    }

    if (!tableName) {
      toast({
        title: "테이블 오류",
        description: "테이블명이 설정되지 않았습니다.",
        variant: "destructive",
      });
      return;
    }

    // 필수 필드 검증
    const requiredFields = (modalColumns || []).filter((col) => col.required);
    for (const field of requiredFields) {
      if (!addModalFormData[field.name]) {
        toast({
          title: "입력 오류",
          description: `${field.label}은(는) 필수 입력 항목입니다.`,
          variant: "destructive",
        });
        return;
      }
    }

    try {
      const result = await dataApi.createRecord(tableName, finalData);

      if (result.success) {
        toast({
          title: "성공",
          description: "데이터가 성공적으로 추가되었습니다.",
        });

        // 모달 닫기
        setShowAddModal(false);
        setAddModalFormData({});

        // 데이터 새로고침
        if (addModalPanel === "left" || addModalPanel === "left-item") {
          // 좌측 패널 데이터 새로고침 (일반 추가 또는 하위 항목 추가)
          loadLeftData();
        } else if (addModalPanel === "right") {
          // 우측 패널 데이터 새로고침
          loadRightData(selectedLeftItem);
        }
      } else {
        toast({
          title: "저장 실패",
          description: result.message || "데이터 추가에 실패했습니다.",
          variant: "destructive",
        });
      }
    } catch (error: any) {
      console.error("데이터 추가 오류:", error);

      // 에러 메시지 추출
      let errorMessage = "데이터 추가 중 오류가 발생했습니다.";

      if (error?.response?.data) {
        const responseData = error.response.data;

        // 백엔드에서 반환한 에러 메시지 확인
        if (responseData.error) {
          // 중복 키 에러 처리
          if (responseData.error.includes("duplicate key")) {
            errorMessage = "이미 존재하는 값입니다. 다른 값을 입력해주세요.";
          }
          // NOT NULL 제약조건 에러
          else if (responseData.error.includes("null value")) {
            const match = responseData.error.match(/column "(\w+)"/);
            const columnName = match ? match[1] : "필수";
            errorMessage = `${columnName} 필드는 필수 입력 항목입니다.`;
          }
          // 외래키 제약조건 에러
          else if (responseData.error.includes("foreign key")) {
            errorMessage = "참조하는 데이터가 존재하지 않습니다.";
          }
          // 기타 에러
          else {
            errorMessage = responseData.message || responseData.error;
          }
        } else if (responseData.message) {
          errorMessage = responseData.message;
        }
      }

      toast({
        title: "오류",
        description: errorMessage,
        variant: "destructive",
      });
    }
  }, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]);

  // 🔧 좌측 컬럼 가시성 설정 저장 및 불러오기
  useEffect(() => {
    const leftTableName = componentConfig.leftPanel?.tableName;
    if (leftTableName && currentUserId) {
      // localStorage에서 저장된 설정 불러오기
      const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`;
      const savedSettings = localStorage.getItem(storageKey);

      if (savedSettings) {
        try {
          const parsed = JSON.parse(savedSettings) as ColumnVisibility[];
          setLeftColumnVisibility(parsed);
        } catch (error) {
          console.error("저장된 컬럼 설정 불러오기 실패:", error);
        }
      }
    }
  }, [componentConfig.leftPanel?.tableName, currentUserId]);

  // 🔧 컬럼 가시성 변경 시 localStorage에 저장 및 순서 업데이트
  useEffect(() => {
    const leftTableName = componentConfig.leftPanel?.tableName;

    if (leftColumnVisibility.length > 0 && leftTableName && currentUserId) {
      // 순서 업데이트
      const newOrder = leftColumnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // 체크박스 제외

      setLeftColumnOrder(newOrder);

      // localStorage에 저장
      const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`;
      localStorage.setItem(storageKey, JSON.stringify(leftColumnVisibility));
    }
  }, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]);

  // 초기 데이터 로드 (좌측 + 우측 전체 데이터)
  useEffect(() => {
    if (!isDesignMode && componentConfig.autoLoad !== false) {
      loadLeftData();
      // 좌측 미선택 상태에서 우측 전체 데이터 기본 로드
      // join 모드: 초기 전체 로드 / detail 모드: 초기 로드 안 함
      const relationshipType = componentConfig.rightPanel?.relation?.type || "detail";
      if (relationshipType === "join") {
        loadRightData(null);
      }
      // 추가 탭: 메인 패널이 "detail"(선택 시 표시)이면 추가 탭도 초기 로드하지 않음
      if (relationshipType !== "detail") {
        const tabs = componentConfig.rightPanel?.additionalTabs;
        if (tabs && tabs.length > 0) {
          tabs.forEach((tab: any, idx: number) => {
            const tabRelType = tab.relation?.type || "join";
            if (tabRelType === "join") {
              loadTabData(idx + 1, null);
            }
          });
        }
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDesignMode, componentConfig.autoLoad]);

  // config에서 pageSize 변경 시 상태 동기화 + 페이지 리셋
  useEffect(() => {
    const configLeftPageSize = componentConfig.leftPanel?.pagination?.pageSize ?? 20;
    setLeftPageSize(configLeftPageSize);
    setLeftCurrentPage(1);
    setLeftPageInput("1");
  }, [componentConfig.leftPanel?.pagination?.pageSize]);

  useEffect(() => {
    const configRightPageSize = componentConfig.rightPanel?.pagination?.pageSize ?? 20;
    setRightPageSize(configRightPageSize);
    setRightCurrentPage(1);
    setRightPageInput("1");
    setTabsPagination({});
  }, [componentConfig.rightPanel?.pagination?.pageSize]);

  // 🔄 필터 변경 시 데이터 다시 로드 (페이지 1로 리셋)
  useEffect(() => {
    if (!isDesignMode && componentConfig.autoLoad !== false) {
      if (leftPaginationEnabled) {
        setLeftCurrentPage(1);
        setLeftPageInput("1");
      }
      loadLeftData(1);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [leftFilters]);

  // 전역 테이블 새로고침 이벤트 리스너
  useEffect(() => {
    const handleRefreshTable = () => {
      if (!isDesignMode) {
        loadLeftData();
        // 현재 활성 탭 데이터 새로고침 (좌측 미선택 시에도 전체 데이터 로드)
        if (activeTabIndex === 0) {
          loadRightData(selectedLeftItem);
        } else {
          loadTabData(activeTabIndex, selectedLeftItem);
        }
      }
    };

    window.addEventListener("refreshTable", handleRefreshTable);

    return () => {
      window.removeEventListener("refreshTable", handleRefreshTable);
    };
  }, [isDesignMode, loadLeftData, loadRightData, loadTabData, activeTabIndex, selectedLeftItem]);

  // 리사이저 드래그 핸들러
  const handleMouseDown = (e: React.MouseEvent) => {
    if (!resizable) return;
    setIsDragging(true);
    e.preventDefault();
  };

  const handleMouseMove = useCallback(
    (e: MouseEvent) => {
      if (!isDragging || !containerRef.current) return;

      const containerRect = containerRef.current.getBoundingClientRect();
      const containerWidth = containerRect.width;
      const relativeX = e.clientX - containerRect.left;
      const newLeftWidth = (relativeX / containerWidth) * 100;

      // 최소/최대 너비 제한 (20% ~ 80%)
      if (newLeftWidth >= 20 && newLeftWidth <= 80) {
        setLeftWidth(newLeftWidth);
      }
    },
    [isDragging],
  );

  const handleMouseUp = useCallback(() => {
    setIsDragging(false);
  }, []);

  React.useEffect(() => {
    if (isDragging) {
      // 드래그 중에는 텍스트 선택 방지
      document.body.style.userSelect = "none";
      document.body.style.cursor = "col-resize";

      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);

      return () => {
        document.body.style.userSelect = "";
        document.body.style.cursor = "";
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
      };
    }
  }, [isDragging, handleMouseMove, handleMouseUp]);

  return (
    <div
      ref={containerRef}
      style={{
        ...componentStyle,
        display: "flex",
        flexDirection: "row",
      }}
      onClick={(e) => {
        if (isDesignMode) {
          e.stopPropagation();
          onClick?.(e);
        }
      }}
      className="w-full overflow-hidden rounded-lg bg-white shadow-sm"
    >
      {/* 좌측 패널 */}
      <div
        style={{ width: `${leftWidth}%`, minWidth: isDesignMode ? `${minLeftWidth}px` : "0", height: "100%" }}
        className="border-border flex flex-col border-r"
      >
        <Card className="flex flex-col border-0 shadow-none" style={{ height: "100%" }}>
          <CardHeader
            className="flex-shrink-0 border-b"
            style={{
              height: componentConfig.leftPanel?.panelHeaderHeight || 48,
              minHeight: componentConfig.leftPanel?.panelHeaderHeight || 48,
              padding: "0 1rem",
              display: "flex",
              alignItems: "center",
            }}
          >
            <div className="flex w-full items-center justify-between gap-2">
              <div className="flex min-w-0 flex-1 items-center gap-2">
                <List className="text-muted-foreground h-4 w-4 shrink-0" />
                <CardTitle className="truncate text-base font-semibold">
                  {componentConfig.leftPanel?.title || "좌측 패널"}
                </CardTitle>
                {!isDesignMode && (
                  <Badge variant="secondary" className="shrink-0 text-xs">
                    {summedLeftData.length}
                  </Badge>
                )}
              </div>
              <div className="flex items-center gap-1">
                {!isDesignMode && (componentConfig.leftPanel as any)?.showBomExcelUpload && (
                  <Button size="sm" variant="outline" onClick={() => setBomExcelUploadOpen(true)}>
                    <FileSpreadsheet className="mr-1 h-4 w-4" />
                    엑셀
                  </Button>
                )}
                {!isDesignMode && componentConfig.leftPanel?.showAdd && (
                  <Button size="sm" variant="outline" onClick={() => handleAddClick("left")}>
                    <Plus className="mr-1 h-4 w-4" />
                    추가
                  </Button>
                )}
              </div>
            </div>
          </CardHeader>
          {componentConfig.leftPanel?.showSearch && (
            <div className="flex-shrink-0 border-b p-2">
              <div className="relative">
                <Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
                <Input
                  placeholder="검색..."
                  value={leftSearchQuery}
                  onChange={(e) => setLeftSearchQuery(e.target.value)}
                  className="pl-9"
                />
              </div>
            </div>
          )}
          <CardContent
            className="flex-1 overflow-auto p-4"
            {...(isDesignMode
              ? {
                  "data-split-panel-container": "true",
                  "data-component-id": component.id,
                  "data-panel-side": "left",
                }
              : {})}
          >
            {/* 좌측 데이터 목록/테이블/커스텀 */}
            {componentConfig.leftPanel?.displayMode === "custom" ? (
              // 🆕 커스텀 모드: 패널 안에 자유롭게 컴포넌트 배치
              <div
                className="relative h-full w-full"
                data-split-panel-container="true"
                data-component-id={component.id}
                data-panel-side="left"
              >
                {/* 커스텀 모드: 디자인/실행 모드 분기 렌더링 */}
                {componentConfig.leftPanel?.components && componentConfig.leftPanel.components.length > 0 ? (
                  !isDesignMode ? (
                    <ScaledCustomPanel
                      components={componentConfig.leftPanel!.components}
                      formData={{}}
                      onFormDataChange={(data: any) => {
                        if (data?.selectedRowsData && data.selectedRowsData.length > 0) {
                          setCustomLeftSelectedData(data.selectedRowsData[0]);
                          setSelectedLeftItem(data.selectedRowsData[0]);
                        } else if (data?.selectedRowsData && data.selectedRowsData.length === 0) {
                          setCustomLeftSelectedData({});
                          setSelectedLeftItem(null);
                        }
                      }}
                      tableName={componentConfig.leftPanel?.tableName}
                      menuObjid={(props as any).menuObjid}
                      screenId={(props as any).screenId}
                      userId={(props as any).userId}
                      userName={(props as any).userName}
                      companyCode={companyCode}
                      allComponents={(props as any).allComponents}
                      selectedRowsData={localSelectedRowsData}
                      onSelectedRowsChange={handleLocalSelectedRowsChange}
                    />
                  ) : (
                    <div className="relative h-full w-full" style={{ minHeight: "100%", minWidth: "100%" }}>
                      {componentConfig.leftPanel.components.map((comp: PanelInlineComponent) => {
                        const isSelectedComp = selectedPanelComponentId === comp.id;
                        const isDraggingComp = draggingCompId === comp.id;
                        const isResizingComp = resizingCompId === comp.id;

                        const displayX = isDraggingComp && dragPosition ? dragPosition.x : comp.position?.x || 0;
                        const displayY = isDraggingComp && dragPosition ? dragPosition.y : comp.position?.y || 0;
                        const displayWidth = isResizingComp && resizeSize ? resizeSize.width : comp.size?.width || 200;
                        const displayHeight =
                          isResizingComp && resizeSize ? resizeSize.height : comp.size?.height || 100;

                        const componentData = {
                          id: comp.id,
                          type: "component" as const,
                          componentType: comp.componentType,
                          label: comp.label,
                          position: comp.position || { x: 0, y: 0 },
                          size: { width: displayWidth, height: displayHeight },
                          componentConfig: comp.componentConfig || {},
                          style: comp.style || {},
                          tableName: comp.componentConfig?.tableName,
                          columnName: comp.componentConfig?.columnName,
                          webType: comp.componentConfig?.webType,
                          inputType: (comp as any).inputType || comp.componentConfig?.inputType,
                        };

                        return (
                          <div
                            key={comp.id}
                            data-panel-comp-id={comp.id}
                            className="absolute"
                            style={{
                              left: displayX,
                              top: displayY,
                              zIndex: isDraggingComp ? 100 : isSelectedComp ? 10 : 1,
                            }}
                            onClick={(e) => {
                              e.stopPropagation();
                              if (comp.componentType !== "v2-tabs-widget") {
                                setNestedTabSelectedCompId(undefined);
                              }
                              setInternalSelectedCompId(comp.id);
                              onSelectPanelComponent?.("left", comp.id, comp);
                            }}
                          >
                            {/* 드래그 핸들 - 컴포넌트 외부 상단 */}
                            <div
                              className={cn(
                                "bg-muted flex h-4 cursor-move items-center justify-between rounded-t border border-b-0 px-1",
                                isSelectedComp ? "border-primary" : "border-border",
                              )}
                              style={{ width: displayWidth }}
                              onMouseDown={(e) => handlePanelDragStart(e, "left", comp)}
                            >
                              <div className="flex items-center gap-0.5">
                                <Move className="text-muted-foreground/70 h-2.5 w-2.5" />
                                <span className="text-muted-foreground max-w-[100px] truncate text-[9px]">
                                  {comp.label || comp.componentType}
                                </span>
                              </div>
                              <div className="flex items-center">
                                <button
                                  className="hover:bg-muted/80 rounded p-0.5"
                                  onClick={(e) => {
                                    e.stopPropagation();
                                    onSelectPanelComponent?.("left", comp.id, comp);
                                  }}
                                  title="설정"
                                >
                                  <Settings className="text-muted-foreground h-2.5 w-2.5" />
                                </button>
                                <button
                                  className="hover:bg-destructive/10 rounded p-0.5"
                                  onClick={(e) => {
                                    e.stopPropagation();
                                    handleRemovePanelComponent("left", comp.id);
                                  }}
                                  title="삭제"
                                >
                                  <Trash2 className="text-destructive h-2.5 w-2.5" />
                                </button>
                              </div>
                            </div>

                            {/* 실제 컴포넌트 렌더링 - 핸들 아래에 별도 영역 */}
                            <div
                              className={cn(
                                "relative overflow-hidden rounded-b border bg-white shadow-sm",
                                isSelectedComp ? "border-primary ring-primary/30 ring-2" : "border-border",
                                (isDraggingComp || isResizingComp) && "opacity-80 shadow-lg",
                                !(isDraggingComp || isResizingComp) && "transition-all",
                              )}
                              style={{
                                width: displayWidth,
                                height: displayHeight,
                              }}
                            >
                              {/* 🆕 컨테이너 컴포넌트(탭, 분할 패널)는 드롭 이벤트를 받을 수 있어야 함 */}
                              <div
                                className={cn(
                                  "h-full w-full",
                                  // 탭/분할 패널 같은 컨테이너 컴포넌트는 pointer-events 활성화
                                  comp.componentType === "v2-tabs-widget" ||
                                    comp.componentType === "tabs-widget" ||
                                    comp.componentType === "v2-split-panel-layout" ||
                                    comp.componentType === "split-panel-layout"
                                    ? ""
                                    : "pointer-events-none",
                                )}
                              >
                                <DynamicComponentRenderer
                                  component={componentData as any}
                                  isDesignMode={true}
                                  formData={{}}
                                  // 🆕 중첩된 컴포넌트 업데이트 핸들러 전달
                                  onUpdateComponent={(updatedComp: any) => {
                                    handleNestedComponentUpdate("left", comp.id, updatedComp);
                                  }}
                                  // 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
                                  onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
                                    // 탭 내 컴포넌트 선택 상태 업데이트
                                    setNestedTabSelectedCompId(compId);
                                    // 부모 분할 패널 정보와 함께 전역 이벤트 발생
                                    const event = new CustomEvent("nested-tab-component-select", {
                                      detail: {
                                        tabsComponentId: comp.id,
                                        tabId,
                                        componentId: compId,
                                        component: tabComp,
                                        parentSplitPanelId: component.id,
                                        parentPanelSide: "left",
                                      },
                                    });
                                    window.dispatchEvent(event);
                                  }}
                                  selectedTabComponentId={nestedTabSelectedCompId}
                                />
                              </div>

                              {/* 리사이즈 가장자리 영역 - 선택된 컴포넌트에만 표시 */}
                              {isSelectedComp && (
                                <>
                                  {/* 오른쪽 가장자리 (너비 조절) */}
                                  <div
                                    className="hover:bg-primary/10 pointer-events-auto absolute top-0 right-0 z-10 h-full w-2 cursor-ew-resize"
                                    onMouseDown={(e) => handlePanelResizeStart(e, "left", comp, "e")}
                                  />
                                  {/* 아래 가장자리 (높이 조절) */}
                                  <div
                                    className="hover:bg-primary/10 pointer-events-auto absolute bottom-0 left-0 z-10 h-2 w-full cursor-ns-resize"
                                    onMouseDown={(e) => handlePanelResizeStart(e, "left", comp, "s")}
                                  />
                                  {/* 오른쪽 아래 모서리 (너비+높이 조절) */}
                                  <div
                                    className="hover:bg-primary/20 pointer-events-auto absolute right-0 bottom-0 z-20 h-3 w-3 cursor-nwse-resize"
                                    onMouseDown={(e) => handlePanelResizeStart(e, "left", comp, "se")}
                                  />
                                </>
                              )}
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  )
                ) : (
                  // 컴포넌트가 없을 때 드롭 영역 표시
                  <div className="border-input bg-muted/50 flex h-full w-full flex-col items-center justify-center rounded border-2 border-dashed">
                    <Plus className="text-muted-foreground/70 mb-2 h-8 w-8" />
                    <p className="text-muted-foreground text-sm font-medium">커스텀 모드</p>
                    <p className="text-muted-foreground/70 mt-1 text-xs">
                      {isDesignMode ? "컴포넌트를 드래그하여 배치하세요" : "배치된 컴포넌트가 없습니다"}
                    </p>
                  </div>
                )}
              </div>
            ) : componentConfig.leftPanel?.displayMode === "table" ? (
              // 테이블 모드
              <div className="w-full">
                {isDesignMode ? (
                  // 디자인 모드: 샘플 테이블
                  <div className="overflow-auto">
                    <table className="divide-border min-w-full divide-y">
                      <thead className="bg-muted">
                        <tr>
                          <th className="text-muted-foreground px-3 py-2 text-left text-xs font-medium uppercase">
                            컬럼 1
                          </th>
                          <th className="text-muted-foreground px-3 py-2 text-left text-xs font-medium uppercase">
                            컬럼 2
                          </th>
                          <th className="text-muted-foreground px-3 py-2 text-left text-xs font-medium uppercase">
                            컬럼 3
                          </th>
                        </tr>
                      </thead>
                      <tbody className="divide-border divide-y bg-white">
                        <tr className="hover:bg-muted cursor-pointer">
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 1-1</td>
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 1-2</td>
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 1-3</td>
                        </tr>
                        <tr className="hover:bg-muted cursor-pointer">
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 2-1</td>
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 2-2</td>
                          <td className="px-3 py-2 text-sm whitespace-nowrap">데이터 2-3</td>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                ) : isLoadingLeft ? (
                  <div className="flex items-center justify-center py-8">
                    <Loader2 className="text-primary h-6 w-6 animate-spin" />
                    <span className="text-muted-foreground ml-2 text-sm">데이터를 불러오는 중...</span>
                  </div>
                ) : (
                  (() => {
                    // 🆕 그룹별 합산된 데이터 사용
                    const dataSource = summedLeftData;

                    // 🔧 로컬 검색 필터 적용
                    const filteredData = leftSearchQuery
                      ? dataSource.filter((item) => {
                          const searchLower = leftSearchQuery.toLowerCase();
                          return Object.entries(item).some(([key, value]) => {
                            if (value === null || value === undefined) return false;
                            return String(value).toLowerCase().includes(searchLower);
                          });
                        })
                      : dataSource;

                    // 🔧 가시성 처리된 컬럼 사용
                    const columnsToShow =
                      visibleLeftColumns.length > 0
                        ? visibleLeftColumns.map((col: any) => {
                            const colName = typeof col === "string" ? col : col.name || col.columnName;
                            return {
                              name: colName,
                              label:
                                leftColumnLabels[colName] || (typeof col === "object" ? col.label : null) || colName,
                              width: typeof col === "object" ? col.width : 150,
                              align: (typeof col === "object" ? col.align : "left") as "left" | "center" | "right",
                              format: typeof col === "object" ? col.format : undefined, // 🆕 포맷 설정 포함
                            };
                          })
                        : Object.keys(filteredData[0] || {})
                            .filter((key) => key !== "children" && key !== "level")
                            .slice(0, 5)
                            .map((key) => ({
                              name: key,
                              label: leftColumnLabels[key] || key,
                              width: 150,
                              align: "left" as const,
                              format: undefined, // 🆕 기본값
                            }));

                    // 🔧 그룹화된 데이터 렌더링
                    const hasGroupedLeftActions =
                      !isDesignMode &&
                      (componentConfig.leftPanel?.showEdit !== false ||
                        componentConfig.leftPanel?.showDelete !== false);
                    const canDragLeftGroupedColumns = !isDesignMode && columnsToShow.length > 1;
                    if (groupedLeftData.length > 0) {
                      return (
                        <div className="overflow-auto">
                          {groupedLeftData.map((group, groupIdx) => (
                            <div key={groupIdx} className="mb-4">
                              <div className="bg-muted px-3 py-2 text-sm font-semibold">
                                {group.groupKey} ({group.count}개)
                              </div>
                              <table className="divide-border min-w-full divide-y">
                                <thead className="bg-muted">
                                  <tr>
                                    {columnsToShow.map((col, idx) => {
                                      const isDropTarget =
                                        canDragLeftGroupedColumns && leftDropTargetColumnIndex === idx;
                                      const isDragging = canDragLeftGroupedColumns && leftDraggedColumnIndex === idx;
                                      return (
                                        <th
                                          key={idx}
                                          className={cn(
                                            "group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase",
                                            isDropTarget && "border-l-primary bg-primary/5 border-l-[3px]",
                                            canDragLeftGroupedColumns && "cursor-grab active:cursor-grabbing",
                                            isDragging && "opacity-50",
                                          )}
                                          style={{
                                            minWidth: col.width ? `${col.width}px` : "80px",
                                            textAlign: col.align || "left",
                                          }}
                                          draggable={canDragLeftGroupedColumns}
                                          onDragStart={(e) =>
                                            canDragLeftGroupedColumns && handleLeftColumnDragStart(e, idx)
                                          }
                                          onDragOver={(e) =>
                                            canDragLeftGroupedColumns && handleLeftColumnDragOver(e, idx)
                                          }
                                          onDragEnd={handleLeftColumnDragEnd}
                                          onDrop={(e) => canDragLeftGroupedColumns && handleLeftColumnDrop(e, idx)}
                                        >
                                          {canDragLeftGroupedColumns && (
                                            <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />
                                          )}
                                          {col.label}
                                        </th>
                                      );
                                    })}
                                    {hasGroupedLeftActions && (
                                      <th
                                        className="bg-muted text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase"
                                        style={{ width: "80px" }}
                                      ></th>
                                    )}
                                  </tr>
                                </thead>
                                <tbody className="divide-border divide-y bg-white">
                                  {group.items.map((item, idx) => {
                                    const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
                                    const itemId = item[sourceColumn] || item.id || item.ID || idx;
                                    const isSelected =
                                      selectedLeftItem &&
                                      (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item);

                                    return (
                                      <tr
                                        key={itemId}
                                        onClick={() => handleLeftItemSelect(item)}
                                        className={`group hover:bg-accent cursor-pointer transition-colors ${
                                          isSelected ? "bg-primary/10" : ""
                                        }`}
                                      >
                                        {columnsToShow.map((col, colIdx) => (
                                          <td
                                            key={colIdx}
                                            className="text-foreground px-3 py-2 text-sm whitespace-nowrap"
                                            style={{
                                              textAlign: col.align || "left",
                                              overflow: "hidden",
                                              textOverflow: "ellipsis",
                                            }}
                                          >
                                            {formatCellValue(
                                              col.name,
                                              getEntityJoinValue(item, col.name),
                                              leftCategoryMappings,
                                              col.format,
                                            )}
                                          </td>
                                        ))}
                                        {hasGroupedLeftActions && (
                                          <td className="bg-card group-hover:bg-accent sticky right-0 z-10 px-3 py-2 text-right">
                                            <div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
                                              {componentConfig.leftPanel?.showEdit !== false && (
                                                <button
                                                  onClick={(e) => {
                                                    e.stopPropagation();
                                                    handleEditClick("left", item);
                                                  }}
                                                  className="hover:bg-muted/80 rounded p-1 transition-colors"
                                                >
                                                  <Pencil className="text-muted-foreground h-3.5 w-3.5" />
                                                </button>
                                              )}
                                              {componentConfig.leftPanel?.showDelete !== false && (
                                                <button
                                                  onClick={(e) => {
                                                    e.stopPropagation();
                                                    handleDeleteClick("left", item);
                                                  }}
                                                  className="hover:bg-destructive/10 rounded p-1 transition-colors"
                                                >
                                                  <Trash2 className="text-destructive h-3.5 w-3.5" />
                                                </button>
                                              )}
                                            </div>
                                          </td>
                                        )}
                                      </tr>
                                    );
                                  })}
                                </tbody>
                              </table>
                            </div>
                          ))}
                        </div>
                      );
                    }

                    // 🔧 일반 테이블 렌더링 (그룹화 없음)
                    const hasLeftTableActions =
                      !isDesignMode &&
                      (componentConfig.leftPanel?.showEdit !== false ||
                        componentConfig.leftPanel?.showDelete !== false);
                    const canDragLeftColumns = !isDesignMode && columnsToShow.length > 1;
                    return (
                      <div className="overflow-auto">
                        <table className="divide-border min-w-full divide-y">
                          <thead className="bg-muted sticky top-0 z-10">
                            <tr>
                              {columnsToShow.map((col, idx) => {
                                const isDropTarget = canDragLeftColumns && leftDropTargetColumnIndex === idx;
                                const isDragging = canDragLeftColumns && leftDraggedColumnIndex === idx;
                                return (
                                  <th
                                    key={idx}
                                    className={cn(
                                      "group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase",
                                      isDropTarget && "border-l-primary bg-primary/5 border-l-[3px]",
                                      canDragLeftColumns && "cursor-grab active:cursor-grabbing",
                                      isDragging && "opacity-50",
                                    )}
                                    style={{
                                      minWidth: col.width ? `${col.width}px` : "80px",
                                      textAlign: col.align || "left",
                                    }}
                                    draggable={canDragLeftColumns}
                                    onDragStart={(e) => canDragLeftColumns && handleLeftColumnDragStart(e, idx)}
                                    onDragOver={(e) => canDragLeftColumns && handleLeftColumnDragOver(e, idx)}
                                    onDragEnd={handleLeftColumnDragEnd}
                                    onDrop={(e) => canDragLeftColumns && handleLeftColumnDrop(e, idx)}
                                  >
                                    {canDragLeftColumns && (
                                      <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />
                                    )}
                                    {col.label}
                                  </th>
                                );
                              })}
                              {hasLeftTableActions && (
                                <th
                                  className="bg-muted text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase"
                                  style={{ width: "80px" }}
                                ></th>
                              )}
                            </tr>
                          </thead>
                          <tbody className="divide-border divide-y bg-white">
                            {filteredData.map((item, idx) => {
                              const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
                              const itemId = item[sourceColumn] || item.id || item.ID || idx;
                              const isSelected =
                                selectedLeftItem &&
                                (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item);

                              return (
                                <tr
                                  key={itemId}
                                  onClick={() => handleLeftItemSelect(item)}
                                  className={`group hover:bg-accent cursor-pointer transition-colors ${
                                    isSelected ? "bg-primary/10" : ""
                                  }`}
                                >
                                  {columnsToShow.map((col, colIdx) => (
                                    <td
                                      key={colIdx}
                                      className="text-foreground px-3 py-2 text-sm whitespace-nowrap"
                                      style={{
                                        textAlign: col.align || "left",
                                        overflow: "hidden",
                                        textOverflow: "ellipsis",
                                      }}
                                    >
                                      {formatCellValue(
                                        col.name,
                                        getEntityJoinValue(item, col.name),
                                        leftCategoryMappings,
                                        col.format,
                                      )}
                                    </td>
                                  ))}
                                  {hasLeftTableActions && (
                                    <td className="bg-card group-hover:bg-accent sticky right-0 z-10 px-3 py-2 text-right">
                                      <div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
                                        {componentConfig.leftPanel?.showEdit !== false && (
                                          <button
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleEditClick("left", item);
                                            }}
                                            className="hover:bg-muted/80 rounded p-1 transition-colors"
                                          >
                                            <Pencil className="text-muted-foreground h-3.5 w-3.5" />
                                          </button>
                                        )}
                                        {componentConfig.leftPanel?.showDelete !== false && (
                                          <button
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleDeleteClick("left", item);
                                            }}
                                            className="hover:bg-destructive/10 rounded p-1 transition-colors"
                                          >
                                            <Trash2 className="text-destructive h-3.5 w-3.5" />
                                          </button>
                                        )}
                                      </div>
                                    </td>
                                  )}
                                </tr>
                              );
                            })}
                          </tbody>
                        </table>
                      </div>
                    );
                  })()
                )}
              </div>
            ) : (
              // 목록 모드 (기존)
              <div className="space-y-1">
                {isDesignMode ? (
                  // 디자인 모드: 샘플 데이터
                  <>
                    <div
                      onClick={() => handleLeftItemSelect({ id: 1, name: "항목 1" })}
                      className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
                        selectedLeftItem?.id === 1 ? "bg-primary/10 text-primary" : ""
                      }`}
                    >
                      <div className="font-medium">항목 1</div>
                      <div className="text-muted-foreground text-xs">설명 텍스트</div>
                    </div>
                    <div
                      onClick={() => handleLeftItemSelect({ id: 2, name: "항목 2" })}
                      className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
                        selectedLeftItem?.id === 2 ? "bg-primary/10 text-primary" : ""
                      }`}
                    >
                      <div className="font-medium">항목 2</div>
                      <div className="text-muted-foreground text-xs">설명 텍스트</div>
                    </div>
                    <div
                      onClick={() => handleLeftItemSelect({ id: 3, name: "항목 3" })}
                      className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
                        selectedLeftItem?.id === 3 ? "bg-primary/10 text-primary" : ""
                      }`}
                    >
                      <div className="font-medium">항목 3</div>
                      <div className="text-muted-foreground text-xs">설명 텍스트</div>
                    </div>
                  </>
                ) : isLoadingLeft ? (
                  // 로딩 중
                  <div className="flex items-center justify-center py-8">
                    <Loader2 className="text-primary h-6 w-6 animate-spin" />
                    <span className="text-muted-foreground ml-2 text-sm">데이터를 불러오는 중...</span>
                  </div>
                ) : (
                  (() => {
                    // 🆕 그룹별 합산된 데이터 사용
                    const dataToDisplay = summedLeftData;

                    // 검색 필터링 (클라이언트 사이드)
                    const filteredLeftData = leftSearchQuery
                      ? dataToDisplay.filter((item) => {
                          const searchLower = leftSearchQuery.toLowerCase();
                          return Object.entries(item).some(([key, value]) => {
                            if (value === null || value === undefined) return false;
                            return String(value).toLowerCase().includes(searchLower);
                          });
                        })
                      : dataToDisplay;

                    // 재귀 렌더링 함수
                    const renderTreeItem = (item: any, index: number): React.ReactNode => {
                      const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
                      const itemId = item[sourceColumn] || item.id || item.ID || index;
                      const isSelected =
                        selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item);
                      const hasChildren = item.children && item.children.length > 0;
                      const isExpanded = expandedItems.has(itemId);
                      const level = item.level || 0;

                      // 🔧 수정: "표시할 컬럼 선택"에서 설정한 컬럼을 우선 사용
                      const configuredColumns = componentConfig.leftPanel?.columns || [];
                      let displayFields: { label: string; value: any }[] = [];

                      if (configuredColumns.length > 0) {
                        // 🔧 "표시할 컬럼 선택"에서 설정한 컬럼 사용
                        displayFields = configuredColumns.slice(0, 2).map((col: any) => {
                          const colName = typeof col === "string" ? col : col.name || col.columnName;
                          const colLabel = typeof col === "object" ? col.label : leftColumnLabels[colName] || colName;
                          const rawValue = getEntityJoinValue(item, colName);
                          // 카테고리 매핑이 있으면 라벨로 변환
                          let displayValue = rawValue;
                          if (rawValue != null && rawValue !== "") {
                            const strVal = String(rawValue);
                            let mapping = leftCategoryMappings[colName];
                            if (!mapping && colName.includes(".")) {
                              mapping = leftCategoryMappings[colName.split(".").pop() || colName];
                            }
                            if (mapping && mapping[strVal]) {
                              displayValue = mapping[strVal].label;
                            }
                          }
                          return {
                            label: colLabel,
                            value: displayValue,
                          };
                        });
                      } else {
                        // 설정된 컬럼이 없으면 자동으로 첫 2개 필드 표시
                        const keys = Object.keys(item).filter(
                          (k) => k !== "id" && k !== "ID" && k !== "children" && k !== "level" && shouldShowField(k),
                        );
                        displayFields = keys.slice(0, 2).map((key) => {
                          const rawValue = item[key];
                          let displayValue = rawValue;
                          if (rawValue != null && rawValue !== "") {
                            const strVal = String(rawValue);
                            const mapping = leftCategoryMappings[key];
                            if (mapping && mapping[strVal]) {
                              displayValue = mapping[strVal].label;
                            }
                          }
                          return {
                            label: leftColumnLabels[key] || key,
                            value: displayValue,
                          };
                        });
                      }

                      const displayTitle = displayFields[0]?.value || item.name || item.title || `항목 ${index + 1}`;
                      const displaySubtitle = displayFields[1]?.value || null;

                      return (
                        <React.Fragment key={itemId}>
                          {/* 현재 항목 */}
                          <div
                            className={`group hover:bg-muted relative cursor-pointer rounded-md p-3 transition-colors ${
                              isSelected ? "bg-primary/10 text-primary" : "text-foreground"
                            }`}
                            style={{ paddingLeft: `${12 + level * 24}px` }}
                          >
                            <div
                              className="flex items-center gap-2"
                              onClick={() => {
                                handleLeftItemSelect(item);
                                if (hasChildren) {
                                  toggleExpand(itemId);
                                }
                              }}
                            >
                              {/* 펼치기/접기 아이콘 */}
                              {hasChildren ? (
                                <div className="flex-shrink-0">
                                  {isExpanded ? (
                                    <ChevronDown className="text-muted-foreground h-4 w-4" />
                                  ) : (
                                    <ChevronRight className="text-muted-foreground h-4 w-4" />
                                  )}
                                </div>
                              ) : (
                                <div className="w-5" />
                              )}

                              {/* 항목 내용 */}
                              <div className="min-w-0 flex-1">
                                <div className="truncate font-medium">{displayTitle}</div>
                                {displaySubtitle && (
                                  <div className="text-muted-foreground truncate text-xs">{displaySubtitle}</div>
                                )}
                              </div>

                              {/* 항목별 버튼들 */}
                              {!isDesignMode && (
                                <div className="flex flex-shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
                                  {/* 수정 버튼 (showEdit 활성화 시에만 표시) */}
                                  {componentConfig.leftPanel?.showEdit !== false && (
                                    <button
                                      onClick={(e) => {
                                        e.stopPropagation();
                                        handleEditClick("left", item);
                                      }}
                                      className="hover:bg-muted/80 rounded p-1 transition-colors"
                                      title="수정"
                                    >
                                      <Pencil className="text-muted-foreground h-4 w-4" />
                                    </button>
                                  )}

                                  {/* 삭제 버튼 (showDelete 활성화 시에만 표시) */}
                                  {componentConfig.leftPanel?.showDelete !== false && (
                                    <button
                                      onClick={(e) => {
                                        e.stopPropagation();
                                        handleDeleteClick("left", item);
                                      }}
                                      className="hover:bg-destructive/10 rounded p-1 transition-colors"
                                      title="삭제"
                                    >
                                      <Trash2 className="text-destructive h-4 w-4" />
                                    </button>
                                  )}

                                  {/* 항목별 추가 버튼 */}
                                  {componentConfig.leftPanel?.showItemAddButton && (
                                    <button
                                      onClick={(e) => {
                                        e.stopPropagation();
                                        handleItemAddClick(item);
                                      }}
                                      className="hover:bg-muted/80 rounded p-1 transition-colors"
                                      title="하위 항목 추가"
                                    >
                                      <Plus className="text-muted-foreground h-4 w-4" />
                                    </button>
                                  )}
                                </div>
                              )}
                            </div>
                          </div>

                          {/* 자식 항목들 (접혀있으면 표시 안함) */}
                          {hasChildren &&
                            isExpanded &&
                            item.children.map((child: any, childIndex: number) => renderTreeItem(child, childIndex))}
                        </React.Fragment>
                      );
                    };

                    return filteredLeftData.length > 0 ? (
                      // 실제 데이터 표시
                      filteredLeftData.map((item, index) => renderTreeItem(item, index))
                    ) : (
                      // 검색 결과 없음
                      <div className="text-muted-foreground py-8 text-center text-sm">
                        {leftSearchQuery ? (
                          <>
                            <p>검색 결과가 없습니다.</p>
                            <p className="text-muted-foreground/70 mt-1 text-xs">다른 검색어를 입력해보세요.</p>
                          </>
                        ) : (
                          "데이터가 없습니다."
                        )}
                      </div>
                    );
                  })()
                )}
              </div>
            )}
          </CardContent>

          {/* 좌측 페이징 UI */}
          {leftPaginationEnabled &&
            !isDesignMode &&
            renderPaginationBar({
              currentPage: leftCurrentPage,
              totalPages: leftTotalPages,
              total: leftTotal,
              pageSize: leftPageSize,
              pageInput: leftPageInput,
              setPageInput: setLeftPageInput,
              onPageChange: handleLeftPageChange,
              onPageSizeChange: handleLeftPageSizeChange,
              commitPageInput: commitLeftPageInput,
              loading: isLoadingLeft,
            })}
        </Card>
      </div>

      {/* 리사이저: 6px 너비, 그립 핸들(2x28px bar), hover 시 primary 하이라이트 */}
      {resizable && (
        <div
          onMouseDown={handleMouseDown}
          className="group bg-border/60 hover:bg-primary/25 flex w-1.5 cursor-col-resize flex-col items-center justify-center gap-0.5 transition-colors"
          aria-label="분할선 드래그"
        >
          <div className="bg-muted-foreground/30 h-7 w-0.5 rounded-full transition-opacity group-hover:opacity-70" />
        </div>
      )}

      {/* 우측 패널 */}
      <div
        style={{ width: `${100 - leftWidth}%`, minWidth: isDesignMode ? `${minRightWidth}px` : "0", height: "100%" }}
        className="border-border/60 bg-muted/5 flex flex-col border-l"
      >
        <Card className="flex flex-col border-0 bg-transparent shadow-none" style={{ height: "100%" }}>
          <CardHeader
            className="bg-muted/30 flex-shrink-0 border-b"
            style={{
              height: componentConfig.rightPanel?.panelHeaderHeight || 48,
              minHeight: componentConfig.rightPanel?.panelHeaderHeight || 48,
              padding: "0 0.75rem",
              display: "flex",
              alignItems: "center",
            }}
          >
            <div className="flex w-full items-center justify-between gap-2">
              <div className="flex min-w-0 flex-1 items-center gap-2">
                <PanelRight className="text-muted-foreground h-4 w-4 shrink-0" />
                {/* 탭이 없으면 제목만, 있으면 탭으로 전환 (2px primary 밑줄 인디케이터) */}
                {(componentConfig.rightPanel?.additionalTabs?.length || 0) > 0 ? (
                  <div className="flex items-center gap-0">
                    <button
                      onClick={() => handleTabChange(0)}
                      className={cn(
                        "-mb-px px-3.5 py-2 text-[10px] font-semibold transition-all",
                        activeTabIndex === 0
                          ? "text-primary border-primary border-b-2"
                          : "text-muted-foreground hover:text-foreground border-b-2 border-transparent",
                      )}
                    >
                      {componentConfig.rightPanel?.title || "기본"}
                    </button>
                    {componentConfig.rightPanel?.additionalTabs?.map((tab: any, index: number) => (
                      <button
                        key={tab.tabId || `tab-${index}`}
                        onClick={() => handleTabChange(index + 1)}
                        className={cn(
                          "-mb-px px-3.5 py-2 text-[10px] font-semibold transition-all",
                          activeTabIndex === index + 1
                            ? "text-primary border-primary border-b-2"
                            : "text-muted-foreground hover:text-foreground border-b-2 border-transparent",
                        )}
                      >
                        {tab.label || `탭 ${index + 1}`}
                      </button>
                    ))}
                  </div>
                ) : (
                  <CardTitle className="truncate text-base font-semibold">
                    {componentConfig.rightPanel?.title || "우측 패널"}
                  </CardTitle>
                )}
                {!isDesignMode && (
                  <Badge variant="secondary" className="shrink-0 text-xs">
                    {activeTabIndex === 0
                      ? Array.isArray(rightData)
                        ? rightData.length
                        : rightData
                          ? 1
                          : 0
                      : (tabsData[activeTabIndex]?.length ?? 0)}
                  </Badge>
                )}
              </div>
              {!isDesignMode && (
                <div className="flex items-center gap-2">
                  {/* 커스텀 모드 기본정보 탭: 저장 버튼 */}
                  {activeTabIndex === 0 && componentConfig.rightPanel?.displayMode === "custom" && selectedLeftItem && (
                    <Button size="sm" variant="default" onClick={handleCustomRightSave}>
                      <Save className="mr-1 h-4 w-4" />
                      저장
                    </Button>
                  )}
                  {activeTabIndex === 0
                    ? componentConfig.rightPanel?.showAdd && (
                        <Button size="sm" variant="outline" onClick={() => handleAddClick("right")}>
                          <Plus className="mr-1 h-4 w-4" />
                          추가
                        </Button>
                      )
                    : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.showAdd && (
                        <Button size="sm" variant="outline" onClick={() => handleAddClick("right")}>
                          <Plus className="mr-1 h-4 w-4" />
                          추가
                        </Button>
                      )}
                </div>
              )}
            </div>
          </CardHeader>
          {componentConfig.rightPanel?.showSearch && (
            <div className="flex-shrink-0 border-b p-2">
              <div className="relative">
                <Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
                <Input
                  placeholder="검색..."
                  value={rightSearchQuery}
                  onChange={(e) => setRightSearchQuery(e.target.value)}
                  className="pl-9"
                />
              </div>
            </div>
          )}
          <CardContent
            className="flex-1 overflow-auto p-4"
            {...(isDesignMode
              ? {
                  "data-split-panel-container": "true",
                  "data-component-id": component.id,
                  "data-panel-side": "right",
                }
              : {})}
          >
            {/* 추가 탭 컨텐츠 */}
            {activeTabIndex > 0 ? (
              (() => {
                const currentTabConfig = componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any;
                const currentTabData = tabsData[activeTabIndex] || [];
                const isTabLoading = tabsLoading[activeTabIndex];

                // 메인 패널이 "detail"(선택 시 표시)이면 좌측 미선택 시 안내 메시지
                const mainRelationType = componentConfig.rightPanel?.relation?.type || "detail";
                if (mainRelationType === "detail" && !selectedLeftItem && !isDesignMode) {
                  return (
                    <div className="text-muted-foreground flex h-full flex-col items-center justify-center gap-2 py-12 text-sm">
                      <p>좌측에서 항목을 선택하세요</p>
                      <p className="text-xs">선택한 항목의 관련 데이터가 여기에 표시됩니다</p>
                    </div>
                  );
                }

                if (isTabLoading) {
                  return (
                    <div className="flex h-32 items-center justify-center">
                      <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
                    </div>
                  );
                }

                if (currentTabData.length === 0 && !isTabLoading) {
                  return (
                    <div className="text-muted-foreground flex h-full flex-col items-center justify-center gap-2 py-12 text-sm">
                      <p>관련 데이터가 없습니다.</p>
                    </div>
                  );
                }

                // 탭 컬럼 설정
                const tabColumns = currentTabConfig?.columns || [];

                // 테이블 모드로 표시 (행 클릭 시 상세 정보 펼치기)
                if (currentTabConfig?.displayMode === "table") {
                  const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
                  const tabIndex = activeTabIndex - 1;
                  let tabSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
                  if (!isDesignMode) {
                    tabSummaryColumns = applyRuntimeOrder(tabSummaryColumns, tabIndex);
                  }
                  const canDragTabColumns = tabSummaryColumns.length > 0;
                  return (
                    <div className="h-full overflow-auto">
                      <table className="w-full text-sm">
                        <thead className="bg-background sticky top-0 z-10">
                          <tr className="border-border/60 border-b-2">
                            {tabSummaryColumns.map((col: any, idx: number) => {
                              const isDropTarget = rightDragSource === tabIndex && rightDropTargetColumnIndex === idx;
                              const isDragging = rightDragSource === tabIndex && rightDraggedColumnIndex === idx;
                              return (
                                <th
                                  key={col.name}
                                  className={cn(
                                    "group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] uppercase",
                                    isDropTarget && "border-l-primary bg-primary/5 border-l-[3px]",
                                    canDragTabColumns && "cursor-grab active:cursor-grabbing",
                                    isDragging && "opacity-50",
                                  )}
                                  draggable={canDragTabColumns}
                                  onDragStart={(e) => canDragTabColumns && handleRightColumnDragStart(e, idx, tabIndex)}
                                  onDragOver={(e) => canDragTabColumns && handleRightColumnDragOver(e, idx)}
                                  onDragEnd={handleRightColumnDragEnd}
                                  onDrop={(e) => canDragTabColumns && handleRightColumnDrop(e, idx, tabIndex)}
                                >
                                  {canDragTabColumns && (
                                    <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />
                                  )}
                                  {col.label || col.name}
                                </th>
                              );
                            })}
                            {hasTabActions && (
                              <th className="text-muted-foreground px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] uppercase">
                                작업
                              </th>
                            )}
                          </tr>
                        </thead>
                        <tbody>
                          {currentTabData.map((item: any, idx: number) => {
                            const tabItemId = item.id || item.ID || idx;
                            const isTabExpanded = expandedRightItems.has(`tab_${activeTabIndex}_${tabItemId}`);

                            // 상세 정보용 전체 값 목록 (showInDetail이 false가 아닌 것만)
                            const tabDetailColumns = tabColumns.filter((col: any) => col.showInDetail !== false);
                            const tabAllValues: [string, any, string][] =
                              tabDetailColumns.length > 0
                                ? tabDetailColumns.map(
                                    (col: any) =>
                                      [col.name, getEntityJoinValue(item, col.name), col.label || col.name] as [
                                        string,
                                        any,
                                        string,
                                      ],
                                  )
                                : Object.entries(item)
                                    .filter(([, v]) => v !== null && v !== undefined && v !== "")
                                    .map(([k, v]) => [k, v, ""] as [string, any, string]);

                            return (
                              <React.Fragment key={tabItemId}>
                                <tr
                                  className={cn(
                                    "group/action border-border/50 cursor-pointer border-b transition-[background] duration-75",
                                    isTabExpanded
                                      ? "bg-primary/5"
                                      : idx % 2 === 1
                                        ? "bg-muted/20 hover:bg-accent"
                                        : "hover:bg-accent",
                                  )}
                                  onClick={() => toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)}
                                >
                                  {tabSummaryColumns.map((col: any) => (
                                    <td
                                      key={col.name}
                                      className="px-3 py-2 text-[11px]"
                                      style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
                                    >
                                      {col.type === "progress"
                                        ? renderProgressCell(col, item, selectedLeftItem)
                                        : formatCellValue(
                                            col.name,
                                            getEntityJoinValue(item, col.name),
                                            rightCategoryMappings,
                                            col.format,
                                          )}
                                    </td>
                                  ))}
                                  {hasTabActions && (
                                    <td className="px-3 py-2 text-right">
                                      <div className="flex items-center justify-end gap-1">
                                        {currentTabConfig?.showEdit && (
                                          <Button
                                            size="sm"
                                            variant="ghost"
                                            className="h-7 px-2 text-xs"
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleEditClick("right", item);
                                            }}
                                          >
                                            <Pencil className="h-3 w-3" />
                                          </Button>
                                        )}
                                        {currentTabConfig?.showDelete && (
                                          <Button
                                            size="sm"
                                            variant="ghost"
                                            className="text-destructive h-7 px-2 text-xs"
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleDeleteClick("right", item, currentTabConfig?.tableName);
                                            }}
                                          >
                                            <Trash2 className="h-3 w-3" />
                                          </Button>
                                        )}
                                      </div>
                                    </td>
                                  )}
                                </tr>
                                {/* 상세 정보 (행 클릭 시 펼쳐짐) */}
                                {isTabExpanded && (
                                  <tr>
                                    <td
                                      colSpan={tabSummaryColumns.length + (hasTabActions ? 1 : 0)}
                                      className="bg-muted/30 px-3 py-2"
                                    >
                                      <div className="mb-1 text-xs font-semibold">상세 정보</div>
                                      <div className="bg-card overflow-auto rounded-md border">
                                        <table className="w-full text-sm">
                                          <tbody className="divide-border divide-y">
                                            {tabAllValues.map(([key, value, label]) => {
                                              const displayValue =
                                                value === null || value === undefined || value === ""
                                                  ? "-"
                                                  : formatCellValue(key, value, rightCategoryMappings);
                                              return (
                                                <tr key={key} className="hover:bg-muted">
                                                  <td className="text-muted-foreground px-3 py-1.5 text-xs font-medium whitespace-nowrap">
                                                    {label || getColumnLabel(key)}
                                                  </td>
                                                  <td className="text-foreground px-3 py-1.5 text-xs break-all">
                                                    {displayValue}
                                                  </td>
                                                </tr>
                                              );
                                            })}
                                          </tbody>
                                        </table>
                                      </div>
                                    </td>
                                  </tr>
                                )}
                              </React.Fragment>
                            );
                          })}
                        </tbody>
                      </table>
                    </div>
                  );
                }

                // 리스트 모드도 테이블형으로 통일 (행 클릭 시 상세 정보 표시)
                {
                  const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
                  const listTabIndex = activeTabIndex - 1;
                  let listSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
                  if (!isDesignMode) {
                    listSummaryColumns = applyRuntimeOrder(listSummaryColumns, listTabIndex);
                  }
                  const canDragListTabColumns = listSummaryColumns.length > 0;
                  return (
                    <div className="h-full overflow-auto">
                      <table className="w-full text-sm">
                        <thead className="bg-background sticky top-0 z-10">
                          <tr className="border-border/60 border-b-2">
                            {listSummaryColumns.map((col: any, idx: number) => {
                              const isDropTarget =
                                rightDragSource === listTabIndex && rightDropTargetColumnIndex === idx;
                              const isDragging = rightDragSource === listTabIndex && rightDraggedColumnIndex === idx;
                              return (
                                <th
                                  key={col.name}
                                  className={cn(
                                    "group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] uppercase",
                                    isDropTarget && "border-l-primary bg-primary/5 border-l-[3px]",
                                    canDragListTabColumns && "cursor-grab active:cursor-grabbing",
                                    isDragging && "opacity-50",
                                  )}
                                  draggable={canDragListTabColumns}
                                  onDragStart={(e) =>
                                    canDragListTabColumns && handleRightColumnDragStart(e, idx, listTabIndex)
                                  }
                                  onDragOver={(e) => canDragListTabColumns && handleRightColumnDragOver(e, idx)}
                                  onDragEnd={handleRightColumnDragEnd}
                                  onDrop={(e) => canDragListTabColumns && handleRightColumnDrop(e, idx, listTabIndex)}
                                >
                                  {canDragListTabColumns && (
                                    <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />
                                  )}
                                  {col.label || col.name}
                                </th>
                              );
                            })}
                            {hasTabActions && (
                              <th className="text-muted-foreground px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] uppercase">
                                작업
                              </th>
                            )}
                          </tr>
                        </thead>
                        <tbody>
                          {currentTabData.map((item: any, idx: number) => {
                            const tabItemId = item.id || item.ID || idx;
                            const isTabExpanded = expandedRightItems.has(`tab_${activeTabIndex}_${tabItemId}`);
                            // showInDetail이 false가 아닌 것만 상세에 표시
                            const listDetailColumns = tabColumns.filter((col: any) => col.showInDetail !== false);
                            const tabAllValues: [string, any, string][] =
                              listDetailColumns.length > 0
                                ? listDetailColumns.map(
                                    (col: any) =>
                                      [col.name, getEntityJoinValue(item, col.name), col.label || col.name] as [
                                        string,
                                        any,
                                        string,
                                      ],
                                  )
                                : Object.entries(item)
                                    .filter(([, v]) => v !== null && v !== undefined && v !== "")
                                    .map(([k, v]) => [k, v, ""] as [string, any, string]);

                            return (
                              <React.Fragment key={tabItemId}>
                                <tr
                                  className={cn(
                                    "group/action border-border/50 cursor-pointer border-b transition-[background] duration-75",
                                    isTabExpanded
                                      ? "bg-primary/5"
                                      : idx % 2 === 1
                                        ? "bg-muted/20 hover:bg-accent"
                                        : "hover:bg-accent",
                                  )}
                                  onClick={() => toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)}
                                >
                                  {listSummaryColumns.map((col: any) => (
                                    <td
                                      key={col.name}
                                      className="px-3 py-2 text-[11px]"
                                      style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
                                    >
                                      {col.type === "progress"
                                        ? renderProgressCell(col, item, selectedLeftItem)
                                        : formatCellValue(
                                            col.name,
                                            getEntityJoinValue(item, col.name),
                                            rightCategoryMappings,
                                            col.format,
                                          )}
                                    </td>
                                  ))}
                                  {hasTabActions && (
                                    <td className="px-3 py-2 text-right">
                                      <div className="flex items-center justify-end gap-1">
                                        {currentTabConfig?.showEdit && (
                                          <Button
                                            size="sm"
                                            variant="ghost"
                                            className="h-7 px-2 text-xs"
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleEditClick("right", item);
                                            }}
                                          >
                                            <Pencil className="h-3 w-3" />
                                          </Button>
                                        )}
                                        {currentTabConfig?.showDelete && (
                                          <Button
                                            size="sm"
                                            variant="ghost"
                                            className="text-destructive h-7 px-2 text-xs"
                                            onClick={(e) => {
                                              e.stopPropagation();
                                              handleDeleteClick("right", item, currentTabConfig?.tableName);
                                            }}
                                          >
                                            <Trash2 className="h-3 w-3" />
                                          </Button>
                                        )}
                                      </div>
                                    </td>
                                  )}
                                </tr>
                                {isTabExpanded && (
                                  <tr>
                                    <td
                                      colSpan={listSummaryColumns.length + (hasTabActions ? 1 : 0)}
                                      className="bg-muted/30 px-3 py-2"
                                    >
                                      <div className="mb-1 text-xs font-semibold">상세 정보</div>
                                      <div className="bg-card overflow-auto rounded-md border">
                                        <table className="w-full text-sm">
                                          <tbody className="divide-border divide-y">
                                            {tabAllValues.map(([key, value, label]) => {
                                              const displayValue =
                                                value === null || value === undefined || value === ""
                                                  ? "-"
                                                  : formatCellValue(key, value, rightCategoryMappings);
                                              return (
                                                <tr key={key} className="hover:bg-muted">
                                                  <td className="text-muted-foreground px-3 py-1.5 text-xs font-medium whitespace-nowrap">
                                                    {label || getColumnLabel(key)}
                                                  </td>
                                                  <td className="text-foreground px-3 py-1.5 text-xs break-all">
                                                    {displayValue}
                                                  </td>
                                                </tr>
                                              );
                                            })}
                                          </tbody>
                                        </table>
                                      </div>
                                    </td>
                                  </tr>
                                )}
                              </React.Fragment>
                            );
                          })}
                        </tbody>
                      </table>
                    </div>
                  );
                }
              })()
            ) : componentConfig.rightPanel?.displayMode === "custom" ? (
              // 커스텀 모드: alwaysShow가 아닌 경우에만 좌측 선택 필요
              !isDesignMode && !selectedLeftItem && !componentConfig.rightPanel?.alwaysShow ? (
                <div className="flex h-full items-center justify-center">
                  <div className="text-muted-foreground text-center text-sm">
                    <p className="mb-2">좌측에서 항목을 선택하세요</p>
                    <p className="text-xs">선택한 항목의 상세 정보가 여기에 표시됩니다</p>
                  </div>
                </div>
              ) : (
                <div
                  className="relative h-full w-full"
                  data-split-panel-container="true"
                  data-component-id={component.id}
                  data-panel-side="right"
                >
                  {/* 커스텀 모드: 디자인/실행 모드 분기 렌더링 */}
                  {componentConfig.rightPanel?.components && componentConfig.rightPanel.components.length > 0 ? (
                    !isDesignMode ? (
                      <ScaledCustomPanel
                        components={componentConfig.rightPanel!.components}
                        formData={customLeftSelectedData}
                        onFormDataChange={(fieldName: string, value: any) => {
                          setCustomLeftSelectedData((prev: Record<string, any>) => ({ ...prev, [fieldName]: value }));
                        }}
                        tableName={componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName}
                        menuObjid={(props as any).menuObjid}
                        screenId={(props as any).screenId}
                        userId={(props as any).userId}
                        userName={(props as any).userName}
                        companyCode={companyCode}
                        allComponents={(props as any).allComponents}
                        selectedRowsData={localSelectedRowsData}
                        onSelectedRowsChange={handleLocalSelectedRowsChange}
                      />
                    ) : (
                      <div className="relative h-full w-full" style={{ minHeight: "100%", minWidth: "100%" }}>
                        {componentConfig.rightPanel.components.map((comp: PanelInlineComponent) => {
                          const isSelectedComp = selectedPanelComponentId === comp.id;
                          const isDraggingComp = draggingCompId === comp.id;
                          const isResizingComp = resizingCompId === comp.id;

                          const displayX = isDraggingComp && dragPosition ? dragPosition.x : comp.position?.x || 0;
                          const displayY = isDraggingComp && dragPosition ? dragPosition.y : comp.position?.y || 0;
                          const displayWidth =
                            isResizingComp && resizeSize ? resizeSize.width : comp.size?.width || 200;
                          const displayHeight =
                            isResizingComp && resizeSize ? resizeSize.height : comp.size?.height || 100;

                          const componentData = {
                            id: comp.id,
                            type: "component" as const,
                            componentType: comp.componentType,
                            label: comp.label,
                            position: comp.position || { x: 0, y: 0 },
                            size: { width: displayWidth, height: displayHeight },
                            componentConfig: comp.componentConfig || {},
                            style: comp.style || {},
                            tableName: comp.componentConfig?.tableName,
                            columnName: comp.componentConfig?.columnName,
                            webType: comp.componentConfig?.webType,
                            inputType: (comp as any).inputType || comp.componentConfig?.inputType,
                          };

                          return (
                            <div
                              key={comp.id}
                              data-panel-comp-id={comp.id}
                              className="absolute"
                              style={{
                                left: displayX,
                                top: displayY,
                                zIndex: isDraggingComp ? 100 : isSelectedComp ? 10 : 1,
                              }}
                              onClick={(e) => {
                                e.stopPropagation();
                                if (comp.componentType !== "v2-tabs-widget") {
                                  setNestedTabSelectedCompId(undefined);
                                }
                                setInternalSelectedCompId(comp.id);
                                onSelectPanelComponent?.("right", comp.id, comp);
                              }}
                            >
                              <div
                                className={cn(
                                  "bg-muted flex h-4 cursor-move items-center justify-between rounded-t border border-b-0 px-1",
                                  isSelectedComp ? "border-primary" : "border-border",
                                )}
                                style={{ width: displayWidth }}
                                onMouseDown={(e) => handlePanelDragStart(e, "right", comp)}
                              >
                                <div className="flex items-center gap-0.5">
                                  <Move className="text-muted-foreground/70 h-2.5 w-2.5" />
                                  <span className="text-muted-foreground max-w-[100px] truncate text-[9px]">
                                    {comp.label || comp.componentType}
                                  </span>
                                </div>
                                <div className="flex items-center">
                                  <button
                                    className="hover:bg-muted/80 rounded p-0.5"
                                    onClick={(e) => {
                                      e.stopPropagation();
                                      onSelectPanelComponent?.("right", comp.id, comp);
                                    }}
                                    title="설정"
                                  >
                                    <Settings className="text-muted-foreground h-2.5 w-2.5" />
                                  </button>
                                  <button
                                    className="hover:bg-destructive/10 rounded p-0.5"
                                    onClick={(e) => {
                                      e.stopPropagation();
                                      handleRemovePanelComponent("right", comp.id);
                                    }}
                                    title="삭제"
                                  >
                                    <Trash2 className="text-destructive h-2.5 w-2.5" />
                                  </button>
                                </div>
                              </div>

                              <div
                                className={cn(
                                  "relative overflow-hidden rounded-b border bg-white shadow-sm",
                                  isSelectedComp ? "border-primary ring-primary/30 ring-2" : "border-border",
                                  (isDraggingComp || isResizingComp) && "opacity-80 shadow-lg",
                                  !(isDraggingComp || isResizingComp) && "transition-all",
                                )}
                                style={{
                                  width: displayWidth,
                                  height: displayHeight,
                                }}
                              >
                                <div
                                  className={cn(
                                    "h-full w-full",
                                    comp.componentType === "v2-tabs-widget" ||
                                      comp.componentType === "tabs-widget" ||
                                      comp.componentType === "v2-split-panel-layout" ||
                                      comp.componentType === "split-panel-layout"
                                      ? ""
                                      : "pointer-events-none",
                                  )}
                                >
                                  <DynamicComponentRenderer
                                    component={componentData as any}
                                    isDesignMode={true}
                                    formData={{}}
                                    onUpdateComponent={(updatedComp: any) => {
                                      handleNestedComponentUpdate("right", comp.id, updatedComp);
                                    }}
                                    onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
                                      setNestedTabSelectedCompId(compId);
                                      const event = new CustomEvent("nested-tab-component-select", {
                                        detail: {
                                          tabsComponentId: comp.id,
                                          tabId,
                                          componentId: compId,
                                          component: tabComp,
                                          parentSplitPanelId: component.id,
                                          parentPanelSide: "right",
                                        },
                                      });
                                      window.dispatchEvent(event);
                                    }}
                                    selectedTabComponentId={nestedTabSelectedCompId}
                                  />
                                </div>

                                {isSelectedComp && (
                                  <>
                                    <div
                                      className="hover:bg-primary/10 pointer-events-auto absolute top-0 right-0 z-10 h-full w-2 cursor-ew-resize"
                                      onMouseDown={(e) => handlePanelResizeStart(e, "right", comp, "e")}
                                    />
                                    <div
                                      className="hover:bg-primary/10 pointer-events-auto absolute bottom-0 left-0 z-10 h-2 w-full cursor-ns-resize"
                                      onMouseDown={(e) => handlePanelResizeStart(e, "right", comp, "s")}
                                    />
                                    <div
                                      className="hover:bg-primary/20 pointer-events-auto absolute right-0 bottom-0 z-20 h-3 w-3 cursor-nwse-resize"
                                      onMouseDown={(e) => handlePanelResizeStart(e, "right", comp, "se")}
                                    />
                                  </>
                                )}
                              </div>
                            </div>
                          );
                        })}
                      </div>
                    )
                  ) : (
                    // 컴포넌트가 없을 때 드롭 영역 표시
                    <div className="border-input bg-muted/50 flex h-full w-full flex-col items-center justify-center rounded border-2 border-dashed">
                      <Plus className="text-muted-foreground/70 mb-2 h-8 w-8" />
                      <p className="text-muted-foreground text-sm font-medium">커스텀 모드</p>
                      <p className="text-muted-foreground/70 mt-1 text-xs">
                        {isDesignMode ? "컴포넌트를 드래그하여 배치하세요" : "배치된 컴포넌트가 없습니다"}
                      </p>
                    </div>
                  )}
                </div>
              )
            ) : isLoadingRight ? (
              // 로딩 중
              <div className="flex h-full items-center justify-center">
                <div className="text-center">
                  <Loader2 className="text-primary mx-auto h-8 w-8 animate-spin" />
                  <p className="text-muted-foreground mt-2 text-sm">데이터를 불러오는 중...</p>
                </div>
              </div>
            ) : rightData ? (
              // 실제 데이터 표시
              Array.isArray(rightData) ? (
                // 조인 모드: 여러 데이터를 테이블/리스트로 표시
                (() => {
                  // 검색 필터링
                  const filteredData = rightSearchQuery
                    ? rightData.filter((item) => {
                        const searchLower = rightSearchQuery.toLowerCase();
                        return Object.entries(item).some(([key, value]) => {
                          if (value === null || value === undefined) return false;
                          return String(value).toLowerCase().includes(searchLower);
                        });
                      })
                    : rightData;

                  // 테이블 모드 체크
                  const isTableMode = componentConfig.rightPanel?.displayMode === "table";

                  if (isTableMode) {
                    // 테이블 모드 렌더링
                    const displayColumns = componentConfig.rightPanel?.columns || [];

                    // 🆕 그룹 합산 모드일 때: 복합키 컬럼을 우선 표시
                    const relationKeys = componentConfig.rightPanel?.relation?.keys || [];
                    const keyColumns = relationKeys.map((k: any) => k.leftColumn).filter(Boolean);
                    const isGroupedMode = selectedLeftItem?._originalItems?.length > 0;

                    let columnsToShow: any[] = [];

                    if (displayColumns.length > 0) {
                      // 설정된 컬럼 사용 (showInSummary가 false가 아닌 것만 테이블에 표시)
                      columnsToShow = displayColumns
                        .filter((col) => col.showInSummary !== false)
                        .map((col) => ({
                          ...col,
                          label: rightColumnLabels[col.name] || col.label || col.name,
                          format: col.format,
                        }));

                      // 🆕 그룹 합산 모드이고, 키 컬럼이 표시 목록에 없으면 맨 앞에 추가
                      if (isGroupedMode && keyColumns.length > 0) {
                        const existingColNames = columnsToShow.map((c) => c.name);
                        const missingKeyColumns = keyColumns.filter((k: string) => !existingColNames.includes(k));

                        if (missingKeyColumns.length > 0) {
                          const keyColsToAdd = missingKeyColumns.map((colName: string) => ({
                            name: colName,
                            label: rightColumnLabels[colName] || colName,
                            width: 120,
                            align: "left" as const,
                            format: undefined,
                            _isKeyColumn: true, // 구분용 플래그
                          }));
                          columnsToShow = [...keyColsToAdd, ...columnsToShow];
                        }
                      }
                    } else {
                      // 기본 컬럼 자동 생성
                      columnsToShow = Object.keys(filteredData[0] || {})
                        .filter((key) => shouldShowField(key))
                        .slice(0, 5)
                        .map((key) => ({
                          name: key,
                          label: rightColumnLabels[key] || key,
                          width: 150,
                          align: "left" as const,
                          format: undefined,
                        }));
                    }

                    // 런타임 컬럼 순서 적용
                    if (!isDesignMode && runtimeColumnOrder["main"]) {
                      const keyColCount = columnsToShow.filter((c: any) => c._isKeyColumn).length;
                      const keyCols = columnsToShow.slice(0, keyColCount);
                      const dataCols = columnsToShow.slice(keyColCount);
                      columnsToShow = [...keyCols, ...applyRuntimeOrder(dataCols, "main")];
                    }

                    // 컬럼 너비 합계 계산 (작업 컬럼 제외, 100% 초과 시 스크롤)
                    const rightTotalColWidth = columnsToShow.reduce((sum, col) => {
                      const w = col.width && col.width <= 100 ? col.width : 0;
                      return sum + w;
                    }, 0);

                    const rightConfigColumnStart = columnsToShow.filter((c: any) => c._isKeyColumn).length;
                    const canDragRightColumns = displayColumns.length > 0;

                    return (
                      <div className="flex h-full w-full flex-col">
                        <div className="min-h-0 flex-1 overflow-auto">
                          <table className="min-w-full">
                            <thead className="sticky top-0 z-10">
                              <tr className="border-border/60 border-b-2">
                                {columnsToShow.map((col, idx) => {
                                  const configColIndex = idx - rightConfigColumnStart;
                                  const isDraggable = canDragRightColumns && !col._isKeyColumn;
                                  const isDropTarget =
                                    rightDragSource === "main" && rightDropTargetColumnIndex === configColIndex;
                                  const isDragging =
                                    rightDragSource === "main" && rightDraggedColumnIndex === configColIndex;
                                  return (
                                    <th
                                      key={idx}
                                      className={cn(
                                        "group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase",
                                        isDropTarget && "border-l-primary bg-primary/5 border-l-[3px]",
                                        isDraggable && "cursor-grab active:cursor-grabbing",
                                        isDragging && "opacity-50",
                                      )}
                                      style={{
                                        width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
                                        textAlign: col.align || "left",
                                      }}
                                      draggable={isDraggable}
                                      onDragStart={(e) =>
                                        isDraggable && handleRightColumnDragStart(e, configColIndex, "main")
                                      }
                                      onDragOver={(e) => isDraggable && handleRightColumnDragOver(e, configColIndex)}
                                      onDragEnd={handleRightColumnDragEnd}
                                      onDrop={(e) => isDraggable && handleRightColumnDrop(e, configColIndex, "main")}
                                    >
                                      {isDraggable && (
                                        <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />
                                      )}
                                      {col.label}
                                    </th>
                                  );
                                })}
                                {(() => {
                                  const rightEditVisible =
                                    (componentConfig.rightPanel?.showEdit ??
                                      componentConfig.rightPanel?.editButton?.enabled) !== false;
                                  const rightDeleteVisible =
                                    (componentConfig.rightPanel?.showDelete ??
                                      componentConfig.rightPanel?.deleteButton?.enabled) !== false;
                                  return !isDesignMode && (rightEditVisible || rightDeleteVisible) ? (
                                    <th
                                      className="bg-background text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] uppercase"
                                      style={{ width: "80px" }}
                                    >
                                      작업
                                    </th>
                                  ) : null;
                                })()}
                              </tr>
                            </thead>
                            <tbody>
                              {filteredData.map((item, idx) => {
                                const itemId = item.id || item.ID || idx;
                                const rightEditVisible =
                                  (componentConfig.rightPanel?.showEdit ??
                                    componentConfig.rightPanel?.editButton?.enabled) !== false;
                                const rightDeleteVisible =
                                  (componentConfig.rightPanel?.showDelete ??
                                    componentConfig.rightPanel?.deleteButton?.enabled) !== false;

                                return (
                                  <tr
                                    key={itemId}
                                    className={cn(
                                      "group/action border-border/50 hover:bg-accent border-b transition-[background] duration-75",
                                      idx % 2 === 1 && "bg-muted/20",
                                    )}
                                  >
                                    {columnsToShow.map((col, colIdx) => (
                                      <td
                                        key={colIdx}
                                        className="px-3 py-2 text-[11px]"
                                        style={{
                                          textAlign: col.align || "left",
                                          overflow: "hidden",
                                          textOverflow: "ellipsis",
                                          whiteSpace: "nowrap",
                                        }}
                                      >
                                        {col.type === "progress"
                                          ? renderProgressCell(col, item, selectedLeftItem)
                                          : formatCellValue(
                                              col.name,
                                              getEntityJoinValue(item, col.name),
                                              rightCategoryMappings,
                                              col.format,
                                            )}
                                      </td>
                                    ))}
                                    {/* 수정 또는 삭제 버튼이 하나라도 활성화되어 있을 때만 작업 셀 표시 */}
                                    {!isDesignMode && (rightEditVisible || rightDeleteVisible) && (
                                      <td className="bg-card group-hover/action:bg-accent sticky right-0 z-10 px-3 py-2 text-right text-sm whitespace-nowrap">
                                        <div className="flex justify-end gap-1 opacity-0 transition-opacity group-hover/action:opacity-100">
                                          {rightEditVisible && (
                                            <Button
                                              variant={
                                                componentConfig.rightPanel?.editButton?.buttonVariant || "outline"
                                              }
                                              size="sm"
                                              onClick={(e) => {
                                                e.stopPropagation();
                                                handleEditClick("right", item);
                                              }}
                                              className="h-7"
                                            >
                                              <Pencil className="mr-1 h-3 w-3" />
                                              {componentConfig.rightPanel?.editButton?.buttonLabel || "수정"}
                                            </Button>
                                          )}
                                          {rightDeleteVisible && (
                                            <button
                                              onClick={(e) => {
                                                e.stopPropagation();
                                                handleDeleteClick("right", item);
                                              }}
                                              className="hover:bg-destructive/10 rounded p-1 transition-colors"
                                              title={componentConfig.rightPanel?.deleteButton?.buttonLabel || "삭제"}
                                            >
                                              <Trash2 className="text-destructive h-4 w-4" />
                                            </button>
                                          )}
                                        </div>
                                      </td>
                                    )}
                                  </tr>
                                );
                              })}
                            </tbody>
                          </table>
                        </div>
                      </div>
                    );
                  }

                  // 목록 모드 - 테이블형 디자인 (행 클릭 시 상세 정보 표시)
                  {
                    // 표시 컬럼 결정
                    const rightColumns = componentConfig.rightPanel?.columns;
                    let columnsToDisplay: {
                      name: string;
                      label: string;
                      format?: string;
                      bold?: boolean;
                      width?: number;
                    }[] = [];

                    if (rightColumns && rightColumns.length > 0) {
                      // showInSummary가 false가 아닌 것만 메인 테이블에 표시
                      columnsToDisplay = rightColumns
                        .filter((col) => col.showInSummary !== false)
                        .map((col) => ({
                          name: col.name,
                          label: rightColumnLabels[col.name] || col.label || col.name,
                          format: col.format,
                          bold: col.bold,
                          width: col.width,
                        }));
                    } else if (filteredData.length > 0) {
                      columnsToDisplay = Object.keys(filteredData[0])
                        .filter((key) => shouldShowField(key))
                        .slice(0, 6)
                        .map((key) => ({
                          name: key,
                          label: rightColumnLabels[key] || key,
                        }));
                    }

                    const hasEditButton =
                      !isDesignMode &&
                      (componentConfig.rightPanel?.showEdit ?? componentConfig.rightPanel?.editButton?.enabled) !==
                        false;
                    const hasDeleteButton =
                      !isDesignMode &&
                      (componentConfig.rightPanel?.showDelete ?? componentConfig.rightPanel?.deleteButton?.enabled) !==
                        false;
                    const hasActions = hasEditButton || hasDeleteButton;

                    return filteredData.length > 0 ? (
                      <div className="flex h-full w-full flex-col">
                        <div className="min-h-0 flex-1 overflow-auto">
                          <table className="min-w-full text-sm">
                            <thead className="bg-background sticky top-0 z-10">
                              <tr className="border-border/60 border-b-2">
                                {columnsToDisplay.map((col) => (
                                  <th
                                    key={col.name}
                                    className="text-muted-foreground px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] whitespace-nowrap uppercase"
                                    style={{ width: col.width && col.width <= 100 ? `${col.width}%` : "auto" }}
                                  >
                                    {col.label}
                                  </th>
                                ))}
                                {hasActions && (
                                  <th
                                    className="bg-background text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] uppercase"
                                    style={{ width: "80px" }}
                                  >
                                    작업
                                  </th>
                                )}
                              </tr>
                            </thead>
                            <tbody>
                              {filteredData.map((item, idx) => {
                                const itemId = item.id || item.ID || idx;
                                const isExpanded = expandedRightItems.has(itemId);

                                // 상세 정보용 전체 값 목록 (showInDetail이 false가 아닌 것만 표시)
                                let allValues: [string, any, string][] = [];
                                if (rightColumns && rightColumns.length > 0) {
                                  allValues = rightColumns
                                    .filter((col) => col.showInDetail !== false)
                                    .map((col) => {
                                      const value = getEntityJoinValue(item, col.name);
                                      return [col.name, value, col.label] as [string, any, string];
                                    });
                                } else {
                                  allValues = Object.entries(item)
                                    .filter(([, value]) => value !== null && value !== undefined && value !== "")
                                    .map(([key, value]) => [key, value, ""] as [string, any, string]);
                                }

                                return (
                                  <React.Fragment key={itemId}>
                                    <tr
                                      className={cn(
                                        "group/action border-border/50 cursor-pointer border-b transition-[background] duration-75",
                                        isExpanded
                                          ? "bg-primary/5"
                                          : idx % 2 === 1
                                            ? "bg-muted/20 hover:bg-accent"
                                            : "hover:bg-accent",
                                      )}
                                      onClick={() => toggleRightItemExpansion(itemId)}
                                    >
                                      {columnsToDisplay.map((col) => (
                                        <td
                                          key={col.name}
                                          className="px-3 py-2 text-[11px]"
                                          style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
                                        >
                                          {formatCellValue(
                                            col.name,
                                            getEntityJoinValue(item, col.name),
                                            rightCategoryMappings,
                                            col.format,
                                          )}
                                        </td>
                                      ))}
                                      {hasActions && (
                                        <td className="bg-card group-hover/action:bg-accent sticky right-0 z-10 px-3 py-2 text-right">
                                          <div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover/action:opacity-100">
                                            {hasEditButton && (
                                              <Button
                                                size="sm"
                                                variant="ghost"
                                                className="h-7 px-2 text-xs"
                                                onClick={(e) => {
                                                  e.stopPropagation();
                                                  handleEditClick("right", item);
                                                }}
                                              >
                                                <Pencil className="h-3 w-3" />
                                              </Button>
                                            )}
                                            {hasDeleteButton && (
                                              <Button
                                                size="sm"
                                                variant="ghost"
                                                className="text-destructive h-7 px-2 text-xs"
                                                onClick={(e) => {
                                                  e.stopPropagation();
                                                  handleDeleteClick("right", item);
                                                }}
                                              >
                                                <Trash2 className="h-3 w-3" />
                                              </Button>
                                            )}
                                          </div>
                                        </td>
                                      )}
                                    </tr>
                                    {/* 상세 정보 (행 클릭 시 펼쳐짐) */}
                                    {isExpanded && (
                                      <tr>
                                        <td
                                          colSpan={columnsToDisplay.length + (hasActions ? 1 : 0)}
                                          className="bg-muted/30 px-3 py-2"
                                        >
                                          <div className="mb-1 text-xs font-semibold">상세 정보</div>
                                          <div className="bg-card overflow-auto rounded-md border">
                                            <table className="w-full text-sm">
                                              <tbody className="divide-border divide-y">
                                                {allValues.map(([key, value, label]) => {
                                                  const colConfig = rightColumns?.find((c) => c.name === key);
                                                  const format = colConfig?.format;
                                                  const displayValue =
                                                    value === null || value === undefined || value === ""
                                                      ? "-"
                                                      : formatCellValue(key, value, rightCategoryMappings, format);
                                                  return (
                                                    <tr key={key} className="hover:bg-muted">
                                                      <td className="text-muted-foreground px-3 py-1.5 text-xs font-medium whitespace-nowrap">
                                                        {label || getColumnLabel(key)}
                                                      </td>
                                                      <td className="text-foreground px-3 py-1.5 text-xs break-all">
                                                        {displayValue}
                                                      </td>
                                                    </tr>
                                                  );
                                                })}
                                              </tbody>
                                            </table>
                                          </div>
                                        </td>
                                      </tr>
                                    )}
                                  </React.Fragment>
                                );
                              })}
                            </tbody>
                          </table>
                        </div>
                      </div>
                    ) : (
                      <div className="text-muted-foreground py-8 text-center text-sm">
                        {rightSearchQuery ? (
                          <>
                            <p>검색 결과가 없습니다.</p>
                            <p className="text-muted-foreground/70 mt-1 text-xs">다른 검색어를 입력해보세요.</p>
                          </>
                        ) : (
                          "관련 데이터가 없습니다."
                        )}
                      </div>
                    );
                  }
                })()
              ) : (
                // 상세 모드: 단일 객체를 상세 정보로 표시
                (() => {
                  const rightColumns = componentConfig.rightPanel?.columns;
                  let displayEntries: [string, any, string][] = [];

                  if (rightColumns && rightColumns.length > 0) {
                    // 설정된 컬럼만 표시 (showInDetail이 false가 아닌 것만)
                    displayEntries = rightColumns
                      .filter((col) => col.showInDetail !== false)
                      .map((col) => {
                        let value = rightData[col.name];

                        if (value === undefined && col.name.includes(".")) {
                          const columnName = col.name.split(".").pop();
                          value = rightData[columnName || ""];
                        }

                        return [col.name, value, col.label] as [string, any, string];
                      });
                  } else {
                    // 설정 없으면 모든 컬럼 표시
                    displayEntries = Object.entries(rightData)
                      .filter(([_, value]) => value !== null && value !== undefined && value !== "")
                      .map(([key, value]) => [key, value, ""] as [string, any, string]);
                  }

                  const hasDetailEditButton =
                    !isDesignMode && (componentConfig.rightPanel?.editButton?.enabled ?? true);
                  const hasDetailDeleteButton =
                    !isDesignMode && (componentConfig.rightPanel?.deleteButton?.enabled ?? true);

                  return (
                    <div className="space-y-2">
                      {(hasDetailEditButton || hasDetailDeleteButton) && (
                        <div className="flex items-center justify-end gap-1 pb-1">
                          {hasDetailEditButton && (
                            <Button
                              size="sm"
                              variant="outline"
                              className="h-7 gap-1 px-2 text-xs"
                              onClick={() => handleEditClick("right", rightData)}
                            >
                              <Pencil className="h-3 w-3" />
                              {componentConfig.rightPanel?.editButton?.buttonLabel || "수정"}
                            </Button>
                          )}
                          {hasDetailDeleteButton && (
                            <Button
                              size="sm"
                              variant="ghost"
                              className="text-destructive h-7 gap-1 px-2 text-xs"
                              onClick={() => handleDeleteClick("right", rightData)}
                            >
                              <Trash2 className="h-3 w-3" />
                              {componentConfig.rightPanel?.deleteButton?.buttonLabel || "삭제"}
                            </Button>
                          )}
                        </div>
                      )}
                      {displayEntries.map(([key, value, label]) => (
                        <div key={key} className="bg-card rounded-lg border p-4 shadow-sm">
                          <div className="text-muted-foreground mb-1 text-xs font-semibold tracking-wide uppercase">
                            {label || getColumnLabel(key)}
                          </div>
                          <div className="text-sm">
                            {value === null || value === undefined || value === "" ? (
                              <span className="text-muted-foreground">-</span>
                            ) : (
                              String(value)
                            )}
                          </div>
                        </div>
                      ))}
                    </div>
                  );
                })()
              )
            ) : selectedLeftItem && isDesignMode ? (
              // 디자인 모드: 샘플 데이터
              <div className="space-y-4">
                <div className="rounded-lg border p-4">
                  <h3 className="mb-2 font-medium">{selectedLeftItem.name} 상세 정보</h3>
                  <div className="space-y-2 text-sm">
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">항목 1:</span>
                      <span className="font-medium">값 1</span>
                    </div>
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">항목 2:</span>
                      <span className="font-medium">값 2</span>
                    </div>
                    <div className="flex justify-between">
                      <span className="text-muted-foreground">항목 3:</span>
                      <span className="font-medium">값 3</span>
                    </div>
                  </div>
                </div>
              </div>
            ) : (
              // 데이터 없음 또는 초기 로딩 대기
              <div className="flex h-full items-center justify-center">
                <div className="text-muted-foreground text-center text-sm">
                  {componentConfig.rightPanel?.relation?.type === "join" ? (
                    <>
                      <Loader2 className="text-muted-foreground mx-auto h-6 w-6 animate-spin" />
                      <p className="mt-2">데이터를 불러오는 중...</p>
                    </>
                  ) : (
                    <>
                      <p className="mb-2">좌측에서 항목을 선택하세요</p>
                      <p className="text-xs">선택한 항목의 관련 데이터가 여기에 표시됩니다</p>
                    </>
                  )}
                </div>
              </div>
            )}
          </CardContent>

          {/* 우측/탭 페이징 UI */}
          {rightPaginationEnabled &&
            !isDesignMode &&
            renderPaginationBar({
              currentPage: rightPagState.currentPage,
              totalPages: rightPagState.totalPages,
              total: rightPagState.total,
              pageSize: rightPagState.pageSize,
              pageInput: rightPageInput,
              setPageInput: setRightPageInput,
              onPageChange: (p) => {
                if (rightPagState.isTab) {
                  setTabsPagination((prev) => ({
                    ...prev,
                    [activeTabIndex]: {
                      ...(prev[activeTabIndex] || { currentPage: 1, totalPages: 1, total: 0, pageSize: rightPageSize }),
                      currentPage: p,
                    },
                  }));
                  setRightPageInput(String(p));
                  loadTabData(activeTabIndex, selectedLeftItem, p);
                } else {
                  handleRightPageChange(p);
                }
              },
              onPageSizeChange: handleRightPageSizeChange,
              commitPageInput: commitRightPageInput,
              loading: isLoadingRight || (tabsLoading[activeTabIndex] ?? false),
            })}
        </Card>
      </div>

      {/* 추가 모달 */}
      <Dialog open={showAddModal} onOpenChange={setShowAddModal}>
        <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">
              {addModalPanel === "left"
                ? `${componentConfig.leftPanel?.title} 추가`
                : addModalPanel === "right"
                  ? `${componentConfig.rightPanel?.title} 추가`
                  : `하위 ${componentConfig.leftPanel?.title} 추가`}
            </DialogTitle>
            <DialogDescription className="text-xs sm:text-sm">
              {addModalPanel === "left-item"
                ? "선택한 항목의 하위 항목을 추가합니다. 필수 항목을 입력해주세요."
                : "새로운 데이터를 추가합니다. 필수 항목을 입력해주세요."}
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-3 sm:space-y-4">
            {(() => {
              // 어떤 컬럼들을 표시할지 결정
              let modalColumns: Array<{ name: string; label: string; required?: boolean }> | undefined;

              if (addModalPanel === "left") {
                modalColumns = componentConfig.leftPanel?.addModalColumns;
              } else if (addModalPanel === "right") {
                modalColumns = componentConfig.rightPanel?.addModalColumns;
              } else if (addModalPanel === "left-item") {
                modalColumns = componentConfig.leftPanel?.itemAddConfig?.addModalColumns;
              }

              return modalColumns?.map((col, index) => {
                // 항목별 추가 버튼으로 열렸을 때, parentColumn은 미리 채워져 있고 수정 불가
                const isItemAddPreFilled =
                  addModalPanel === "left-item" &&
                  componentConfig.leftPanel?.itemAddConfig?.parentColumn === col.name &&
                  addModalFormData[col.name];

                // 우측 패널 추가 시, 조인 컬럼(rightColumn)은 미리 채워져 있고 수정 불가
                const isRightJoinPreFilled =
                  addModalPanel === "right" &&
                  componentConfig.rightPanel?.rightColumn === col.name &&
                  addModalFormData[col.name];

                const isPreFilled = isItemAddPreFilled || isRightJoinPreFilled;

                return (
                  <div key={index}>
                    <Label htmlFor={col.name} className="text-xs sm:text-sm">
                      {col.label} {col.required && <span className="text-destructive">*</span>}
                      {isPreFilled && <span className="text-primary ml-2 text-[10px]">(자동 설정됨)</span>}
                    </Label>
                    <Input
                      id={col.name}
                      value={addModalFormData[col.name] || ""}
                      onChange={(e) => {
                        setAddModalFormData((prev) => ({
                          ...prev,
                          [col.name]: e.target.value,
                        }));
                      }}
                      placeholder={`${col.label} 입력`}
                      className="h-8 text-xs sm:h-10 sm:text-sm"
                      required={col.required}
                      disabled={isPreFilled}
                    />
                  </div>
                );
              });
            })()}
          </div>

          <DialogFooter className="gap-2 sm:gap-0">
            <Button
              variant="outline"
              onClick={() => setShowAddModal(false)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </Button>
            <Button onClick={handleAddModalSave} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
              <Save className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
              저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 수정 모달 */}
      <Dialog open={showEditModal} onOpenChange={setShowEditModal}>
        <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">
              {editModalPanel === "left"
                ? `${componentConfig.leftPanel?.title} 수정`
                : `${componentConfig.rightPanel?.title} 수정`}
            </DialogTitle>
            <DialogDescription className="text-xs sm:text-sm">
              데이터를 수정합니다. 필요한 항목을 변경해주세요.
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-3 sm:space-y-4">
            {editModalItem &&
              (() => {
                // 좌측 패널 수정: leftColumn만 수정 가능
                if (editModalPanel === "left") {
                  const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;

                  // leftColumn만 표시
                  if (!leftColumn || editModalFormData[leftColumn] === undefined) {
                    return <p className="text-muted-foreground text-sm">수정 가능한 컬럼이 없습니다.</p>;
                  }

                  return (
                    <div>
                      <Label htmlFor={`edit-${leftColumn}`} className="text-xs sm:text-sm">
                        {leftColumn}
                      </Label>
                      <Input
                        id={`edit-${leftColumn}`}
                        value={editModalFormData[leftColumn] || ""}
                        onChange={(e) => {
                          setEditModalFormData((prev) => ({
                            ...prev,
                            [leftColumn]: e.target.value,
                          }));
                        }}
                        placeholder={`${leftColumn} 입력`}
                        className="h-8 text-xs sm:h-10 sm:text-sm"
                      />
                    </div>
                  );
                }

                // 우측 패널 수정: 우측 패널에 설정된 표시 컬럼들만
                if (editModalPanel === "right") {
                  const rightColumns = componentConfig.rightPanel?.columns;

                  if (rightColumns && rightColumns.length > 0) {
                    // 설정된 컬럼만 표시
                    return rightColumns.map((col) => (
                      <div key={col.name}>
                        <Label htmlFor={`edit-${col.name}`} className="text-xs sm:text-sm">
                          {col.label || col.name}
                        </Label>
                        <Input
                          id={`edit-${col.name}`}
                          value={editModalFormData[col.name] || ""}
                          onChange={(e) => {
                            setEditModalFormData((prev) => ({
                              ...prev,
                              [col.name]: e.target.value,
                            }));
                          }}
                          placeholder={`${col.label || col.name} 입력`}
                          className="h-8 text-xs sm:h-10 sm:text-sm"
                        />
                      </div>
                    ));
                  } else {
                    // 설정이 없으면 모든 컬럼 표시 (민감한 필드 제외)
                    return Object.entries(editModalFormData)
                      .filter(([key]) => shouldShowField(key))
                      .map(([key, value]) => (
                        <div key={key}>
                          <Label htmlFor={`edit-${key}`} className="text-xs sm:text-sm">
                            {key}
                          </Label>
                          <Input
                            id={`edit-${key}`}
                            value={editModalFormData[key] || ""}
                            onChange={(e) => {
                              setEditModalFormData((prev) => ({
                                ...prev,
                                [key]: e.target.value,
                              }));
                            }}
                            placeholder={`${key} 입력`}
                            className="h-8 text-xs sm:h-10 sm:text-sm"
                          />
                        </div>
                      ));
                  }
                }

                return null;
              })()}
          </div>

          <DialogFooter className="gap-2 sm:gap-0">
            <Button
              variant="outline"
              onClick={() => setShowEditModal(false)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </Button>
            <Button onClick={handleEditModalSave} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
              <Save className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
              저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 삭제 확인 모달 */}
      <Dialog open={showDeleteModal} onOpenChange={setShowDeleteModal}>
        <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">삭제 확인</DialogTitle>
            <DialogDescription className="text-xs sm:text-sm">
              정말로 이 데이터를 삭제하시겠습니까?
              <br />이 작업은 되돌릴 수 없습니다.
            </DialogDescription>
          </DialogHeader>

          <DialogFooter className="gap-2 sm:gap-0">
            <Button
              variant="outline"
              onClick={() => setShowDeleteModal(false)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </Button>
            <Button
              variant="destructive"
              onClick={handleDeleteConfirm}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              <Trash2 className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
              삭제
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {(componentConfig.leftPanel as any)?.showBomExcelUpload && (
        <BomExcelUploadModal
          open={bomExcelUploadOpen}
          onOpenChange={setBomExcelUploadOpen}
          onSuccess={() => {
            loadLeftData();
          }}
        />
      )}
    </div>
  );
};

/**
 * SplitPanelLayout 래퍼 컴포넌트
 */
export const SplitPanelLayoutWrapper: React.FC<SplitPanelLayoutComponentProps> = (props) => {
  return <SplitPanelLayoutComponent {...props} />;
};
