"use client";

import { useState, useEffect, useMemo } from "react";
import { Button } from "@/components/ui/button";
import {
  ArrowLeft,
  Save,
  Loader2,
  Grid3x3,
  Move,
  Box,
  Package,
  Truck,
  Check,
  ParkingCircle,
  RefreshCw,
} from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import dynamic from "next/dynamic";
import { useToast } from "@/hooks/use-toast";
import type { PlacedObject, ToolType, Warehouse, Area, Location, ObjectType } from "@/types/digitalTwin";
import {
  getWarehouses,
  getAreas,
  getLocations,
  getLayoutById,
  updateLayout,
  getMaterialCounts,
  getMaterials,
  getHierarchyData,
  getChildrenData,
  getMappingTemplates,
  createMappingTemplate,
  type HierarchyData,
  type DigitalTwinMappingTemplate,
} from "@/lib/api/digitalTwin";
import type { MaterialData } from "@/types/digitalTwin";
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";
import HierarchyConfigPanel, { HierarchyConfig } from "./HierarchyConfigPanel";
import { OBJECT_COLORS, DEFAULT_COLOR } from "./constants";
import { validateSpatialContainment, updateChildrenPositions, getAllDescendants } from "./spatialContainment";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";

// 성능 최적화를 위한 디바운스/Blur 처리된 Input 컴포넌트
const DebouncedInput = ({
  value,
  onChange,
  onCommit,
  type = "text",
  debounce = 0,
  ...props
}: React.InputHTMLAttributes<HTMLInputElement> & {
  onCommit?: (value: any) => void;
  debounce?: number;
}) => {
  const [localValue, setLocalValue] = useState(value);
  const [isEditing, setIsEditing] = useState(false);

  useEffect(() => {
    if (!isEditing) {
      setLocalValue(value);
    }
  }, [value, isEditing]);

  // 색상 입력 등을 위한 디바운스 커밋
  useEffect(() => {
    if (debounce > 0 && isEditing && onCommit) {
      const timer = setTimeout(() => {
        onCommit(type === "number" ? parseFloat(localValue as string) : localValue);
      }, debounce);
      return () => clearTimeout(timer);
    }
  }, [localValue, debounce, isEditing, onCommit, type]);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setLocalValue(e.target.value);
    if (onChange) onChange(e);
  };

  const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
    setIsEditing(false);
    if (onCommit && debounce === 0) {
      // 값이 변경되었을 때만 커밋하도록 하면 좋겠지만,
      // 부모 상태와 비교하기 어려우므로 항상 커밋 (handleObjectUpdate 내부에서 처리됨)
      onCommit(type === "number" ? parseFloat(localValue as string) : localValue);
    }
    if (props.onBlur) props.onBlur(e);
  };

  const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
    setIsEditing(true);
    if (props.onFocus) props.onFocus(e);
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") {
      e.currentTarget.blur();
    }
    if (props.onKeyDown) props.onKeyDown(e);
  };

  return (
    <Input
      {...props}
      type={type}
      value={localValue}
      onChange={handleChange}
      onBlur={handleBlur}
      onFocus={handleFocus}
      onKeyDown={handleKeyDown}
    />
  );
};

// 백엔드 DB 객체 타입 (snake_case)
interface DbObject {
  id: number;
  object_type: ObjectType;
  object_name: string;
  position_x: string;
  position_y: string;
  position_z: string;
  size_x: string;
  size_y: string;
  size_z: string;
  rotation?: string;
  color: string;
  area_key?: string;
  loca_key?: string;
  loc_type?: string;
  material_count?: number;
  material_preview_height?: string;
  parent_id?: number;
  display_order?: number;
  locked?: boolean;
  visible?: boolean;
}

const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), {
  ssr: false,
  loading: () => (
    <div className="bg-muted flex h-full items-center justify-center">
      <Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
    </div>
  ),
});

interface DigitalTwinEditorProps {
  layoutId: number;
  layoutName: string;
  onBack: () => void;
}

