import React, { createContext, useContext, useState, useCallback, ReactNode, useMemo } from "react"; import { LayerDefinition, LayerType, ComponentData } from "@/types/screen-management"; import { v4 as uuidv4 } from "uuid"; interface LayerContextType { // 레이어 상태 layers: LayerDefinition[]; activeLayerId: string | null; activeLayer: LayerDefinition | null; // 레이어 관리 setLayers: (layers: LayerDefinition[]) => void; setActiveLayerId: (id: string | null) => void; addLayer: (type: LayerType, name?: string) => void; removeLayer: (id: string) => void; updateLayer: (id: string, updates: Partial) => void; moveLayer: (dragIndex: number, hoverIndex: number) => void; toggleLayerVisibility: (id: string) => void; toggleLayerLock: (id: string) => void; getLayerById: (id: string) => LayerDefinition | undefined; // 컴포넌트 관리 (레이어별) addComponentToLayer: (layerId: string, component: ComponentData) => void; removeComponentFromLayer: (layerId: string, componentId: string) => void; updateComponentInLayer: (layerId: string, componentId: string, updates: Partial) => void; moveComponentToLayer: (componentId: string, fromLayerId: string, toLayerId: string) => void; // 컴포넌트 조회 getAllComponents: () => ComponentData[]; getComponentById: (componentId: string) => { component: ComponentData; layerId: string } | null; getComponentsInActiveLayer: () => ComponentData[]; // 레이어 가시성 (런타임용) runtimeVisibleLayers: string[]; setRuntimeVisibleLayers: React.Dispatch>; showLayer: (layerId: string) => void; hideLayer: (layerId: string) => void; toggleLayerRuntime: (layerId: string) => void; } const LayerContext = createContext(undefined); export const useLayer = () => { const context = useContext(LayerContext); if (!context) { throw new Error("useLayer must be used within a LayerProvider"); } return context; }; // LayerProvider가 없을 때 사용할 기본 컨텍스트 (선택적 사용) export const useLayerOptional = () => { return useContext(LayerContext); }; interface LayerProviderProps { children: ReactNode; initialLayers?: LayerDefinition[]; onLayersChange?: (layers: LayerDefinition[]) => void; onActiveLayerChange?: (activeLayerId: string | null) => void; // 🆕 활성 레이어 변경 콜백 } // 기본 레이어 생성 헬퍼 export const createDefaultLayer = (components?: ComponentData[]): LayerDefinition => ({ id: "default-layer", name: "기본 레이어", type: "base", zIndex: 0, isVisible: true, isLocked: false, components: components || [], }); export const LayerProvider: React.FC = ({ children, initialLayers = [], onLayersChange, onActiveLayerChange, }) => { // 초기 레이어가 없으면 기본 레이어 생성 const effectiveInitialLayers = initialLayers.length > 0 ? initialLayers : [createDefaultLayer()]; const [layers, setLayersState] = useState(effectiveInitialLayers); const [activeLayerIdState, setActiveLayerIdState] = useState( effectiveInitialLayers.length > 0 ? effectiveInitialLayers[0].id : null, ); // 🆕 활성 레이어 변경 시 콜백 호출 const setActiveLayerId = useCallback((id: string | null) => { setActiveLayerIdState(id); onActiveLayerChange?.(id); }, [onActiveLayerChange]); // 활성 레이어 ID (내부 상태 사용) const activeLayerId = activeLayerIdState; // 런타임 가시성 상태 (편집기에서의 isVisible과 별개) const [runtimeVisibleLayers, setRuntimeVisibleLayers] = useState( effectiveInitialLayers.filter(l => l.isVisible).map(l => l.id) ); // 레이어 변경 시 콜백 호출 const setLayers = useCallback((newLayers: LayerDefinition[]) => { setLayersState(newLayers); onLayersChange?.(newLayers); }, [onLayersChange]); // 활성 레이어 계산 const activeLayer = useMemo(() => { return layers.find(l => l.id === activeLayerId) || null; }, [layers, activeLayerId]); const addLayer = useCallback( (type: LayerType, name?: string) => { const newLayer: LayerDefinition = { id: uuidv4(), name: name || `새 레이어 ${layers.length + 1}`, type, zIndex: layers.length, isVisible: true, isLocked: false, components: [], // 모달/드로어 기본 설정 ...(type === "modal" || type === "drawer" ? { overlayConfig: { backdrop: true, closeOnBackdropClick: true, width: type === "drawer" ? "320px" : "600px", height: type === "drawer" ? "100%" : "auto", }, } : {}), }; setLayers([...layers, newLayer]); setActiveLayerId(newLayer.id); // 새 레이어는 런타임에서도 기본적으로 표시 setRuntimeVisibleLayers(prev => [...prev, newLayer.id]); }, [layers, setLayers], ); const removeLayer = useCallback( (id: string) => { // 기본 레이어는 삭제 불가 const layer = layers.find(l => l.id === id); if (layer?.type === "base") { console.warn("기본 레이어는 삭제할 수 없습니다."); return; } const filtered = layers.filter((layer) => layer.id !== id); setLayers(filtered); if (activeLayerId === id) { setActiveLayerId(filtered.length > 0 ? filtered[0].id : null); } setRuntimeVisibleLayers(prev => prev.filter(lid => lid !== id)); }, [layers, activeLayerId, setLayers], ); const updateLayer = useCallback((id: string, updates: Partial) => { setLayers(layers.map((layer) => (layer.id === id ? { ...layer, ...updates } : layer))); }, [layers, setLayers]); const moveLayer = useCallback((dragIndex: number, hoverIndex: number) => { const newLayers = [...layers]; const [removed] = newLayers.splice(dragIndex, 1); newLayers.splice(hoverIndex, 0, removed); // Update zIndex based on new order setLayers(newLayers.map((layer, index) => ({ ...layer, zIndex: index }))); }, [layers, setLayers]); const toggleLayerVisibility = useCallback((id: string) => { setLayers(layers.map((layer) => (layer.id === id ? { ...layer, isVisible: !layer.isVisible } : layer))); }, [layers, setLayers]); const toggleLayerLock = useCallback((id: string) => { setLayers(layers.map((layer) => (layer.id === id ? { ...layer, isLocked: !layer.isLocked } : layer))); }, [layers, setLayers]); const getLayerById = useCallback( (id: string) => { return layers.find((layer) => layer.id === id); }, [layers], ); // ===== 컴포넌트 관리 함수 ===== const addComponentToLayer = useCallback((layerId: string, component: ComponentData) => { setLayers(layers.map(layer => { if (layer.id === layerId) { return { ...layer, components: [...layer.components, component], }; } return layer; })); }, [layers, setLayers]); const removeComponentFromLayer = useCallback((layerId: string, componentId: string) => { setLayers(layers.map(layer => { if (layer.id === layerId) { return { ...layer, components: layer.components.filter(c => c.id !== componentId), }; } return layer; })); }, [layers, setLayers]); const updateComponentInLayer = useCallback((layerId: string, componentId: string, updates: Partial) => { setLayers(layers.map(layer => { if (layer.id === layerId) { return { ...layer, components: layer.components.map(c => c.id === componentId ? { ...c, ...updates } as ComponentData : c ), }; } return layer; })); }, [layers, setLayers]); const moveComponentToLayer = useCallback((componentId: string, fromLayerId: string, toLayerId: string) => { if (fromLayerId === toLayerId) return; const fromLayer = layers.find(l => l.id === fromLayerId); const component = fromLayer?.components.find(c => c.id === componentId); if (!component) return; setLayers(layers.map(layer => { if (layer.id === fromLayerId) { return { ...layer, components: layer.components.filter(c => c.id !== componentId), }; } if (layer.id === toLayerId) { return { ...layer, components: [...layer.components, component], }; } return layer; })); }, [layers, setLayers]); // ===== 컴포넌트 조회 함수 ===== const getAllComponents = useCallback((): ComponentData[] => { return layers.flatMap(layer => layer.components); }, [layers]); const getComponentById = useCallback((componentId: string): { component: ComponentData; layerId: string } | null => { for (const layer of layers) { const component = layer.components.find(c => c.id === componentId); if (component) { return { component, layerId: layer.id }; } } return null; }, [layers]); const getComponentsInActiveLayer = useCallback((): ComponentData[] => { const layer = layers.find(l => l.id === activeLayerId); return layer?.components || []; }, [layers, activeLayerId]); // ===== 런타임 레이어 가시성 관리 ===== const showLayer = useCallback((layerId: string) => { setRuntimeVisibleLayers(prev => [...new Set([...prev, layerId])]); }, []); const hideLayer = useCallback((layerId: string) => { setRuntimeVisibleLayers(prev => prev.filter(id => id !== layerId)); }, []); const toggleLayerRuntime = useCallback((layerId: string) => { setRuntimeVisibleLayers(prev => prev.includes(layerId) ? prev.filter(id => id !== layerId) : [...prev, layerId] ); }, []); return ( {children} ); };