'use client';

import React, { useCallback, useMemo, useState, useEffect, memo, useRef } from 'react';
import { AnimatePresence } from 'framer-motion';
import dynamic from 'next/dynamic';
import {
  ReactFlow,
  Background,
  Connection,
  Edge,
  BackgroundVariant,
  NodeTypes,
  useReactFlow,
  useStore,
  useViewport,
  ConnectionLineType,
  MiniMap,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';

import { AppNode } from './nodes/AppNode';
import { GroupNode } from './nodes/GroupNode';
import { GradientEdge } from './edges/GradientEdge';
import { AppNodeData, AppNodeType, NodeType, ContextMenuState, ContextMenuTarget } from './types/node';
import {
    X,
    List
} from 'lucide-react';
import { CanvasContextMenu } from './controls/CanvasContextMenu';
import { CanvasViewportControls } from './controls/CanvasViewportControls';
import { CanvasShortcuts } from './controls/CanvasShortcuts';
import { OnboardingTour } from './controls/OnboardingTour';
import { BatchGenerationToolbar } from './controls/BatchGenerationToolbar';
import { GenerationQueuePanel } from './panels/GenerationQueuePanel';
import { PromptTemplateLibrary } from './panels/PromptTemplateLibrary';
import { CanvasProviders } from './CanvasProviders';
import { useSocketInteraction } from './hooks/useSocketInteraction';
import { useCanvasHotkeys } from './hooks/useCanvasHotkeys';
import { useCanvasDragDrop } from './hooks/useCanvasDragDrop';
import { useNodeSnapping } from './hooks/useNodeSnapping';
import { Sidebar } from './panels/docks/CanvasLeftDock';
import { useProjectStore } from '@lib/store/project';

import { NodeStatus } from './types/node';

import { useCanvasContext, CanvasContextType } from './CanvasContext';
import { useAssetSaver } from './hooks/useAssetSaver';
import { useCanvasEvents } from './hooks/useCanvasEvents';
import { useResumeTaskPolling } from './hooks/useResumeTaskPolling';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { LoadingState } from '@/components/ui/loading-state';

// Managers
import { useCanvasPersistence } from './managers/useCanvasPersistence';
import { useGenerationHistory } from './managers/useGenerationHistory';
import { useCanvasInteraction } from './managers/useCanvasInteraction';
import { useCanvasStore } from '@lib/store/canvasStore';
import { useAutoWorkflow } from './hooks/useAutoWorkflow';
import { SaveFeedback } from './SaveFeedback';
import { CanvasHeader } from './CanvasHeader';
import { UserAvatar } from './UserAvatar';
import { useModelStore } from '@lib/store/modelStore';
import { useUIStore } from '@/store/uiStore';
import { logger } from '@/lib/utils/logger';

const log = logger.namespace('Canvas');

// Lazy load heavy components
const ImageCropper = dynamic(() => import('./controls/ImageCropper').then(mod => ({ default: mod.ImageCropper })), {
  loading: () => <LoadingState message="加载裁剪工具..." />,
  ssr: false
});

const SketchEditor = dynamic(() => import('./controls/SketchEditor').then(mod => ({ default: mod.SketchEditor })), {
  loading: () => <LoadingState message="加载绘图工具..." />,
  ssr: false
});

const ProjectDetailsModal = dynamic(() => import('./panels/management/project-details/index').then(mod => ({ default: mod.ProjectDetailsModal })), {
  loading: () => <LoadingState message="加载项目详情..." />,
  ssr: false
});

const ExpandedView = dynamic(() => import('./panels/ExpandedView').then(mod => ({ default: mod.ExpandedView })), {
  loading: () => <LoadingState message="加载预览..." />,
  ssr: false
});

const RightSidebar = dynamic(() => import('./panels/docks/CanvasRightDock').then(mod => ({ default: mod.RightSidebar })), {
  ssr: false
});

const InteractionInput = dynamic(
  () => import('./InteractionInput').then(mod => ({ default: mod.InteractionInput })),
  { ssr: false, loading: () => <LoadingState message="加载输入栏..." /> }
);

const nodeTypes: NodeTypes = {
  appNode: AppNode,
};

const edgeTypes = {
  gradient: GradientEdge,
  default: GradientEdge,
};

// Memoized components to prevent unnecessary re-renders
const MemoizedSidebar = memo(Sidebar);
const MemoizedRightSidebar = memo(RightSidebar);
const MemoizedCanvasContextMenu = memo(CanvasContextMenu);

function Flow() {
  const [isMiniMapOpen, setIsMiniMapOpen] = useState(false);
  const [isQueuePanelOpen, setIsQueuePanelOpen] = useState(false);
  const [isBatchToolbarOpen, setIsBatchToolbarOpen] = useState(false);
  const [isTemplateLibraryOpen, setIsTemplateLibraryOpen] = useState(false);

  // Ctrl+Q shortcut for queue panel, Ctrl+B for batch generation, Ctrl+T for template library
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'q') {
        e.preventDefault();
        setIsQueuePanelOpen(prev => !prev);
      }
      if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
        e.preventDefault();
        setIsBatchToolbarOpen(prev => !prev);
      }
      if ((e.metaKey || e.ctrlKey) && e.key === 't') {
        e.preventDefault();
        setIsTemplateLibraryOpen(prev => !prev);
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, []);

  const isConnecting = useStore((state: any) => !!state.connectionNodeId);
  const {
    onClose,
    onUpdateAsset,
    onUpdateStoryboard,
    activeCanvasId,
    activeCanvasType,
    switchCanvas,
    isCanvasLoaded,
    currentCanvasMetadata
  } = useCanvasContext();

  // Select from store
  const nodes = useCanvasStore((state) => state.nodes);
  const onNodesChange = useCanvasStore((state) => state.onNodesChange);
  const setNodes = useCanvasStore((state) => state.setNodes);

  const edges = useCanvasStore((state) => state.edges);
  const onEdgesChange = useCanvasStore((state) => state.onEdgesChange);
  const setEdges = useCanvasStore((state) => state.setEdges);
  const onConnect = useCanvasStore((state) => state.onConnect);
  const takeSnapshot = useCanvasStore((state) => state.saveHistory);
  const undo = useCanvasStore((state) => state.undo);
  const redo = useCanvasStore((state) => state.redo);

  // Groups
  const groups = useCanvasStore((state) => state.groups);
  const selectedGroupId = useCanvasStore((state) => state.selectedGroupId);
  const setSelectedGroupId = useCanvasStore((state) => state.setSelectedGroupId);
  const createGroupFromSelected = useCanvasStore((state) => state.createGroupFromSelected);
  const deleteGroup = useCanvasStore((state) => state.deleteGroup);
  const ungroup = useCanvasStore((state) => state.ungroup);
  const updateGroup = useCanvasStore((state) => state.updateGroup);

  const { screenToFlowPosition, zoomIn, zoomOut, zoomTo, fitView } = useReactFlow();
  const { zoom } = useViewport();

  // Check model availability
  const { imageModels, videoModels, llmModels } = useModelStore();

  // Warn if models are not loaded (only once on mount)
  useEffect(() => {
    const hasModels = Object.keys(imageModels).length > 0 ||
                     Object.keys(videoModels).length > 0 ||
                     Object.keys(llmModels).length > 0;

    if (!hasModels) {
      log.warn('No models loaded yet. Node creation may use fallback defaults.');
    }
  }, [imageModels, videoModels, llmModels]);

  const defaultEdgeOptions = useMemo(() => ({
        type: 'gradient',
        // animated: true,
    }), []);

    const isSaving = useCanvasStore((state) => state.isSaving);
    const isDirty = useCanvasStore((state) => state.isDirty);
    const lastSavedAt = useCanvasStore((state) => state.lastSavedAt);

    // Select from project store
    const assets = useProjectStore((state) => state.assets);
    const storyboards = useProjectStore((state) => state.storyboards);
    const episodes = useProjectStore((state) => state.episodes);
    const projectName = useProjectStore((state) => state.name);

    // Memoize sorted storyboards for consistent navigation and shortcuts
    const sortedStoryboards = useMemo(() => {
        const episodeOrderMap = new Map(episodes.map(e => [e.id, e.order]));
        return [...storyboards].sort((a, b) => {
            const orderA = episodeOrderMap.get(a.episodeId) ?? Infinity;
            const orderB = episodeOrderMap.get(b.episodeId) ?? Infinity;
            if (orderA !== orderB) return orderA - orderB;
            return a.order - b.order;
        });
    }, [storyboards, episodes]);

    const deleteAsset = useProjectStore((state) => state.deleteAsset);

    const [manualSaveRequested, setManualSaveRequested] = useState(false);
    const updateAsset = useProjectStore((state) => state.updateAsset);
    const projectId = useProjectStore((state) => state.id);

    const { saveAsset } = useAssetSaver();

    // Select from canvas store
    const generationHistory = useCanvasStore((state) => state.generationHistory);
    const setGenerationHistory = useCanvasStore((state) => state.setGenerationHistory);

    const { confirm, ConfirmDialog } = useConfirm();

    // --- Managers ---

    // 1. History Manager (Generation History)
    const { loadHistory } = useGenerationHistory();

    useEffect(() => {
        loadHistory();
    }, [loadHistory]);

    // 2. Persistence Manager
    const { saveImmediately, isSaving: isSavingMutation, saveError } = useCanvasPersistence({
        nodes,
        edges,
        activeCanvasId: activeCanvasId || null,
        activeCanvasType: activeCanvasType || 'main',
        isCanvasLoaded: !!isCanvasLoaded,
        canvasMetadata: currentCanvasMetadata
    });

  // Context for Saving - memoized to prevent recreation
  const saveContext = useMemo(() => ({
      assetId: activeCanvasType === 'asset' ? (activeCanvasId || null) : null,
      storyboardId: activeCanvasType === 'storyboard' ? (activeCanvasId || null) : null
  }), [activeCanvasType, activeCanvasId]);

  // 4. Interaction Manager
  const {
      handleAddNode,
      handleSketchResult,
      handleInteractionGenerate,
      handleCreateAndConnect: handleCreateAndConnectManager
  } = useCanvasInteraction({
      saveContext,
      setNodes,
      setEdges,
      saveAsset
  });

  // 5. Auto Workflow Manager
  const {
    createNodesFromAsset,
    createWorkflowFromStoryboard,
    applyWorkflow,
  } = useAutoWorkflow();


  const { isValidConnection } = useSocketInteraction(nodes, setEdges);
  const { onDragOver, onDrop } = useCanvasDragDrop(setNodes);

  // 画布加载后恢复进行中的异步任务轮询（音乐/音频/歌词等，与各节点写入 taskId 逻辑统一）
  useResumeTaskPolling();
  const { onNodeDrag } = useNodeSnapping(setNodes);

  // Handle custom keyboard shortcut events
  useEffect(() => {
    const handleFitView = () => {
      fitView({
        duration: 300,
        padding: {
          top: 0.1,
          right: 0.15,
          bottom: 0.15,
          left: 0.12
        },
        minZoom: 0.5,
        maxZoom: 1.2
      });
    };

    const handleZoomIn = () => {
      zoomIn({ duration: 200 });
    };

    const handleZoomOut = () => {
      zoomOut({ duration: 200 });
    };

    window.addEventListener('canvas:fitView', handleFitView);
    window.addEventListener('canvas:zoomIn', handleZoomIn);
    window.addEventListener('canvas:zoomOut', handleZoomOut);

    return () => {
      window.removeEventListener('canvas:fitView', handleFitView);
      window.removeEventListener('canvas:zoomIn', handleZoomIn);
      window.removeEventListener('canvas:zoomOut', handleZoomOut);
    };
  }, [fitView, zoomIn, zoomOut]);

  const handleManualSave = useCallback(() => {
      setManualSaveRequested(true);
      saveImmediately();
  }, [saveImmediately]);

  const handleSwitchNext = useCallback(() => {
      if (!switchCanvas || !activeCanvasId) return;

      if (activeCanvasType === 'asset') {
          const currentAsset = assets.find(a => a.id === activeCanvasId);
          if (!currentAsset) return;

          // Filter assets by the same type as the current one to maintain context
          // and sort them by name for consistent navigation
          const sameTypeAssets = assets
              .filter(a => a.type === currentAsset.type)
              .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));

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

          const currentIndex = sameTypeAssets.findIndex(item => item.id === activeCanvasId);
          if (currentIndex === -1) return;

          const nextIndex = (currentIndex + 1) % sameTypeAssets.length;
          switchCanvas(sameTypeAssets[nextIndex].id, 'asset');

      } else if (activeCanvasType === 'storyboard') {
          // Use memoized sorted storyboards
          if (sortedStoryboards.length === 0) return;

          const currentIndex = sortedStoryboards.findIndex(item => item.id === activeCanvasId);
          if (currentIndex === -1) return;

          const nextIndex = (currentIndex + 1) % sortedStoryboards.length;
          switchCanvas(sortedStoryboards[nextIndex].id, 'storyboard');
      }
  }, [activeCanvasType, activeCanvasId, assets, sortedStoryboards, switchCanvas]);

  const handleSwitchPrev = useCallback(() => {
       if (!switchCanvas || !activeCanvasId) return;

      if (activeCanvasType === 'asset') {
          const currentAsset = assets.find(a => a.id === activeCanvasId);
          if (!currentAsset) return;

          const sameTypeAssets = assets
              .filter(a => a.type === currentAsset.type)
              .sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));

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

          const currentIndex = sameTypeAssets.findIndex(item => item.id === activeCanvasId);
          if (currentIndex === -1) return;

          const prevIndex = (currentIndex - 1 + sameTypeAssets.length) % sameTypeAssets.length;
          switchCanvas(sameTypeAssets[prevIndex].id, 'asset');

      } else if (activeCanvasType === 'storyboard') {
          // Use memoized sorted storyboards
          if (sortedStoryboards.length === 0) return;

          const currentIndex = sortedStoryboards.findIndex(item => item.id === activeCanvasId);
          if (currentIndex === -1) return;

          const prevIndex = (currentIndex - 1 + sortedStoryboards.length) % sortedStoryboards.length;
          switchCanvas(sortedStoryboards[prevIndex].id, 'storyboard');
      }
  }, [activeCanvasType, activeCanvasId, assets, sortedStoryboards, switchCanvas]);

  const { setClipboard, clipboard, pasteNodes } = useCanvasHotkeys({
    undo,
    redo,
    saveHistory: takeSnapshot,
    save: handleManualSave,
    switchNext: handleSwitchNext,
    switchPrev: handleSwitchPrev,
    switchCanvas,
    assets,
    storyboards: sortedStoryboards,
    createGroup: createGroupFromSelected,
    selectedNodeIds: useCanvasStore((state) => state.selectedNodeIds)
  });

  const onConnectWrapper = useCallback((params: Connection) => {
      takeSnapshot();
      onConnect(params);
  }, [onConnect, takeSnapshot]);

  const onNodeDragStart = useCallback(() => {
      takeSnapshot();
  }, [takeSnapshot]);

  // Auto-generate workflow when canvas is loaded but empty
  useEffect(() => {
      if (!isCanvasLoaded) return;

      // If canvas already has content, don't auto-generate
      if (nodes.length > 0) return;

      if (activeCanvasType === 'asset' && activeCanvasId) {
          const asset = assets.find(a => a.id === activeCanvasId);
          if (asset) {
              log.log('Auto-generating workflow for asset:', asset.name);
              const { nodes: workflowNodes, edges: workflowEdges } = createNodesFromAsset(
                  asset,
                  { x: 100, y: 100 }
              );
              applyWorkflow(workflowNodes, workflowEdges);

              // Auto-fit view with safe area padding (avoid docks and input)
              setTimeout(() => {
                  fitView({
                      duration: 300,
                      padding: {
                          top: 0.1,      // 10% top padding
                          right: 0.15,   // 15% right padding (avoid right dock)
                          bottom: 0.15,  // 15% bottom padding (avoid input box)
                          left: 0.12     // 12% left padding (avoid left dock)
                      },
                      minZoom: 0.5,
                      maxZoom: 1.2
                  });
              }, 50);
          }
      } else if (activeCanvasType === 'storyboard' && activeCanvasId) {
          const storyboard = storyboards.find(s => s.id === activeCanvasId);
          if (storyboard) {
              log.log('Auto-generating workflow for storyboard:', storyboard.shot);
              const { nodes: workflowNodes, edges: workflowEdges } = createWorkflowFromStoryboard(
                  storyboard,
                  assets,
                  { x: 100, y: 100 }
              );
              applyWorkflow(workflowNodes, workflowEdges);

              // Auto-fit view with safe area padding (avoid docks and input)
              setTimeout(() => {
                  fitView({
                      duration: 300,
                      padding: {
                          top: 0.1,      // 10% top padding
                          right: 0.15,   // 15% right padding (avoid right dock)
                          bottom: 0.15,  // 15% bottom padding (avoid input box)
                          left: 0.12     // 12% left padding (avoid left dock)
                      },
                      minZoom: 0.5,
                      maxZoom: 1.2
                  });
              }, 50);
          }
      }
  }, [isCanvasLoaded, nodes.length, activeCanvasType, activeCanvasId, assets, storyboards, createNodesFromAsset, createWorkflowFromStoryboard, applyWorkflow, fitView]);

  const sidebarSourceContext = useMemo(() => {
      if (activeCanvasType === 'asset' && activeCanvasId) return { id: activeCanvasId, type: 'asset' as const };
      if (activeCanvasType === 'storyboard' && activeCanvasId) return { id: activeCanvasId, type: 'storyboard' as const };
      return null;
  }, [activeCanvasType, activeCanvasId]);

  // Context Menu
  const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
  const [contextMenuTarget, setContextMenuTarget] = useState<ContextMenuTarget | null>(null);

  // Replacement Input Refs
  const replaceVideoInputRef = React.useRef<HTMLInputElement>(null);
  const replaceImageInputRef = React.useRef<HTMLInputElement>(null);
  const replacementTargetRef = React.useRef<string | null>(null);

  // --- Missing State from Canvas.tsx ---
    const {
      cropRequest,
      setCropRequest,
      handleClick,
      setHandleClick,
      expandedMedia
    } = useCanvasEvents();

  // Sync handleClick to Context Menu
  useEffect(() => {
      if (handleClick) {
          setContextMenu({
              visible: true,
              x: handleClick.x,
              y: handleClick.y,
              id: handleClick.nodeId
          });
          setContextMenuTarget({ type: 'handle-create', nodeId: handleClick.nodeId });
          setHandleClick(null);
      }
  }, [handleClick, setHandleClick]);

  const [isSketchEditorOpen, setIsSketchEditorOpen] = useState(false);
  const [isProjectDetailsOpen, setIsProjectDetailsOpen] = useState(false);

  // --- Dock Interaction ---
  const [dockConnectionStart, setDockConnectionStart] = useState<{ x: number, y: number } | null>(null);
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });

  const handleReplaceFile = useCallback((e: React.ChangeEvent<HTMLInputElement>, type: 'image' | 'video') => {
      takeSnapshot();
      const file = e.target.files?.[0];
      const targetId = replacementTargetRef.current;
      if (file && targetId) {
          const reader = new FileReader();
          reader.onload = (e) => {
              const result = e.target?.result as string;
              if (type === 'image') {
                  setNodes((nds) => nds.map(n => (n.id === targetId && n.type === 'appNode') ? { ...n, data: { ...n.data, image: result } } : n));
              } else {
                  setNodes((nds) => nds.map(n => (n.id === targetId && n.type === 'appNode') ? { ...n, data: { ...n.data, videoUri: result } } : n));
              }
          };
          reader.readAsDataURL(file);
      }
      e.target.value = '';
      setContextMenu(null);
      replacementTargetRef.current = null;
  }, [setNodes, takeSnapshot]);

  const handleCreateAndConnect = useCallback((type: NodeType, data?: Partial<AppNodeData>) => {
      handleCreateAndConnectManager(type, contextMenuTarget?.nodeId, nodes, edges, takeSnapshot, data);
      setContextMenu(null);
  }, [handleCreateAndConnectManager, contextMenuTarget, nodes, edges, takeSnapshot]);

  const onNodeContextMenu = useCallback(
    (event: React.MouseEvent, node: AppNodeType) => {
      event.preventDefault();
      setContextMenu({
        visible: true,
        x: event.clientX,
        y: event.clientY,
        id: node.id,
      });
      setContextMenuTarget({ type: 'node', id: node.id });
    },
    []
  );

  const onEdgeContextMenu = useCallback(
    (event: React.MouseEvent, edge: Edge) => {
      event.preventDefault();
      setContextMenu({
        visible: true,
        x: event.clientX,
        y: event.clientY,
        id: edge.id,
      });
      setContextMenuTarget({ type: 'connection', id: edge.id });
    },
    []
  );

  const onPaneContextMenu = useCallback(
    (event: React.MouseEvent | MouseEvent) => {
      event.preventDefault();
      setContextMenu({
        visible: true,
        x: event.clientX,
        y: event.clientY,
      });
      setContextMenuTarget({ type: 'pane' });
    },
    []
  );

  const onPaneClick = useCallback(() => setContextMenu(null), []);

  const handleCopyNode = useCallback((nodeId: string) => {
      const targetNode = nodes.find(n => n.id === nodeId);
      if (targetNode && targetNode.type === 'appNode') {
          setClipboard(targetNode);
      }
  }, [nodes, setClipboard]);

  const handleReplaceMedia = useCallback((nodeId: string, mediaType: 'image' | 'video') => {
      replacementTargetRef.current = nodeId;
      if (mediaType === 'video') replaceVideoInputRef.current?.click();
      else replaceImageInputRef.current?.click();
  }, []);

  const handleDeleteNode = useCallback((nodeId: string) => {
      takeSnapshot();
      setNodes((nds) => nds.filter(n => n.id !== nodeId));
  }, [setNodes, takeSnapshot]);

  const handleDeleteEdge = useCallback((edgeId: string) => {
      takeSnapshot();
      setEdges((eds) => eds.filter(e => e.id !== edgeId));
  }, [setEdges, takeSnapshot]);

  const handleAddToLibrary = useCallback((item: any) => {
      const type = item.type.includes('video') ? 'video' : 'image';
      // Prefer item.context (original generation context) over current context
      const targetContext = item.context || saveContext;
      saveAsset(type, item.src, item.title || 'Saved Asset', { prompt: item.title }, targetContext);
  }, [saveAsset, saveContext]);

  // --- Double Click Handler ---
  const onPaneDoubleClick = useCallback((event: React.MouseEvent) => {
      event.preventDefault();
      // Only trigger if clicking on the pane itself, not on nodes
      if ((event.target as HTMLElement).classList.contains('react-flow__pane')) {
           setContextMenu({
              visible: true,
              x: event.clientX,
              y: event.clientY,
              id: 'create-menu',
           });
           setContextMenuTarget({ type: 'create' });
      }
  }, []);

  // Memoized handlers for sidebar
  const handleHistoryItemClick = useCallback((item: any) => {
      const type = item.type.includes('image') ? NodeType.IMAGE_GENERATOR : NodeType.VIDEO_GENERATOR;
      const data = item.type.includes('image') ? { image: item.src } : { videoUri: item.src };
      const pos = screenToFlowPosition({ x: window.innerWidth/2 - 200, y: window.innerHeight/2 - 150 });
      handleAddNode(type, pos, data);
  }, [screenToFlowPosition, handleAddNode]);

  const handleDeleteHistory = useCallback((id: string) => {
      setGenerationHistory(prev => prev.filter(i => i.id !== id));
  }, [setGenerationHistory]);

  const handleRenameAsset = useCallback((id: string, name: string) => {
      updateAsset(id, { name });
  }, [updateAsset]);

  const handleClearCanvas = useCallback(() => {
      confirm({
          title: '确认清空画布？',
          description: '确定要清空画布吗？此操作无法撤销。',
          variant: 'destructive',
          onConfirm: () => {
              setNodes([]);
              setEdges([]);
          }
      });
  }, [confirm, setNodes, setEdges]);

  const handleUndo = useCallback(() => undo(), [undo]);

  return (
    <>
    <ConfirmDialog />
    <div
        className={`w-full h-full overflow-hidden bg-background relative ${isConnecting ? 'is-connecting' : ''}`}
        onDragOver={onDragOver}
        onDrop={onDrop}
        onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
        onMouseUp={() => setDockConnectionStart(null)}
    >
        <div className="absolute inset-0 noise-bg pointer-events-none z-0" />

        {dockConnectionStart && (
             <svg className="absolute inset-0 pointer-events-none z-[2000] overflow-visible" style={{ width: '100%', height: '100%' }}>
                 <line
                     x1={dockConnectionStart.x} y1={dockConnectionStart.y}
                     x2={mousePos.x} y2={mousePos.y}
                     stroke="#22d3ee" strokeWidth="2" strokeDasharray="5,5"
                     className="animate-pulse"
                 />
                 <circle cx={dockConnectionStart.x} cy={dockConnectionStart.y} r="4" fill="#22d3ee" />
                 <circle cx={mousePos.x} cy={mousePos.y} r="4" fill="#22d3ee" />
             </svg>
        )}



        <MemoizedSidebar
            onAddNode={(type, data) => handleAddNode(type, undefined, data)}
            onUndo={handleUndo}
            assetHistory={generationHistory}
            projectAssets={assets}
            projectStoryboards={storyboards}
            projectEpisodes={episodes}
            onHistoryItemClick={handleHistoryItemClick}
            onDeleteAsset={handleDeleteHistory}
            onDeleteProjectAsset={deleteAsset}
            onRenameAsset={handleRenameAsset}
            onAddToLibrary={handleAddToLibrary}
            sourceContext={sidebarSourceContext}
            onOpenProjectDetails={() => setIsProjectDetailsOpen(true)}
            onClear={handleClearCanvas}
        />

        <SaveFeedback
            isSaving={isSaving}
            lastSavedAt={lastSavedAt || undefined}
            error={saveError}
            trigger={manualSaveRequested}
            onComplete={() => setManualSaveRequested(false)}
        />

        <CanvasHeader
            title={projectName || '未命名项目'}
            rightContent={
                <div className="flex items-center gap-3">
                    <UserAvatar />
                    {onClose && (
                        <button
                            onClick={onClose}
                            className="p-2 text-muted-foreground hover:text-white bg-black/20 hover:bg-black/40 backdrop-blur-sm border border-white/5 rounded-full transition-all"
                        >
                            <X size={20} />
                        </button>
                    )}
                </div>
            }
        />

        <input type="file" ref={replaceVideoInputRef} className="hidden" accept="video/*" onChange={(e) => handleReplaceFile(e, 'video')} />
        <input type="file" ref={replaceImageInputRef} className="hidden" accept="image/*" onChange={(e) => handleReplaceFile(e, 'image')} />

        <ReactFlow
            nodes={nodes.filter((n) => n.type === 'appNode')}
            edges={edges}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            onConnect={onConnectWrapper}
            isValidConnection={isValidConnection}
            onNodeDrag={onNodeDrag}
            onNodeDragStart={onNodeDragStart}
            onNodeContextMenu={onNodeContextMenu}
            onEdgeContextMenu={onEdgeContextMenu}
            onPaneContextMenu={onPaneContextMenu}
            onPaneClick={onPaneClick}
            onDoubleClick={onPaneDoubleClick}
            nodeTypes={nodeTypes}
            edgeTypes={edgeTypes}
            defaultViewport={{ x: 0, y: 0, zoom: 0.75 }}
            className="bg-black"
            colorMode="dark"
            minZoom={0.1}
            maxZoom={4}
            defaultEdgeOptions={defaultEdgeOptions}
            connectionLineType={ConnectionLineType.Bezier}
            proOptions={{ hideAttribution: true }}
            connectionLineStyle={{ stroke: 'rgba(255,255,255,0.8)', strokeWidth: 2 }}
        >
            <Background
                variant={BackgroundVariant.Dots}
                gap={32}
                size={1}
                color="#aaa"
                className="opacity-[0.06]"
            />
            {isMiniMapOpen && (
                <MiniMap
                    position="bottom-left"
                    style={{ width: 240, height: 140 }}
                    className="!left-6 !bottom-20 !m-0 bg-black/40 backdrop-blur-md border border-white/10 rounded-xl overflow-hidden shadow-xl z-50 animate-in fade-in slide-in-from-bottom-4 duration-300"
                    nodeColor={() => '#3b82f6'}
                    maskColor="rgba(0, 0, 0, 0.6)"
                    nodeStrokeColor="transparent"
                    nodeBorderRadius={4}
                    zoomable
                    pannable
                />
            )}
        </ReactFlow>

        {/* Render Groups */}
        {groups.map((group) => {
          // Get nodes in this group
          const groupNodes = nodes.filter(n => (n as any).parentId === group.id);

          return (
            <GroupNode
              key={group.id}
              group={group}
              isSelected={selectedGroupId === group.id}
              onSelect={() => setSelectedGroupId(group.id)}
              onUpdate={(updates) => updateGroup(group.id, updates)}
              onDelete={() => deleteGroup(group.id)}
              onUngroup={() => ungroup(group.id)}
              onContextMenu={(e) => {
                setContextMenu({
                  visible: true,
                  x: e.clientX,
                  y: e.clientY,
                  id: group.id
                });
                setContextMenuTarget({ type: 'group', id: group.id });
              }}
              onDragStart={() => {
                takeSnapshot();
              }}
              onDrag={(dx, dy) => {
                // Move all nodes in the group
                setNodes((prevNodes) =>
                  prevNodes.map(node => {
                    if ((node as any).parentId === group.id) {
                      return {
                        ...node,
                        position: {
                          x: node.position.x + dx,
                          y: node.position.y + dy
                        }
                      };
                    }
                    return node;
                  })
                );
                // Update group position
                updateGroup(group.id, {
                  x: group.x + dx,
                  y: group.y + dy
                });
              }}
              onDragEnd={() => {
                // Group position already updated during drag
              }}
            />
          );
        })}

        <CanvasViewportControls
          isMiniMapOpen={isMiniMapOpen}
          onToggleMiniMap={() => setIsMiniMapOpen(!isMiniMapOpen)}
          onToggleQueuePanel={() => setIsQueuePanelOpen(!isQueuePanelOpen)}
          isQueuePanelOpen={isQueuePanelOpen}
        />
        <CanvasShortcuts />

        {contextMenu && contextMenuTarget && (
            <MemoizedCanvasContextMenu
                contextMenu={contextMenu}
                target={contextMenuTarget}
                nodes={nodes}
                clipboard={clipboard}
                onClose={() => setContextMenu(null)}
                onCreateNode={(type, data) => handleAddNode(type, screenToFlowPosition({ x: contextMenu.x, y: contextMenu.y }), data)}
                onCreateAndConnect={handleCreateAndConnect}
                onCopyNode={handleCopyNode}
                onReplaceMedia={handleReplaceMedia}
                onDeleteNode={handleDeleteNode}
                onPaste={() => pasteNodes({ x: contextMenu.x, y: contextMenu.y })}
                onDeleteEdge={handleDeleteEdge}
                onCreateGroup={() => createGroupFromSelected()}
                onUngroup={ungroup}
                onDeleteGroup={deleteGroup}
            />
        )}

        {/* Overlays */}
        {cropRequest && <ImageCropper />}
        {expandedMedia && <ExpandedView />}
        {isSketchEditorOpen && <SketchEditor onClose={() => setIsSketchEditorOpen(false)} onGenerate={handleSketchResult} />}
        {isProjectDetailsOpen && <ProjectDetailsModal isOpen={isProjectDetailsOpen} onClose={() => setIsProjectDetailsOpen(false)} />}

        <MemoizedRightSidebar
            activeCanvasId={activeCanvasId || null}
            activeCanvasType={activeCanvasType || 'main'}
            onSwitchCanvas={switchCanvas || (async () => {})}
        />

        {/* Interaction Input */}
        <div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-50">
            <InteractionInput
                onGenerate={handleInteractionGenerate}
            />
        </div>

        {/* Onboarding Tour */}
        <OnboardingTour />

        {/* Batch Generation Toolbar */}
        <AnimatePresence>
          {isBatchToolbarOpen && (
            <BatchGenerationToolbar
              onClose={() => setIsBatchToolbarOpen(false)}
            />
          )}
        </AnimatePresence>

        {/* Generation Queue Panel */}
        <GenerationQueuePanel
          isOpen={isQueuePanelOpen}
          onClose={() => setIsQueuePanelOpen(false)}
        />

        {/* Prompt Template Library */}
        <PromptTemplateLibrary
          isOpen={isTemplateLibraryOpen}
          onClose={() => setIsTemplateLibraryOpen(false)}
          onSelectTemplate={(template) => {
            // Handle template selection - copy to clipboard for now
            navigator.clipboard.writeText(template.content);
            // Could also apply directly to selected node
          }}
        />
    </div>
    </>
  );
}

export function Canvas(props: CanvasContextType) {
  return (
    <CanvasProviders {...props}>
        <Flow />
    </CanvasProviders>
  );
}