export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: DigitalTwinEditorProps) {
  const { toast } = useToast();
  const [placedObjects, setPlacedObjects] = useState<PlacedObject[]>([]);
  const [selectedObject, setSelectedObject] = useState<PlacedObject | null>(null);
  const [draggedTool, setDraggedTool] = useState<ToolType | null>(null);
  const [draggedAreaData, setDraggedAreaData] = useState<Area | null>(null); // 드래그 중인 Area 정보
  const [draggedLocationData, setDraggedLocationData] = useState<Location | null>(null); // 드래그 중인 Location 정보
  const [previewPosition, setPreviewPosition] = useState<{ x: number; z: number } | null>(null); // 드래그 프리뷰 위치
  const [isLoading, setIsLoading] = useState(true);
  const [isSaving, setIsSaving] = useState(false);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  const [externalDbConnections, setExternalDbConnections] = useState<{ id: number; name: string; db_type: string }[]>(
    [],
  );
  const [selectedDbConnection, setSelectedDbConnection] = useState<number | null>(null);
  const [selectedWarehouse, setSelectedWarehouse] = useState<string | null>(null);
  const [warehouses, setWarehouses] = useState<Warehouse[]>([]);
  const [availableAreas, setAvailableAreas] = useState<Area[]>([]);
  const [availableLocations, setAvailableLocations] = useState<Location[]>([]);
  const [nextObjectId, setNextObjectId] = useState(-1);
  const [loadingWarehouses, setLoadingWarehouses] = useState(false);
  const [loadingAreas, setLoadingAreas] = useState(false);
  const [loadingLocations, setLoadingLocations] = useState(false);
  const [materials, setMaterials] = useState<MaterialData[]>([]);
  const [loadingMaterials, setLoadingMaterials] = useState(false);
  const [showMaterialPanel, setShowMaterialPanel] = useState(false);

  // 매핑 템플릿
  const [mappingTemplates, setMappingTemplates] = useState<DigitalTwinMappingTemplate[]>([]);
  const [selectedTemplateId, setSelectedTemplateId] = useState<string>("");
  const [loadingTemplates, setLoadingTemplates] = useState(false);
  const [isSaveTemplateDialogOpen, setIsSaveTemplateDialogOpen] = useState(false);
  const [newTemplateName, setNewTemplateName] = useState("");
  const [newTemplateDescription, setNewTemplateDescription] = useState("");

  // 동적 계층 구조 설정
  const [hierarchyConfig, setHierarchyConfig] = useState<HierarchyConfig | null>(null);
  const [availableTables, setAvailableTables] = useState<Array<{ table_name: string; description?: string }>>([]);
  const [loadingTables, setLoadingTables] = useState(false);

  // 레거시: 테이블 매핑 (구 Area/Location 방식 호환용)
  const [selectedTables, setSelectedTables] = useState<{
    warehouse: string;
    area: string;
    location: string;
    material: string;
  }>({
    warehouse: "",
    area: "",
    location: "",
    material: "",
  });
  const [tableColumns, setTableColumns] = useState<{
    warehouse?: { name: string; code: string };
    area?: { name: string; code: string; warehouseCode: string };
    location?: { name: string; code: string; areaCode: string };
  }>({});
  const [selectedColumns, setSelectedColumns] = useState<{
    warehouseKey: string;
    warehouseName: string;
    areaKey: string;
    areaName: string;
    areaWarehouseKey: string;
    locationKey: string;
    locationName: string;
    locationAreaKey: string;
    materialKey?: string;
  }>({
    warehouseKey: "WAREKEY",
    warehouseName: "WARENAME",
    areaKey: "AREAKEY",
    areaName: "AREANAME",
    areaWarehouseKey: "WAREKEY",
    locationKey: "LOCAKEY",
    locationName: "LOCANAME",
    locationAreaKey: "AREAKEY",
    materialKey: "LOCAKEY",
  });

  // placedObjects를 YardPlacement 형식으로 변환 (useMemo로 최적화)
  const placements = useMemo(() => {
    const now = new Date().toISOString(); // 한 번만 생성
    return placedObjects.map((obj) => ({
      id: obj.id,
      yard_layout_id: layoutId,
      material_code: null,
      material_name: obj.name,
      name: obj.name, // 객체 이름 (야드 이름 표시용)
      quantity: null,
      unit: null,
      position_x: obj.position.x,
      position_y: obj.position.y,
      position_z: obj.position.z,
      size_x: obj.size.x,
      size_y: obj.size.y,
      size_z: obj.size.z,
      color: obj.color,
      data_source_type: obj.type,
      data_source_config: null,
      data_binding: null,
      created_at: now, // 고정된 값 사용
      updated_at: now, // 고정된 값 사용
      material_count: obj.materialCount,
      material_preview_height: obj.materialPreview?.height,
    }));
  }, [placedObjects, layoutId]);

  // 외부 DB 또는 레이아웃 타입이 변경될 때 템플릿 목록 로드
  useEffect(() => {
    const loadTemplates = async () => {
      if (!selectedDbConnection) {
        setMappingTemplates([]);
        setSelectedTemplateId("");
        return;
      }

      try {
        setLoadingTemplates(true);
        const response = await getMappingTemplates({
          externalDbConnectionId: selectedDbConnection,
          layoutType: "yard-3d",
        });
        if (response.success && response.data) {
          setMappingTemplates(response.data);
        } else {
          setMappingTemplates([]);
        }
      } catch (error) {
        console.error("매핑 템플릿 목록 조회 실패:", error);
      } finally {
        setLoadingTemplates(false);
      }
    };

    loadTemplates();
  }, [selectedDbConnection]);

  // 외부 DB 연결 목록 로드
  useEffect(() => {
    const loadExternalDbConnections = async () => {
      try {
        const connections = await ExternalDbConnectionAPI.getConnections({ is_active: "Y" });
        console.log("🔍 외부 DB 연결 목록 (is_active=Y):", connections);
        console.log(
          "🔍 연결 ID들:",
          connections.map((c) => c.id),
        );
        setExternalDbConnections(
          connections.map((conn) => ({
            id: conn.id!,
            name: conn.connection_name,
            db_type: conn.db_type,
          })),
        );
      } catch (error) {
        console.error("외부 DB 연결 목록 조회 실패:", error);
        toast({
          variant: "destructive",
          title: "오류",
          description: "외부 DB 연결 목록을 불러오는데 실패했습니다.",
        });
      }
    };

    loadExternalDbConnections();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // 컴포넌트 마운트 시 한 번만 실행

  // 외부 DB 선택 시 테이블 목록 로드
  useEffect(() => {
    if (!selectedDbConnection) {
      setAvailableTables([]);
      setSelectedTables({ warehouse: "", area: "", location: "", material: "" }); // warehouse는 HierarchyConfigPanel에서 관리
      return;
    }

    const loadTables = async () => {
      try {
        setLoadingTables(true);
        // 외부 DB 메타데이터 API 사용 (테이블 + 설명)
        const response = await ExternalDbConnectionAPI.getTables(selectedDbConnection);
        if (response.success && response.data) {
          const rawTables = response.data as any[];
          const normalized = rawTables.map((t: any) =>
            typeof t === "string"
              ? { table_name: t }
              : {
                  table_name: t.table_name || t.TABLE_NAME || String(t),
                  description: t.description || t.table_description || undefined,
                },
          );
          setAvailableTables(normalized);
          console.log("📋 테이블 목록:", normalized);
        } else {
          setAvailableTables([]);
          console.warn("테이블 목록 조회 실패:", response.message || response.error);
        }
      } catch (error) {
        console.error("테이블 목록 조회 실패:", error);
        toast({
          variant: "destructive",
          title: "오류",
          description: "테이블 목록을 불러오는데 실패했습니다.",
        });
      } finally {
        setLoadingTables(false);
      }
    };

    loadTables();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedDbConnection]);

  // 동적 계층 구조 데이터 로드
  useEffect(() => {
    const loadHierarchy = async () => {
      if (!selectedDbConnection || !hierarchyConfig) {
        return;
      }

      // 필수 필드 검증: 창고가 선택되었는지 확인
      if (!hierarchyConfig.warehouseKey) {
        return;
      }

      // 레벨 설정 검증
      if (!hierarchyConfig.levels || hierarchyConfig.levels.length === 0) {
        return;
      }

      // 각 레벨의 필수 필드 검증
      for (const level of hierarchyConfig.levels) {
        if (!level.tableName || !level.keyColumn || !level.nameColumn) {
          return;
        }
      }

      try {
        const response = await getHierarchyData(selectedDbConnection, hierarchyConfig);
        if (response.success && response.data) {
          const { warehouse, levels, materials } = response.data;

          // 창고 데이터 설정
          if (warehouse) {
            setWarehouses(warehouse);
          }

          // 레벨 데이터 설정
          // 기존 호환성을 위해 레벨 1 -> Area, 레벨 2 -> Location으로 매핑
          // TODO: UI를 동적으로 생성하도록 개선 필요
          const level1 = levels.find((l) => l.level === 1);
          if (level1) {
            setAvailableAreas(level1.data);
          }

          const level2 = levels.find((l) => l.level === 2);
          if (level2) {
            setAvailableLocations(level2.data);
          }

          console.log("계층 데이터 로드 완료:", response.data);
        }
      } catch (error) {
        console.error("계층 데이터 로드 실패:", error);
      }
    };

    loadHierarchy();
  }, [selectedDbConnection, hierarchyConfig]);

  // 테이블 컬럼 로드
  const loadColumnsForTable = async (tableName: string, type: "warehouse" | "area" | "location" | "material") => {
    if (!selectedDbConnection || !tableName) return;

    try {
      const { getTablePreview } = await import("@/lib/api/digitalTwin");
      const response = await getTablePreview(selectedDbConnection, tableName);

      console.log(`📊 ${type} 테이블 미리보기:`, response);

      if (response.success && response.data && response.data.length > 0) {
        const columns = Object.keys(response.data[0]);
        setTableColumns((prev) => ({ ...prev, [type]: columns }));

        // 자동 매핑 시도 (기본값 설정)
        if (type === "warehouse") {
          const keyCol = columns.find((c) => c.includes("KEY") || c.includes("ID")) || columns[0];
          const nameCol = columns.find((c) => c.includes("NAME") || c.includes("NAM")) || columns[1] || columns[0];
          setSelectedColumns((prev) => ({ ...prev, warehouseKey: keyCol, warehouseName: nameCol }));
        }
      } else {
        console.warn(`⚠️ ${tableName} 테이블에 데이터가 없습니다.`);
        toast({
          variant: "default", // destructive 대신 default로 변경 (단순 알림)
          title: "데이터 없음",
          description: `${tableName} 테이블에 데이터가 없습니다.`,
        });
      }
    } catch (error) {
      console.error(`컬럼 로드 실패 (${tableName}):`, error);
    }
  };

  // 외부 DB 선택 시 창고 목록 로드 (테이블이 선택되어 있을 때만)
  useEffect(() => {
    if (!selectedDbConnection || !selectedTables.warehouse) {
      setWarehouses([]);
      setSelectedWarehouse(null);
      return;
    }

    const loadWarehouses = async () => {
      if (!hierarchyConfig?.warehouse?.tableName) {
        return;
      }
      try {
        setLoadingWarehouses(true);
        const response = await getWarehouses(selectedDbConnection, hierarchyConfig.warehouse.tableName);
        console.log("📦 창고 API 응답:", response);
        if (response.success && response.data) {
          console.log("📦 창고 데이터:", response.data);
          setWarehouses(response.data);
        } else {
          // 외부 DB 연결이 유효하지 않으면 선택 초기화
          console.warn("외부 DB 연결이 유효하지 않습니다:", selectedDbConnection);
          setSelectedDbConnection(null);
          toast({
            variant: "destructive",
            title: "외부 DB 연결 오류",
            description: "저장된 외부 DB 연결을 찾을 수 없습니다. 다시 선택해주세요.",
          });
        }
      } catch (error: any) {
        console.error("창고 목록 조회 실패:", error);
        // 외부 DB 연결이 존재하지 않으면 선택 초기화
        if (error.response?.status === 500 && error.response?.data?.error?.includes("연결 정보를 찾을 수 없습니다")) {
          setSelectedDbConnection(null);
          toast({
            variant: "destructive",
            title: "외부 DB 연결 오류",
            description: "저장된 외부 DB 연결을 찾을 수 없습니다. 다시 선택해주세요.",
          });
        } else {
          toast({
            variant: "destructive",
            title: "오류",
            description: "창고 목록을 불러오는데 실패했습니다.",
          });
        }
      } finally {
        setLoadingWarehouses(false);
      }
    };

    loadWarehouses();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedDbConnection, hierarchyConfig?.warehouse?.tableName]); // hierarchyConfig.warehouse.tableName 추가

  // 창고 선택 시 Area 목록 로드
  useEffect(() => {
    if (!selectedDbConnection || !selectedWarehouse) {
      setAvailableAreas([]);
      return;
    }

    // Area 테이블명이 설정되지 않으면 API 호출 스킵
    if (!selectedTables.area) {
      setAvailableAreas([]);
      return;
    }

    const loadAreas = async () => {
      try {
        setLoadingAreas(true);
        const response = await getAreas(selectedDbConnection, selectedTables.area, selectedWarehouse);
        if (response.success && response.data) {
          setAvailableAreas(response.data);
        }
      } catch (error) {
        console.error("Area 목록 조회 실패:", error);
        toast({
          variant: "destructive",
          title: "오류",
          description: "Area 목록을 불러오는데 실패했습니다.",
        });
      } finally {
        setLoadingAreas(false);
      }
    };

    loadAreas();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedDbConnection, selectedWarehouse, selectedTables.area]); // toast 제거, area 테이블 추가

  // 레이아웃 데이터 로드
  const [layoutData, setLayoutData] = useState<{ layout: any; objects: any[] } | null>(null);
  const [isRefreshing, setIsRefreshing] = useState(false);

  // 레이아웃 로드 함수
  const loadLayout = async () => {
    try {
      setIsLoading(true);
      const response = await getLayoutById(layoutId);

      if (response.success && response.data) {
        const { layout, objects } = response.data;
        setLayoutData({ layout, objects }); // 레이아웃 데이터 저장

        // 외부 DB 연결 ID 복원
        if (layout.external_db_connection_id) {
          setSelectedDbConnection(layout.external_db_connection_id);
        }

        // 계층 구조 설정 로드
        if (layout.hierarchy_config) {
          try {
            // hierarchy_config가 문자열이면 파싱, 객체면 그대로 사용
            const config =
              typeof layout.hierarchy_config === "string"
                ? JSON.parse(layout.hierarchy_config)
                : layout.hierarchy_config;
            setHierarchyConfig(config);

            // 선택된 테이블 정보도 복원
            const newSelectedTables: any = {
              warehouse: config.warehouse?.tableName || "",
              area: "",
              location: "",
              material: "",
            };

            if (config.levels && config.levels.length > 0) {
              // 레벨 1 = Area
              if (config.levels[0]?.tableName) {
                newSelectedTables.area = config.levels[0].tableName;
              }
              // 레벨 2 = Location
              if (config.levels[1]?.tableName) {
                newSelectedTables.location = config.levels[1].tableName;
              }
            }

            // 자재 테이블 정보
            if (config.material?.tableName) {
              newSelectedTables.material = config.material.tableName;
            }

            setSelectedTables(newSelectedTables);
          } catch (e) {
            console.error("계층 구조 설정 파싱 실패:", e);
          }
        }

        // 객체 데이터 변환 (DB -> PlacedObject)
        const loadedObjects: PlacedObject[] = (objects as unknown as DbObject[]).map((obj) => ({
          id: obj.id,
          type: obj.object_type,
          name: obj.object_name,
          position: {
            x: parseFloat(obj.position_x),
            y: parseFloat(obj.position_y),
            z: parseFloat(obj.position_z),
          },
          size: {
            x: parseFloat(obj.size_x),
            y: parseFloat(obj.size_y),
            z: parseFloat(obj.size_z),
          },
          rotation: obj.rotation ? parseFloat(obj.rotation) : 0,
          color: obj.color,
          areaKey: obj.area_key,
          locaKey: obj.loca_key,
          locType: obj.loc_type,
          materialCount: obj.loc_type === "STP" ? undefined : obj.material_count,
          materialPreview:
            obj.loc_type === "STP" || !obj.material_preview_height
              ? undefined
              : { height: parseFloat(obj.material_preview_height) },
          parentId: obj.parent_id,
          displayOrder: obj.display_order,
          locked: obj.locked,
          visible: obj.visible !== false,
          hierarchyLevel: obj.hierarchy_level || 1,
          parentKey: obj.parent_key,
          externalKey: obj.external_key,
        }));

        setPlacedObjects(loadedObjects);

        // 다음 임시 ID 설정 (기존 ID 중 최소값 - 1)
        const minId = Math.min(...loadedObjects.map((o) => o.id), 0);
        setNextObjectId(minId - 1);

        setHasUnsavedChanges(false);

        toast({
          title: "레이아웃 불러오기 완료",
          description: `${loadedObjects.length}개의 객체를 불러왔습니다.`,
        });

        // Location 객체들의 자재 개수 로드 (직접 dbConnectionId와 materialTableName 전달)
        const dbConnectionId = layout.external_db_connection_id;
        const hierarchyConfigParsed =
          typeof layout.hierarchy_config === "string" ? JSON.parse(layout.hierarchy_config) : layout.hierarchy_config;
        const materialTableName = hierarchyConfigParsed?.material?.tableName;

        const locationObjects = loadedObjects.filter(
          (obj) =>
            (obj.type === "location-bed" ||
              obj.type === "location-stp" ||
              obj.type === "location-temp" ||
              obj.type === "location-dest") &&
            obj.locaKey,
        );
        if (locationObjects.length > 0 && dbConnectionId && materialTableName) {
          const locaKeys = locationObjects.map((obj) => obj.locaKey!);
          setTimeout(() => {
            loadMaterialCountsForLocations(locaKeys, dbConnectionId, materialTableName);
          }, 100);
        }
      } else {
        throw new Error(response.error || "레이아웃 조회 실패");
      }
    } catch (error) {
      console.error("레이아웃 로드 실패:", error);
      const errorMessage = error instanceof Error ? error.message : "레이아웃을 불러오는데 실패했습니다.";
      toast({
        variant: "destructive",
        title: "오류",
        description: errorMessage,
      });
    } finally {
      setIsLoading(false);
    }
  };

  // 위젯 새로고침 핸들러
  const handleRefresh = async () => {
    if (hasUnsavedChanges) {
      const confirmed = window.confirm(
        "저장되지 않은 변경사항이 있습니다. 새로고침하면 변경사항이 사라집니다. 계속하시겠습니까?",
      );
      if (!confirmed) return;
    }
    setIsRefreshing(true);
    setSelectedObject(null);
    setMaterials([]);
    await loadLayout();
    setIsRefreshing(false);
    toast({
      title: "새로고침 완료",
      description: "데이터가 갱신되었습니다.",
    });
  };

  // 초기 로드
  useEffect(() => {
    loadLayout();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [layoutId]);

  // 외부 DB 연결 자동 선택 (externalDbConnections 로드 완료 시)
  useEffect(() => {
    console.log("🔍 useEffect 실행:", {
      layoutData: !!layoutData,
      external_db_connection_id: layoutData?.layout?.external_db_connection_id,
      externalDbConnections: externalDbConnections.length,
    });

    if (!layoutData || !layoutData.layout.external_db_connection_id || externalDbConnections.length === 0) {
      console.log("🔍 조건 미충족으로 종료");
      return;
    }

    const layout = layoutData.layout;
    console.log("🔍 외부 DB 연결 자동 선택 시도");
    console.log("🔍 레이아웃의 external_db_connection_id:", layout.external_db_connection_id);
    console.log("🔍 사용 가능한 연결 목록:", externalDbConnections);

    const connectionExists = externalDbConnections.some((conn) => conn.id === layout.external_db_connection_id);
    console.log("🔍 연결 존재 여부:", connectionExists);

    if (connectionExists) {
      setSelectedDbConnection(layout.external_db_connection_id);
      if (layout.warehouse_key) {
        setSelectedWarehouse(layout.warehouse_key);
      }
      console.log("✅ 외부 DB 연결 자동 선택 완료:", layout.external_db_connection_id);
    } else {
      console.warn("⚠️ 저장된 외부 DB 연결을 찾을 수 없습니다:", layout.external_db_connection_id);
      console.warn(
        "⚠️ 사용 가능한 연결 ID들:",
        externalDbConnections.map((c) => c.id),
      );
      toast({
        variant: "destructive",
        title: "외부 DB 연결 오류",
        description: "저장된 외부 DB 연결을 찾을 수 없습니다. 다시 선택해주세요.",
      });
    }
  }, [layoutData, externalDbConnections]); // layoutData와 externalDbConnections가 모두 준비되면 실행

  // 도구 타입별 기본 설정
  const getToolDefaults = (type: ToolType): Partial<PlacedObject> => {
    switch (type) {
      case "area":
        return {
          name: "영역",
          size: { x: 20, y: 0.1, z: 20 }, // 4x4 칸
          color: "#3b82f6", // 파란색
        };
      case "location-bed":
        return {
          name: "베드(BED)",
          size: { x: 5, y: 2, z: 5 }, // 1x1 칸
          color: "#10b981", // 에메랄드
        };
      case "location-stp":
        return {
          name: "정차포인트(STP)",
          size: { x: 5, y: 0.5, z: 5 }, // 1x1 칸, 낮은 높이
          color: "#f59e0b", // 주황색
        };
      case "location-temp":
        return {
          name: "임시베드(TMP)",
          size: { x: 5, y: 2, z: 5 }, // 베드와 동일
          color: "#10b981", // 베드와 동일
        };
      case "location-dest":
        return {
          name: "지정착지(DES)",
          size: { x: 5, y: 2, z: 5 }, // 베드와 동일
          color: "#10b981", // 베드와 동일
        };
      // case "crane-gantry":
      //   return {
      //     name: "겐트리 크레인",
      //     size: { x: 5, y: 8, z: 5 }, // 1x1 칸
      //     color: "#22c55e", // 녹색
      //   };
      case "crane-mobile":
        return {
          name: "크레인",
          size: { x: 5, y: 6, z: 5 }, // 1x1 칸
          color: "#eab308", // 노란색
        };
      case "rack":
        return {
          name: "랙",
          size: { x: 5, y: 3, z: 5 }, // 1x1 칸
          color: "#a855f7", // 보라색
        };
      // case "material":
      //   return {
      //     name: "자재",
      //     size: { x: 5, y: 2, z: 5 }, // 1x1 칸
      //     color: "#ef4444", // 빨간색
      //   };
    }
  };

  // 도구 드래그 시작
  const handleToolDragStart = (toolType: ToolType) => {
    setDraggedTool(toolType);
  };

  // 캔버스에 드롭
  const handleCanvasDrop = async (x: number, z: number) => {
    if (!draggedTool) return;

    const defaults = getToolDefaults(draggedTool);

    // Area는 바닥(y=0.05)에, 다른 객체는 중앙 정렬
    let yPosition = draggedTool === "area" ? 0.05 : (defaults.size?.y || 1) / 2;

    // 외부 DB 데이터에서 드래그한 경우 해당 정보 사용
    let objectName = defaults.name || "새 객체";
    let areaKey: string | undefined = undefined;
    let locaKey: string | undefined = undefined;
    let locType: string | undefined = undefined;
    let hierarchyLevel = 1;
    let parentKey: string | undefined = undefined;
    let externalKey: string | undefined = undefined;

    if (draggedTool === "area" && draggedAreaData) {
      objectName = draggedAreaData.AREANAME;
      areaKey = draggedAreaData.AREAKEY;
      // 계층 정보 설정 (예: Area는 레벨 1)
      hierarchyLevel = 1;
      externalKey = draggedAreaData.AREAKEY;
    } else if (
      (draggedTool === "location-bed" ||
        draggedTool === "location-stp" ||
        draggedTool === "location-temp" ||
        draggedTool === "location-dest") &&
      draggedLocationData
    ) {
      objectName = draggedLocationData.LOCANAME || draggedLocationData.LOCAKEY;
      areaKey = draggedLocationData.AREAKEY;
      locaKey = draggedLocationData.LOCAKEY;
      locType = draggedLocationData.LOCTYPE;
      // 계층 정보 설정 (예: Location은 레벨 2)
      hierarchyLevel = 2;
      parentKey = draggedLocationData.AREAKEY;
      externalKey = draggedLocationData.LOCAKEY;
    }

    // 기본 크기 설정
    let objectSize = defaults.size || { x: 5, y: 5, z: 5 };

    // Location 배치 시 자재 개수에 따라 높이 자동 설정 (BED/TMP/DES만 대상, STP는 자재 미적재)
    if (
      (draggedTool === "location-bed" || draggedTool === "location-temp" || draggedTool === "location-dest") &&
      locaKey &&
      selectedDbConnection &&
      hierarchyConfig?.material
    ) {
      try {
        // 해당 Location의 자재 개수 조회
        const countsResponse = await getMaterialCounts(selectedDbConnection, hierarchyConfig.material.tableName, [
          locaKey,
        ]);

        if (countsResponse.success && countsResponse.data && countsResponse.data.length > 0) {
          const materialCount = countsResponse.data[0].count;

          // 자재 개수에 비례해서 높이(Y축) 설정 (최소 5, 최대 30)
          // 자재 1개 = 높이 5, 자재 10개 = 높이 15, 자재 50개 = 높이 30
          const calculatedHeight = Math.min(30, Math.max(5, 5 + materialCount * 0.5));

          objectSize = {
            ...objectSize,
            y: calculatedHeight, // Y축이 높이!
          };

          // 높이가 높아진 만큼 Y 위치도 올려서 바닥을 뚫지 않게 조정
          yPosition = calculatedHeight / 2;
        }
      } catch (error) {
        console.error("자재 개수 조회 실패, 기본 높이 사용:", error);
      }
    }

    const newObject: PlacedObject = {
      id: nextObjectId,
      type: draggedTool,
      name: objectName,
      position: { x, y: yPosition, z },
      size: objectSize,
      color: OBJECT_COLORS[draggedTool] || DEFAULT_COLOR, // 타입별 기본 색상
      areaKey,
      locaKey,
      locType,
      hierarchyLevel,
      parentKey,
      externalKey,
    };

    // 공간적 종속성 검증
    if (hierarchyConfig && hierarchyLevel > 1) {
      const validation = validateSpatialContainment(
        {
          id: newObject.id,
          position: newObject.position,
          size: newObject.size,
          hierarchyLevel: newObject.hierarchyLevel || 1,
          parentId: newObject.parentId,
        },
        placedObjects.map((obj) => ({
          id: obj.id,
          position: obj.position,
          size: obj.size,
          hierarchyLevel: obj.hierarchyLevel || 1,
          parentId: obj.parentId,
        })),
      );

      if (!validation.valid) {
        toast({
          variant: "destructive",
          title: "배치 오류",
          description: "하위 영역은 반드시 상위 영역 내부에 배치되어야 합니다.",
        });
        return;
      }

      // 부모 ID 설정 및 논리적 유효성 검사
      if (validation.parent) {
        // 1. 부모 객체 찾기
        const parentObj = placedObjects.find((obj) => obj.id === validation.parent!.id);

        // 2. 논리적 키 검사 (DB에서 가져온 데이터인 경우)
        if (parentObj && parentObj.externalKey && newObject.parentKey) {
          if (parentObj.externalKey !== newObject.parentKey) {
            toast({
              variant: "destructive",
              title: "배치 오류",
              description: `이 Location은 '${newObject.parentKey}' Area에만 배치할 수 있습니다. (현재 선택된 Area: ${parentObj.externalKey})`,
            });
            return;
          }
        }

        newObject.parentId = validation.parent.id;
      } else if (newObject.parentKey) {
        // DB 데이터인데 부모 영역 위에 놓이지 않은 경우
        toast({
          variant: "destructive",
          title: "배치 오류",
          description: `이 Location은 '${newObject.parentKey}' Area 내부에 배치해야 합니다.`,
        });
        return;
      }
    }

    setPlacedObjects((prev) => [...prev, newObject]);
    setSelectedObject(newObject);
    setNextObjectId((prev) => prev - 1);
    setHasUnsavedChanges(true);
    setDraggedTool(null);
    setDraggedAreaData(null);
    setDraggedLocationData(null);

    // Location 배치 시 자재 개수 로드 (BED/TMP/DES만 대상, STP는 자재 미적재)
    if (
      (draggedTool === "location-bed" || draggedTool === "location-temp" || draggedTool === "location-dest") &&
      locaKey
    ) {
      // 새 객체 추가 후 자재 개수 로드 (약간의 딜레이를 두어 state 업데이트 완료 후 실행)
      setTimeout(() => {
        loadMaterialCountsForLocations([locaKey!]);
      }, 100);
    }
  };

  // Location의 자재 목록 로드
  const loadMaterialsForLocation = async (locaKey: string) => {
    console.log("🔍 자재 조회 시작:", { locaKey, selectedDbConnection, material: hierarchyConfig?.material });

    if (!selectedDbConnection || !hierarchyConfig?.material) {
      console.error("❌ 설정 누락:", { selectedDbConnection, material: hierarchyConfig?.material });
      toast({
        variant: "destructive",
        title: "자재 조회 실패",
        description: "자재 테이블 설정이 필요합니다.",
      });
      return;
    }

    try {
      setLoadingMaterials(true);
      setShowMaterialPanel(true);

      const materialConfig = {
        ...hierarchyConfig.material,
        locaKey: locaKey,
      };
      console.log("📡 API 호출:", { externalDbConnectionId: selectedDbConnection, materialConfig });

      const response = await getMaterials(selectedDbConnection, materialConfig);
      console.log("📦 API 응답:", response);
      if (response.success && response.data) {
        // layerColumn이 있으면 정렬
        const sortedMaterials = hierarchyConfig.material.layerColumn
          ? response.data.sort((a: any, b: any) => {
              const aLayer = a[hierarchyConfig.material!.layerColumn!] || 0;
              const bLayer = b[hierarchyConfig.material!.layerColumn!] || 0;
              return aLayer - bLayer;
            })
          : response.data;
        setMaterials(sortedMaterials);
      } else {
        setMaterials([]);
        toast({
          variant: "destructive",
          title: "자재 조회 실패",
          description: response.error || "자재 목록을 불러올 수 없습니다.",
        });
      }
    } catch (error) {
      console.error("자재 로드 실패:", error);
      setMaterials([]);
      toast({
        variant: "destructive",
        title: "오류",
        description: "자재 목록을 불러오는데 실패했습니다.",
      });
    } finally {
      setLoadingMaterials(false);
    }
  };

  // 객체 클릭
  const handleObjectClick = (objectId: number | null) => {
    if (objectId === null) {
      setSelectedObject(null);
      setShowMaterialPanel(false);
      return;
    }

    const obj = placedObjects.find((o) => o.id === objectId);
    setSelectedObject(obj || null);

    // Area를 클릭한 경우, 해당 Area의 Location 목록 로드
    if (obj && obj.type === "area" && obj.areaKey && selectedDbConnection) {
      loadLocationsForArea(obj.areaKey);
      setShowMaterialPanel(false);
    }
    // Location을 클릭한 경우, 해당 Location의 자재 목록 로드 (STP는 자재 미적재이므로 제외)
    else if (
      obj &&
      (obj.type === "location-bed" || obj.type === "location-temp" || obj.type === "location-dest") &&
      obj.locaKey &&
      selectedDbConnection
    ) {
      loadMaterialsForLocation(obj.locaKey);
    } else {
      setShowMaterialPanel(false);
    }
  };

  // Location별 자재 개수 로드 (locaKeys를 직접 받음)
  const loadMaterialCountsForLocations = async (
    locaKeys: string[],
    dbConnectionId?: number,
    materialTableName?: string,
  ) => {
    const connectionId = dbConnectionId || selectedDbConnection;
    const tableName = materialTableName || selectedTables.material;
    if (!connectionId || locaKeys.length === 0) return;

    try {
      const response = await getMaterialCounts(connectionId, tableName, locaKeys);
      console.log("📊 자재 개수 API 응답:", response);

      if (response.success && response.data) {
        // 각 Location 객체에 자재 개수 업데이트 (STP는 자재 미적재이므로 제외)
        setPlacedObjects((prev) =>
          prev.map((obj) => {
            if (
              !obj.locaKey ||
              obj.type === "location-stp" // STP는 자재 없음
            ) {
              return obj;
            }
            // 백엔드 응답 필드명: location_key, count (대소문자 모두 체크)
            const materialCount = response.data?.find(
              (mc: any) => mc.LOCAKEY === obj.locaKey || mc.location_key === obj.locaKey || mc.locakey === obj.locaKey,
            );
            if (materialCount) {
              // count 또는 material_count 필드 사용
              const count = materialCount.count || materialCount.material_count || 0;
              const maxLayer = materialCount.max_layer || count;
              console.log(`📊 ${obj.locaKey}: 자재 ${count}개`);
              return {
                ...obj,
                materialCount: Number(count),
                materialPreview: {
                  height: maxLayer * 1.5, // 층당 1.5 높이 (시각적)
                },
              };
            }
            return obj;
          }),
        );
      }
    } catch (error) {
      console.error("자재 개수 로드 실패:", error);
    }
  };

  // 특정 Area의 Location 목록 로드
  const loadLocationsForArea = async (areaKey: string) => {
    if (!selectedDbConnection) return;

    try {
      setLoadingLocations(true);
      const response = await getLocations(selectedDbConnection, selectedTables.location, areaKey);
      if (response.success && response.data) {
        setAvailableLocations(response.data);
        toast({
          title: "Location 로드 완료",
          description: `${response.data.length}개 Location을 불러왔습니다.`,
        });
      }
    } catch (error) {
      console.error("Location 목록 조회 실패:", error);
      toast({
        variant: "destructive",
        title: "오류",
        description: "Location 목록을 불러오는데 실패했습니다.",
      });
    } finally {
      setLoadingLocations(false);
    }
  };

  // 매핑 템플릿 적용
  const handleApplyTemplate = (templateId: string) => {
    if (!templateId) return;
    const template = mappingTemplates.find((t) => t.id === templateId);
    if (!template) {
      toast({
        variant: "destructive",
        title: "템플릿 적용 실패",
        description: "선택한 템플릿을 찾을 수 없습니다.",
      });
      return;
    }

    const config = template.config as HierarchyConfig;
    setHierarchyConfig(config);

    // 선택된 테이블 정보 동기화
    const newSelectedTables: any = {
      warehouse: config.warehouse?.tableName || "",
      area: "",
      location: "",
      material: "",
    };

    if (config.levels && config.levels.length > 0) {
      // 레벨 1 = Area
      if (config.levels[0]?.tableName) {
        newSelectedTables.area = config.levels[0].tableName;
      }
      // 레벨 2 = Location
      if (config.levels[1]?.tableName) {
        newSelectedTables.location = config.levels[1].tableName;
      }
    }

    if (config.material?.tableName) {
      newSelectedTables.material = config.material.tableName;
    }

    setSelectedTables(newSelectedTables);
    setSelectedWarehouse(config.warehouseKey || null);
    setHasUnsavedChanges(true);

    toast({
      title: "템플릿 적용 완료",
      description: `"${template.name}" 템플릿이 적용되었습니다.`,
    });
  };

  // 매핑 템플릿 저장
  const handleSaveTemplate = async () => {
    if (!selectedDbConnection || !hierarchyConfig) {
      toast({
        variant: "destructive",
        title: "템플릿 저장 불가",
        description: "외부 DB와 계층 설정을 먼저 완료해주세요.",
      });
      return;
    }

    if (!newTemplateName.trim()) {
      toast({
        variant: "destructive",
        title: "템플릿 이름 필요",
        description: "템플릿 이름을 입력해주세요.",
      });
      return;
    }

    try {
      const response = await createMappingTemplate({
        name: newTemplateName.trim(),
        description: newTemplateDescription.trim() || undefined,
        externalDbConnectionId: selectedDbConnection,
        layoutType: "yard-3d",
        config: hierarchyConfig,
      });

      if (response.success && response.data) {
        setMappingTemplates((prev) => [response.data!, ...prev]);
        setIsSaveTemplateDialogOpen(false);
        setNewTemplateName("");
        setNewTemplateDescription("");
        toast({
          title: "템플릿 저장 완료",
          description: `"${response.data.name}" 템플릿이 저장되었습니다.`,
        });
      } else {
        toast({
          variant: "destructive",
          title: "템플릿 저장 실패",
          description: response.error || "템플릿을 저장하지 못했습니다.",
        });
      }
    } catch (error) {
      console.error("매핑 템플릿 저장 실패:", error);
      toast({
        variant: "destructive",
        title: "템플릿 저장 실패",
        description: "템플릿을 저장하는 중 오류가 발생했습니다.",
      });
    }
  };

  // 객체 이동
  const handleObjectMove = (objectId: number, newX: number, newZ: number, newY?: number) => {
    setPlacedObjects((prev) => {
      const targetObj = prev.find((obj) => obj.id === objectId);
      if (!targetObj) return prev;

      const oldPosition = targetObj.position;
      const newPosition = {
        x: newX,
        y: newY !== undefined ? newY : oldPosition.y,
        z: newZ,
      };

      // 1. 이동 대상 객체 업데이트
      let updatedObjects = prev.map((obj) => {
        if (obj.id === objectId) {
          return { ...obj, position: newPosition };
        }
        return obj;
      });

      // 2. 하위 계층 객체 이동 시 논리적 키 검증
      if (hierarchyConfig && targetObj.hierarchyLevel && targetObj.hierarchyLevel > 1) {
        const spatialObjects = updatedObjects.map((obj) => ({
          id: obj.id,
          position: obj.position,
          size: obj.size,
          hierarchyLevel: obj.hierarchyLevel || 1,
          parentId: obj.parentId,
        }));

        const targetSpatialObj = spatialObjects.find((obj) => obj.id === objectId);
        if (targetSpatialObj) {
          const validation = validateSpatialContainment(
            targetSpatialObj,
            spatialObjects.filter((obj) => obj.id !== objectId),
          );

          // 새로운 부모 영역 찾기
          if (validation.parent) {
            const newParentObj = prev.find((obj) => obj.id === validation.parent!.id);

            // DB에서 가져온 데이터인 경우 논리적 키 검증
            if (newParentObj && newParentObj.externalKey && targetObj.parentKey) {
              if (newParentObj.externalKey !== targetObj.parentKey) {
                toast({
                  variant: "destructive",
                  title: "이동 불가",
                  description: `이 Location은 '${targetObj.parentKey}' Area에만 배치할 수 있습니다. (현재 선택된 Area: ${newParentObj.externalKey})`,
                });
                return prev; // 이동 취소
              }
            }

            // 부모 ID 업데이트
            updatedObjects = updatedObjects.map((obj) => {
              if (obj.id === objectId) {
                return { ...obj, parentId: validation.parent!.id };
              }
              return obj;
            });
          } else if (targetObj.parentKey) {
            // DB 데이터인데 부모 영역 밖으로 이동하려는 경우
            toast({
              variant: "destructive",
              title: "이동 불가",
              description: `이 Location은 '${targetObj.parentKey}' Area 내부에 있어야 합니다.`,
            });
            return prev; // 이동 취소
          }
        }
      }

      // 3. 그룹 이동: 자식 객체들도 함께 이동
      const spatialObjects = updatedObjects.map((obj) => ({
        id: obj.id,
        position: obj.position,
        size: obj.size,
        hierarchyLevel: obj.hierarchyLevel || 1,
        parentId: obj.parentId,
      }));

      const descendants = getAllDescendants(objectId, spatialObjects);

      if (descendants.length > 0) {
        const delta = {
          x: newPosition.x - oldPosition.x,
          y: newPosition.y - oldPosition.y,
          z: newPosition.z - oldPosition.z,
        };

        updatedObjects = updatedObjects.map((obj) => {
          if (descendants.some((d) => d.id === obj.id)) {
            return {
              ...obj,
              position: {
                x: obj.position.x + delta.x,
                y: obj.position.y + delta.y,
                z: obj.position.z + delta.z,
              },
            };
          }
          return obj;
        });
      }

      return updatedObjects;
    });

    if (selectedObject?.id === objectId) {
      setSelectedObject((prev) => {
        if (!prev) return null;
        const newPosition = { ...prev.position, x: newX, z: newZ };
        if (newY !== undefined) {
          newPosition.y = newY;
        }
        return { ...prev, position: newPosition };
      });
    }

    setHasUnsavedChanges(true);
  };

  // 객체 속성 업데이트
  const handleObjectUpdate = (updates: Partial<PlacedObject>) => {
    if (!selectedObject) return;

    let finalUpdates = { ...updates };

    // 크기 변경 시에만 5 단위로 스냅하고 위치 조정 (position 변경은 제외)
    if (updates.size && !updates.position) {
      // placedObjects 배열에서 실제 저장된 객체를 가져옴 (selectedObject 상태가 아닌)
      const actualObject = placedObjects.find((obj) => obj.id === selectedObject.id);
      if (!actualObject) return;

      const oldSize = actualObject.size;
      const newSize = { ...oldSize, ...updates.size };

      // W, D를 5 단위로 스냅 (STP 포함)
      newSize.x = Math.max(5, Math.round(newSize.x / 5) * 5);
      newSize.z = Math.max(5, Math.round(newSize.z / 5) * 5);

      // H는 자유롭게 (Area 제외)
      if (actualObject.type !== "area") {
        newSize.y = Math.max(0.1, newSize.y);
      }

      // 크기 차이 계산
      const deltaX = newSize.x - oldSize.x;
      const deltaZ = newSize.z - oldSize.z;
      const deltaY = newSize.y - oldSize.y;

      // 위치 조정: 왼쪽/뒤쪽/바닥 모서리 고정, 오른쪽/앞쪽/위쪽으로만 늘어남
      // Three.js는 중심점 기준이므로 크기 차이의 절반만큼 위치 이동
      // actualObject.position (실제 배열의 position)을 기준으로 계산
      const newPosition = {
        ...actualObject.position,
        x: actualObject.position.x + deltaX / 2, // 오른쪽으로 늘어남
        y: actualObject.position.y + deltaY / 2, // 위쪽으로 늘어남 (바닥 고정)
        z: actualObject.position.z + deltaZ / 2, // 앞쪽으로 늘어남
      };

      finalUpdates = {
        ...finalUpdates,
        size: newSize,
        position: newPosition,
      };
    }

    setPlacedObjects((prev) => prev.map((obj) => (obj.id === selectedObject.id ? { ...obj, ...finalUpdates } : obj)));

    setSelectedObject((prev) => (prev ? { ...prev, ...finalUpdates } : null));
    setHasUnsavedChanges(true);
  };

  // 객체 삭제
  const handleObjectDelete = () => {
    if (!selectedObject) return;

    setPlacedObjects((prev) => prev.filter((obj) => obj.id !== selectedObject.id));
    setSelectedObject(null);
    setHasUnsavedChanges(true);
  };

  // 저장
  const handleSave = async () => {
    if (!selectedDbConnection) {
      toast({
        title: "외부 DB 선택 필요",
        description: "외부 데이터베이스 연결을 선택하세요.",
        variant: "destructive",
      });
      return;
    }

    if (!selectedWarehouse) {
      toast({
        title: "창고 선택 필요",
        description: "창고를 선택하세요.",
        variant: "destructive",
      });
      return;
    }

    setIsSaving(true);
    try {
      const response = await updateLayout(layoutId, {
        layoutName: layoutName,
        description: undefined,
        hierarchyConfig: hierarchyConfig, // 계층 구조 설정
        externalDbConnectionId: selectedDbConnection, // 외부 DB 연결 ID
        warehouseKey: selectedWarehouse, // 선택된 창고
        objects: placedObjects.map((obj, index) => ({
          ...obj,
          displayOrder: index, // 현재 순서대로 저장
        })),
      });

      if (response.success) {
        toast({
          title: "저장 완료",
          description: `${placedObjects.length}개의 객체가 저장되었습니다.`,
        });

        setHasUnsavedChanges(false);

        // 저장 후 DB에서 할당된 ID로 객체 업데이트 (필요 시)
        // 현재는 updateLayout이 기존 객체 삭제 후 재생성하므로
        // 레이아웃 다시 불러오기
        const reloadResponse = await getLayoutById(layoutId);
        if (reloadResponse.success && reloadResponse.data) {
          const { objects } = reloadResponse.data;
          const reloadedObjects: PlacedObject[] = (objects as unknown as DbObject[]).map((obj) => ({
            id: obj.id,
            type: obj.object_type,
            name: obj.object_name,
            position: {
              x: parseFloat(obj.position_x),
              y: parseFloat(obj.position_y),
              z: parseFloat(obj.position_z),
            },
            size: {
              x: parseFloat(obj.size_x),
              y: parseFloat(obj.size_y),
              z: parseFloat(obj.size_z),
            },
            rotation: obj.rotation ? parseFloat(obj.rotation) : 0,
            color: obj.color,
            areaKey: obj.area_key,
            locaKey: obj.loca_key,
            locType: obj.loc_type,
            materialCount: obj.loc_type === "STP" ? undefined : obj.material_count,
            materialPreview:
              obj.loc_type === "STP" || !obj.material_preview_height
                ? undefined
                : { height: parseFloat(obj.material_preview_height) },
            parentId: obj.parent_id,
            displayOrder: obj.display_order,
            locked: obj.locked,
            visible: obj.visible !== false,
          }));

          setPlacedObjects(reloadedObjects);
        }
      } else {
        throw new Error(response.error || "레이아웃 저장 실패");
      }
    } catch (error) {
      console.error("저장 실패:", error);
      const errorMessage = error instanceof Error ? error.message : "레이아웃 저장에 실패했습니다.";
      toast({
        title: "저장 실패",
        description: errorMessage,
        variant: "destructive",
      });
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <div className="bg-background flex h-full flex-col">
      {/* 상단 툴바 */}
      <div className="flex items-center justify-between border-b p-4">
        <div className="flex items-center gap-4">
          <Button variant="ghost" size="sm" onClick={onBack}>
            <ArrowLeft className="mr-2 h-4 w-4" />
            목록으로
          </Button>
          <div>
            <h2 className="text-lg font-semibold">{layoutName}</h2>
            <p className="text-muted-foreground text-sm">디지털 트윈 야드 편집</p>
          </div>
        </div>

        <div className="flex items-center gap-2">
          {hasUnsavedChanges && <span className="text-warning text-sm font-medium">미저장 변경사항 있음</span>}
          <Button
            variant="outline"
            size="sm"
            onClick={handleRefresh}
            disabled={isRefreshing || isLoading}
            title="새로고침"
          >
            <RefreshCw className={`mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
            {isRefreshing ? "갱신 중..." : "새로고침"}
          </Button>
          <Button size="sm" onClick={handleSave} disabled={isSaving || !hasUnsavedChanges}>
            {isSaving ? (
              <>
                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                저장 중...
              </>
            ) : (
              <>
                <Save className="mr-2 h-4 w-4" />
                저장
              </>
            )}
          </Button>
        </div>
      </div>

      {/* 도구 팔레트 (현재 숨김 처리 - 나중에 재사용 가능) */}
      {/*
      <div className="bg-muted flex items-center justify-center gap-2 border-b p-4">
        <span className="text-muted-foreground text-sm font-medium">도구:</span>
        {[
          { type: "area" as ToolType, label: "영역", icon: Grid3x3, color: "text-primary" },
          { type: "location-bed" as ToolType, label: "베드", icon: Package, color: "text-primary" },
          { type: "location-stp" as ToolType, label: "정차", icon: Move, color: "text-muted-foreground" },
          // { type: "crane-gantry" as ToolType, label: "겐트리", icon: Combine, color: "text-emerald-500" },
          { type: "crane-mobile" as ToolType, label: "크레인", icon: Truck, color: "text-amber-500" },
          { type: "rack" as ToolType, label: "랙", icon: Box, color: "text-purple-500" },
        ].map((tool) => {
          const Icon = tool.icon;
          return (
            <div
              key={tool.type}
              draggable
              onDragStart={() => handleToolDragStart(tool.type)}
              className="bg-background hover:bg-accent flex cursor-grab items-center gap-1 rounded-md border px-3 py-2 transition-colors active:cursor-grabbing"
              title={`${tool.label} 드래그하여 배치`}
            >
              <Icon className={`h-4 w-4 ${tool.color}`} />
              <span className="text-xs">{tool.label}</span>
            </div>
          );
        })}
      </div>
      */}

      {/* 메인 영역 */}
      <div className="flex flex-1 overflow-hidden">
        {/* 좌측: 외부 DB 선택 + 객체 목록 */}
        <div className="flex h-full w-[20%] flex-col border-r">
          {/* 스크롤 영역 */}
          <div className="flex-1 space-y-6 overflow-y-auto p-4">
            {/* 외부 DB 선택 */}
            <div>
              <Label className="mb-2 block text-sm font-semibold">외부 데이터베이스</Label>
              <Select
                value={selectedDbConnection?.toString() || ""}
                onValueChange={(value) => {
                  setSelectedDbConnection(parseInt(value));
                  setSelectedWarehouse(null);
                  setSelectedTemplateId("");
                  setHasUnsavedChanges(true);
                }}
              >
                <SelectTrigger className="h-10 text-sm">
                  <SelectValue placeholder="DB 선택..." />
                </SelectTrigger>
                <SelectContent>
                  {externalDbConnections.map((conn) => (
                    <SelectItem key={conn.id} value={conn.id.toString()} className="text-sm">
                      {conn.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>

              {/* 매핑 템플릿 선택/저장 */}
              {selectedDbConnection && (
                <div className="mt-4 space-y-2">
                  <div className="flex items-center justify-between">
                    <Label className="text-sm font-semibold">매핑 템플릿</Label>
                    <Button
                      variant="outline"
                      size="sm"
                      className="h-7 px-2 text-xs"
                      onClick={() => {
                        setNewTemplateName(layoutName || "");
                        setIsSaveTemplateDialogOpen(true);
                      }}
                    >
                      템플릿 저장
                    </Button>
                  </div>
                  <div className="flex gap-2">
                    <Select value={selectedTemplateId} onValueChange={(val) => setSelectedTemplateId(val)}>
                      <SelectTrigger className="h-8 flex-1 text-xs">
                        <SelectValue placeholder={loadingTemplates ? "로딩 중..." : "템플릿 선택..."} />
                      </SelectTrigger>
                      <SelectContent>
                        {mappingTemplates.length === 0 ? (
                          <div className="text-muted-foreground px-2 py-1 text-xs">사용 가능한 템플릿이 없습니다</div>
                        ) : (
                          mappingTemplates.map((tpl) => (
                            <SelectItem key={tpl.id} value={tpl.id} className="text-xs">
                              <div className="flex flex-col">
                                <span>{tpl.name}</span>
                                {tpl.description && (
                                  <span className="text-muted-foreground text-[10px]">{tpl.description}</span>
                                )}
                              </div>
                            </SelectItem>
                          ))
                        )}
                      </SelectContent>
                    </Select>
                    <Button
                      variant="outline"
                      size="sm"
                      className="h-8 px-3 text-xs"
                      disabled={!selectedTemplateId}
                      onClick={() => handleApplyTemplate(selectedTemplateId)}
                    >
                      적용
                    </Button>
                  </div>
                </div>
              )}
            </div>

            {/* 계층 설정 패널 (신규) */}
            {selectedDbConnection && (
              <HierarchyConfigPanel
                externalDbConnectionId={selectedDbConnection}
                hierarchyConfig={hierarchyConfig}
                onHierarchyConfigChange={(config) => {
                  // 새로운 객체로 생성하여 참조 변경 (useEffect 트리거를 위해)
                  setHierarchyConfig({ ...config });

                  // 레벨 테이블 정보를 selectedTables와 동기화
                  const newSelectedTables: any = { ...selectedTables };

                  // 창고 테이블 정보
                  if (config.warehouse?.tableName) {
                    newSelectedTables.warehouse = config.warehouse.tableName;
                  }

                  if (config.levels && config.levels.length > 0) {
                    // 레벨 1 = Area
                    if (config.levels[0]?.tableName) {
                      newSelectedTables.area = config.levels[0].tableName;
                    }
                    // 레벨 2 = Location
                    if (config.levels[1]?.tableName) {
                      newSelectedTables.location = config.levels[1].tableName;
                    }
                  }

                  // 자재 테이블 정보
                  if (config.material?.tableName) {
                    newSelectedTables.material = config.material.tableName;
                  }

                  setSelectedTables(newSelectedTables);
                  setHasUnsavedChanges(true);
                }}
                availableTables={availableTables}
                onLoadTables={async () => {
                  // 이미 로드되어 있으므로 스킵
                }}
                onLoadColumns={async (tableName: string) => {
                  try {
                    const response = await ExternalDbConnectionAPI.getTableColumns(selectedDbConnection, tableName);
                    if (response.success && response.data) {
                      // 컬럼 정보 객체 배열로 반환 (이름 + 설명 + PK 플래그)
                      return response.data.map((col: any) => ({
                        column_name: typeof col === "string" ? col : col.column_name || col.COLUMN_NAME || String(col),
                        data_type: col.data_type || col.DATA_TYPE,
                        description: col.description || col.COLUMN_COMMENT || undefined,
                        is_primary_key: col.is_primary_key ?? col.IS_PRIMARY_KEY,
                      }));
                    }
                    return [];
                  } catch (error) {
                    console.error("컬럼 로드 실패:", error);
                    return [];
                  }
                }}
              />
            )}

            {/* 창고 선택 (HierarchyConfigPanel 아래로 이동) */}
            {selectedDbConnection && hierarchyConfig?.warehouse?.tableName && hierarchyConfig?.warehouse?.keyColumn && (
              <div className="space-y-3">
                <Label className="text-sm font-semibold">창고 선택</Label>

                <div>
                  <Label className="text-muted-foreground mb-1 block text-xs">배치할 창고를 선택하세요</Label>
                  {loadingWarehouses ? (
                    <div className="flex h-9 items-center justify-center rounded-md border">
                      <Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
                    </div>
                  ) : (
                    <Select
                      value={selectedWarehouse || ""}
                      onValueChange={(value) => {
                        setSelectedWarehouse(value);
                        // hierarchyConfig 업데이트
                        setHierarchyConfig((prev) => ({
                          ...prev,
                          warehouseKey: value,
                          levels: prev?.levels || [],
                          material: prev?.material,
                          warehouse: prev?.warehouse,
                        }));
                        setHasUnsavedChanges(true);
                      }}
                    >
                      <SelectTrigger className="h-9 text-xs">
                        <SelectValue placeholder="창고 선택..." />
                      </SelectTrigger>
                      <SelectContent>
                        {warehouses.map((wh: any) => {
                          const keyCol = hierarchyConfig.warehouse!.keyColumn;
                          const nameCol = hierarchyConfig.warehouse!.nameColumn;
                          return (
                            <SelectItem key={wh[keyCol]} value={wh[keyCol]} className="text-xs">
                              {wh[nameCol] || wh[keyCol]}
                            </SelectItem>
                          );
                        })}
                      </SelectContent>
                    </Select>
                  )}
                </div>
              </div>
            )}

            {/* Area 목록 */}
            {selectedDbConnection && selectedWarehouse && (
              <div className="space-y-3">
                <div className="flex items-center justify-between">
                  <h4 className="text-sm font-semibold">사용 가능한 Area</h4>
                  {loadingAreas && <Loader2 className="text-muted-foreground h-3 w-3 animate-spin" />}
                </div>

                {availableAreas.length === 0 ? (
                  <p className="text-muted-foreground text-xs">Area가 없습니다</p>
                ) : (
                  <div className="space-y-2">
                    {availableAreas.map((area) => {
                      // 이미 배치된 Area인지 확인
                      const isPlaced = placedObjects.some((obj) => obj.areaKey === area.AREAKEY);

                      return (
                        <div
                          key={area.AREAKEY}
                          draggable={!isPlaced}
                          onDragStart={() => {
                            if (isPlaced) return;
                            // Area 정보를 임시 저장
                            setDraggedTool("area");
                            setDraggedAreaData(area);
                          }}
                          onDragEnd={() => {
                            setDraggedAreaData(null);
                          }}
                          className={`rounded-lg border p-3 transition-all ${
                            isPlaced
                              ? "bg-muted text-muted-foreground cursor-not-allowed opacity-60"
                              : "bg-background hover:bg-accent cursor-grab active:cursor-grabbing"
                          }`}
                        >
                          <div className="flex items-center justify-between">
                            <div className="flex-1">
                              <p className="text-sm font-medium">{area.AREANAME}</p>
                              <p className="text-muted-foreground text-xs">{area.AREAKEY}</p>
                            </div>
                            {isPlaced ? (
                              <span className="text-muted-foreground text-xs">배치됨</span>
                            ) : (
                              <Grid3x3 className="text-muted-foreground h-4 w-4" />
                            )}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            )}

            {/* Location 목록 (Area 클릭 시 표시) */}
            {availableLocations.length > 0 && (
              <div className="space-y-3 border-t pt-4">
                <div className="flex items-center justify-between">
                  <h4 className="text-sm font-semibold">사용 가능한 Location</h4>
                  {loadingLocations && <Loader2 className="text-muted-foreground h-3 w-3 animate-spin" />}
                </div>

                <div className="space-y-2">
                  {availableLocations.map((location) => {
                    // Location 타입에 따라 ObjectType 결정
                    let locationType: ToolType = "location-bed";
                    if (location.LOCTYPE === "STP") {
                      locationType = "location-stp";
                    } else if (location.LOCTYPE === "TMP") {
                      locationType = "location-temp";
                    } else if (location.LOCTYPE === "DES") {
                      locationType = "location-dest";
                    }

                    // Location이 이미 배치되었는지 확인
                    const isLocationPlaced = placedObjects.some(
                      (obj) =>
                        (obj.type === "location-bed" ||
                          obj.type === "location-stp" ||
                          obj.type === "location-temp" ||
                          obj.type === "location-dest") &&
                        obj.locaKey === location.LOCAKEY,
                    );

                    return (
                      <div
                        key={location.LOCAKEY}
                        draggable={!isLocationPlaced}
                        onDragStart={() => {
                          if (!isLocationPlaced) {
                            setDraggedTool(locationType);
                            setDraggedLocationData(location);
                          }
                        }}
                        onDragEnd={() => {
                          setDraggedLocationData(null);
                        }}
                        className={`rounded-lg border p-3 transition-all ${
                          isLocationPlaced
                            ? "bg-muted text-muted-foreground cursor-not-allowed opacity-60"
                            : "bg-background hover:bg-accent cursor-grab active:cursor-grabbing"
                        }`}
                      >
                        <div className="flex items-center justify-between">
                          <div className="flex-1">
                            <p className="text-sm font-medium">{location.LOCANAME || location.LOCAKEY}</p>
                            <div className="text-muted-foreground mt-1 flex items-center gap-2 text-xs">
                              <span>{location.LOCAKEY}</span>
                              <span className="bg-muted rounded px-1.5 py-0.5">{location.LOCTYPE}</span>
                            </div>
                          </div>
                          {isLocationPlaced ? (
                            <Check className="h-4 w-4 text-emerald-500" />
                          ) : locationType === "location-stp" ? (
                            <ParkingCircle className="text-muted-foreground h-4 w-4" />
                          ) : (
                            <Package className="text-muted-foreground h-4 w-4" />
                          )}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            )}
          </div>

          {/* 배치된 객체 목록 (계층 구조) */}
          <div className="flex-1 overflow-y-auto border-t p-4">
            <h3 className="mb-3 text-sm font-semibold">배치된 객체 ({placedObjects.length})</h3>

            {placedObjects.length === 0 ? (
              <div className="text-muted-foreground text-center text-sm">상단 도구를 드래그하여 배치하세요</div>
            ) : (
              <Accordion type="multiple" className="w-full">
                {/* Area별로 그룹핑 */}
                {(() => {
                  // Area 객체들
                  const areaObjects = placedObjects.filter((obj) => obj.type === "area");

                  // Area가 없으면 기존 방식으로 표시
                  if (areaObjects.length === 0) {
                    return (
                      <div className="space-y-2">
                        {placedObjects.map((obj) => (
                          <div
                            key={obj.id}
                            onClick={() => handleObjectClick(obj.id)}
                            className={`cursor-pointer rounded-lg border p-3 transition-all ${
                              selectedObject?.id === obj.id ? "border-primary bg-primary/10" : "hover:border-primary/50"
                            }`}
                          >
                            <div className="flex items-center justify-between">
                              <span className="text-sm font-medium">{obj.name}</span>
                              <div className="h-3 w-3 rounded-full" style={{ backgroundColor: obj.color }} />
                            </div>
                            <p className="text-muted-foreground mt-1 text-xs">
                              위치: ({obj.position.x.toFixed(1)}, {obj.position.z.toFixed(1)})
                            </p>
                          </div>
                        ))}
                      </div>
                    );
                  }

                  // Area별로 Location들을 그룹핑
                  return areaObjects.map((areaObj) => {
                    // 이 Area의 자식 Location들 찾기
                    const childLocations = placedObjects.filter(
                      (obj) =>
                        obj.type !== "area" &&
                        obj.areaKey === areaObj.areaKey &&
                        (obj.parentId === areaObj.id || obj.externalKey === areaObj.externalKey),
                    );

                    return (
                      <AccordionItem key={areaObj.id} value={`area-${areaObj.id}`} className="border-b">
                        <AccordionTrigger className="px-2 py-3 hover:no-underline">
                          <div
                            className={`flex w-full items-center justify-between pr-2 ${
                              selectedObject?.id === areaObj.id ? "text-primary font-semibold" : ""
                            }`}
                            onClick={(e) => {
                              e.stopPropagation();
                              handleObjectClick(areaObj.id);
                            }}
                          >
                            <div className="flex items-center gap-2">
                              <Grid3x3 className="h-4 w-4" />
                              <span className="text-sm font-medium">{areaObj.name}</span>
                            </div>
                            <div className="flex items-center gap-2">
                              <span className="text-muted-foreground text-xs">({childLocations.length})</span>
                              <div className="h-3 w-3 rounded-full" style={{ backgroundColor: areaObj.color }} />
                            </div>
                          </div>
                        </AccordionTrigger>
                        <AccordionContent className="px-2 pb-3">
                          {childLocations.length === 0 ? (
                            <p className="text-muted-foreground py-2 text-center text-xs">Location이 없습니다</p>
                          ) : (
                            <div className="space-y-2">
                              {childLocations.map((locationObj) => (
                                <div
                                  key={locationObj.id}
                                  onClick={() => handleObjectClick(locationObj.id)}
                                  className={`cursor-pointer rounded-lg border p-2 transition-all ${
                                    selectedObject?.id === locationObj.id
                                      ? "border-primary bg-primary/10"
                                      : "hover:border-primary/50"
                                  }`}
                                >
                                  <div className="flex items-center justify-between">
                                    <div className="flex items-center gap-2">
                                      <Package className="h-3 w-3" />
                                      <span className="text-xs font-medium">{locationObj.name}</span>
                                    </div>
                                    <div
                                      className="h-2.5 w-2.5 rounded-full"
                                      style={{ backgroundColor: locationObj.color }}
                                    />
                                  </div>
                                  <p className="text-muted-foreground mt-1 text-[10px]">
                                    위치: ({locationObj.position.x.toFixed(1)}, {locationObj.position.z.toFixed(1)})
                                  </p>
                                  {locationObj.locaKey && (
                                    <p className="text-muted-foreground mt-0.5 text-[10px]">
                                      Key: {locationObj.locaKey}
                                    </p>
                                  )}
                                </div>
                              ))}
                            </div>
                          )}
                        </AccordionContent>
                      </AccordionItem>
                    );
                  });
                })()}
              </Accordion>
            )}
          </div>
        </div>

        {/* 중앙: 3D 캔버스 */}
        <div className="relative h-full flex-1 bg-muted">
          {isLoading ? (
            <div className="flex h-full items-center justify-center">
              <Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
            </div>
          ) : (
            <>
              <Yard3DCanvas
                placements={placements}
                selectedPlacementId={selectedObject?.id || null}
                onPlacementClick={(placement) => handleObjectClick(placement?.id || null)}
                onPlacementDrag={(id, position) => handleObjectMove(id, position.x, position.z, position.y)}
                focusOnPlacementId={null}
                onCollisionDetected={() => {}}
                previewTool={draggedTool}
                previewPosition={previewPosition}
                onPreviewPositionUpdate={setPreviewPosition}
              />
              {/* 드래그 중일 때 Canvas 위에 투명한 오버레이 (프리뷰 및 드롭 이벤트 캐치용) */}
              {draggedTool && (
                <div
                  className="pointer-events-auto absolute inset-0"
                  style={{ zIndex: 10 }}
                  onDragOver={(e) => {
                    e.preventDefault();
                    const rect = e.currentTarget.getBoundingClientRect();
                    const rawX = ((e.clientX - rect.left) / rect.width - 0.5) * 100;
                    const rawZ = ((e.clientY - rect.top) / rect.height - 0.5) * 100;

                    // 그리드 크기 (5 단위)
                    const gridSize = 5;

                    // 그리드에 스냅
                    let snappedX = Math.round(rawX / gridSize) * gridSize;
                    let snappedZ = Math.round(rawZ / gridSize) * gridSize;

                    // 5x5 객체는 타일 중앙으로 오프셋 (Area는 제외)
                    if (draggedTool !== "area") {
                      snappedX += gridSize / 2;
                      snappedZ += gridSize / 2;
                    }

                    setPreviewPosition({ x: snappedX, z: snappedZ });
                  }}
                  onDragLeave={() => {
                    setPreviewPosition(null);
                  }}
                  onDrop={(e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    if (previewPosition) {
                      handleCanvasDrop(previewPosition.x, previewPosition.z);
                      setPreviewPosition(null);
                    }
                    setDraggedTool(null);
                    setDraggedAreaData(null);
                    setDraggedLocationData(null);
                  }}
                />
              )}
            </>
          )}
        </div>

        {/* 우측: 객체 속성 편집 or 자재 목록 */}
        <div className="h-full w-[25%] overflow-y-auto border-l">
          {showMaterialPanel && selectedObject ? (
            /* 자재 목록 패널 */
            <div className="p-4">
              <div className="mb-4 flex items-center justify-between">
                <div>
                  <h3 className="text-lg font-semibold">자재 목록</h3>
                  <p className="text-muted-foreground text-xs">
                    {selectedObject.name} ({selectedObject.locaKey})
                  </p>
                </div>
                <Button variant="ghost" size="sm" onClick={() => setShowMaterialPanel(false)}>
                  닫기
                </Button>
              </div>

              {loadingMaterials ? (
                <div className="flex h-32 items-center justify-center">
                  <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
                </div>
              ) : materials.length === 0 ? (
                <div className="text-muted-foreground flex h-32 items-center justify-center text-sm">
                  자재가 없습니다
                </div>
              ) : (
                <div className="max-h-[400px] overflow-y-auto rounded-md border">
                  <Table>
                    <TableHeader className="bg-muted sticky top-0">
                      <TableRow>
                        <TableHead className="w-[70px] whitespace-nowrap px-3 py-3 text-sm">층</TableHead>
                        {(hierarchyConfig?.material?.displayColumns || []).map((col) => (
                          <TableHead key={col.column} className="px-3 py-3 text-sm">
                            {col.label}
                          </TableHead>
                        ))}
                      </TableRow>
                    </TableHeader>
                    <TableBody>
                  {materials.map((material, index) => {
                    const layerColumn = hierarchyConfig?.material?.layerColumn || "LOLAYER";
                    const keyColumn = hierarchyConfig?.material?.keyColumn || "STKKEY";
                    const displayColumns = hierarchyConfig?.material?.displayColumns || [];
                        const layerNumber = material[layerColumn] || index + 1;

                    return (
                          <TableRow key={material[keyColumn] || `material-${index}`}>
                            <TableCell className="whitespace-nowrap px-3 py-3 text-sm font-medium">{layerNumber}단</TableCell>
                            {displayColumns.map((col) => (
                              <TableCell key={col.column} className="px-3 py-3 text-sm">
                                {material[col.column] || "-"}
                              </TableCell>
                              ))}
                          </TableRow>
                    );
                  })}
                    </TableBody>
                  </Table>
                </div>
              )}
            </div>
          ) : selectedObject ? (
            <div className="p-4">
              <h3 className="mb-4 text-lg font-semibold">객체 속성</h3>

              <div className="space-y-4">
                {/* 이름 */}
                <div>
                  <Label htmlFor="object-name" className="text-sm">
                    이름
                  </Label>
                  <DebouncedInput
                    id="object-name"
                    value={selectedObject.name || ""}
                    onCommit={(val) => handleObjectUpdate({ name: val })}
                    className="mt-1.5 h-9 text-sm"
                  />
                </div>

                {/* 위치 */}
                <div>
                  <Label className="text-sm">위치</Label>
                  <div className="mt-1.5 grid grid-cols-2 gap-2">
                    <div>
                      <Label htmlFor="pos-x" className="text-muted-foreground text-xs">
                        X
                      </Label>
                      <DebouncedInput
                        id="pos-x"
                        type="number"
                        value={(selectedObject.position?.x || 0).toFixed(1)}
                        onCommit={(val) =>
                          handleObjectUpdate({
                            position: {
                              ...selectedObject.position,
                              x: val,
                            },
                          })
                        }
                        className="h-9 text-sm"
                      />
                    </div>
                    <div>
                      <Label htmlFor="pos-z" className="text-muted-foreground text-xs">
                        Z
                      </Label>
                      <DebouncedInput
                        id="pos-z"
                        type="number"
                        value={(selectedObject.position?.z || 0).toFixed(1)}
                        onCommit={(val) =>
                          handleObjectUpdate({
                            position: {
                              ...selectedObject.position,
                              z: val,
                            },
                          })
                        }
                        className="h-9 text-sm"
                      />
                    </div>
                  </div>
                </div>

                {/* 크기 */}
                <div>
                  <Label className="text-sm">크기</Label>
                  <div className="mt-1.5 grid grid-cols-3 gap-2">
                    <div>
                      <Label htmlFor="size-x" className="text-muted-foreground text-xs">
                        W (5 단위)
                      </Label>
                      <DebouncedInput
                        id="size-x"
                        type="number"
                        step="5"
                        min="5"
                        value={selectedObject.size?.x || 5}
                        onCommit={(val) =>
                          handleObjectUpdate({
                            size: {
                              ...selectedObject.size,
                              x: val,
                            },
                          })
                        }
                        className="h-9 text-sm"
                      />
                    </div>
                    <div>
                      <Label htmlFor="size-y" className="text-muted-foreground text-xs">
                        H
                      </Label>
                      <DebouncedInput
                        id="size-y"
                        type="number"
                        value={selectedObject.size?.y || 5}
                        onCommit={(val) =>
                          handleObjectUpdate({
                            size: {
                              ...selectedObject.size,
                              y: val,
                            },
                          })
                        }
                        className="h-9 text-sm"
                      />
                    </div>
                    <div>
                      <Label htmlFor="size-z" className="text-muted-foreground text-xs">
                        D (5 단위)
                      </Label>
                      <DebouncedInput
                        id="size-z"
                        type="number"
                        step="5"
                        min="5"
                        value={selectedObject.size?.z || 5}
                        onCommit={(val) =>
                          handleObjectUpdate({
                            size: {
                              ...selectedObject.size,
                              z: val,
                            },
                          })
                        }
                        className="h-9 text-sm"
                      />
                    </div>
                  </div>
                </div>

                {/* 색상 */}
                <div>
                  <Label htmlFor="object-color" className="text-sm">
                    색상
                  </Label>
                  <DebouncedInput
                    id="object-color"
                    type="color"
                    debounce={100}
                    value={selectedObject.color || "#3b82f6"}
                    onCommit={(val) => handleObjectUpdate({ color: val })}
                    className="mt-1.5 h-9"
                  />
                </div>

                {/* 삭제 버튼 */}
                <Button variant="destructive" size="sm" onClick={handleObjectDelete} className="w-full">
                  객체 삭제
                </Button>
              </div>
            </div>
          ) : (
            <div className="flex h-full items-center justify-center p-4 text-center">
              <p className="text-muted-foreground text-sm">객체를 선택하면 속성을 편집할 수 있습니다</p>
            </div>
          )}
        </div>
      </div>
      {/* 매핑 템플릿 저장 다이얼로그 */}
      <Dialog open={isSaveTemplateDialogOpen} onOpenChange={setIsSaveTemplateDialogOpen}>
        <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">
              현재 창고/계층/자재 설정을 템플릿으로 저장하여 다른 레이아웃에서 재사용할 수 있습니다.
            </DialogDescription>
          </DialogHeader>
          <div className="space-y-3 sm:space-y-4">
            <div>
              <Label htmlFor="template-name" className="text-xs sm:text-sm">
                템플릿 이름 *
              </Label>
              <Input
                id="template-name"
                value={newTemplateName}
                onChange={(e) => setNewTemplateName(e.target.value)}
                placeholder="예: 동연 야드 표준 매핑"
                className="h-8 text-xs sm:h-10 sm:text-sm"
              />
            </div>
            <div>
              <Label htmlFor="template-desc" className="text-xs sm:text-sm">
                설명 (선택)
              </Label>
              <Input
                id="template-desc"
                value={newTemplateDescription}
                onChange={(e) => setNewTemplateDescription(e.target.value)}
                placeholder="이 템플릿에 대한 설명을 입력하세요"
                className="h-8 text-xs sm:h-10 sm:text-sm"
              />
            </div>
          </div>
          <DialogFooter className="gap-2 sm:gap-0">
            <Button
              variant="outline"
              onClick={() => setIsSaveTemplateDialogOpen(false)}
              className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
            >
              취소
            </Button>
            <Button onClick={handleSaveTemplate} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
              저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
