import { useCallback, useMemo, useState, useEffect } from 'react' import { ReactFlow, Background, Controls, MiniMap, BackgroundVariant, useReactFlow, useNodesState, useEdgesState, ReactFlowProvider, PanOnScrollMode, type NodeMouseHandler, } from '@xyflow/react' import '@xyflow/react/dist/style.css' import { FlowCanvasNode, NODE_TYPE_CONFIG } from './FlowCanvasNode' import { FlowCanvasAnswerNode } from './FlowCanvasAnswerNode' import { useTreeLayout } from './useTreeLayout' import { cn } from '@/lib/utils' import { Map as MapIcon, MapPinOff } from 'lucide-react' import type { FlowCanvasNodeData } from './FlowCanvasNode' import type { FlowCanvasAnswerNodeData } from './FlowCanvasAnswerNode' const nodeTypes = { flowNode: FlowCanvasNode, answerStub: FlowCanvasAnswerNode, } interface FlowCanvasProps { selectedNodeId: string | null onNodeSelect: (nodeId: string | null) => void onSelectAnswerType: (nodeId: string, type: 'decision' | 'action' | 'solution') => void onNodeContextMenu?: (e: React.MouseEvent, nodeId: string) => void } function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onNodeContextMenu }: FlowCanvasProps) { const { fitView, setCenter } = useReactFlow() const { nodes: layoutNodes, edges: layoutEdges, collapsedNodeIds, toggleCollapse, onNodesMeasured } = useTreeLayout() const [minimapVisible, setMinimapVisible] = useState(true) // Inject callbacks into node data (because useTreeLayout creates placeholder functions) const nodesWithCallbacks = useMemo(() => { return layoutNodes.map(n => { if (n.type === 'flowNode') { const data = n.data as unknown as FlowCanvasNodeData return { ...n, selected: n.id === selectedNodeId, data: { ...data, onToggleCollapse: toggleCollapse, onContextMenu: onNodeContextMenu }, } } if (n.type === 'answerStub') { const data = n.data as unknown as FlowCanvasAnswerNodeData return { ...n, selected: n.id === selectedNodeId, data: { ...data, onSelectType: onSelectAnswerType }, } } return n }) }, [layoutNodes, selectedNodeId, toggleCollapse, onSelectAnswerType, onNodeContextMenu]) const [nodes, setNodes, onNodesChange] = useNodesState(nodesWithCallbacks) const [edges, setEdges, onEdgesChange] = useEdgesState(layoutEdges) // Sync layout changes into React Flow state useEffect(() => { setNodes(nodesWithCallbacks) setEdges(layoutEdges) }, [nodesWithCallbacks, layoutEdges, setNodes, setEdges]) // Fit view after layout changes useEffect(() => { // Small delay to let React Flow process the node updates const timer = setTimeout(() => { fitView({ padding: 0.1, duration: 200 }) }, 50) return () => clearTimeout(timer) }, [layoutNodes.length, collapsedNodeIds.size]) // eslint-disable-line react-hooks/exhaustive-deps // Auto-center on selected node when panel opens useEffect(() => { if (!selectedNodeId) return const node = nodes.find(n => n.id === selectedNodeId) if (node) { const x = node.position.x + 140 // center of 280px node const y = node.position.y + 50 setCenter(x, y, { duration: 300, zoom: 1 }) } }, [selectedNodeId]) // eslint-disable-line react-hooks/exhaustive-deps // Height measurement correction useEffect(() => { if (nodes.length > 0 && nodes.some(n => n.measured?.height)) { onNodesMeasured(nodes) } }, [nodes]) // eslint-disable-line react-hooks/exhaustive-deps const handleNodeClick: NodeMouseHandler = useCallback((_event, node) => { onNodeSelect(node.id) }, [onNodeSelect]) const handlePaneClick = useCallback(() => { onNodeSelect(null) }, [onNodeSelect]) // Custom minimap node color based on actual tree node type const minimapNodeColor = useCallback((rfNode: { data?: unknown }) => { const data = rfNode.data as (FlowCanvasNodeData & FlowCanvasAnswerNodeData) | undefined if (!data || !('node' in data)) return '#6b7280' const treeNode = data.node if (treeNode.type === 'answer') return '#6b7280' const config = NODE_TYPE_CONFIG[treeNode.type as keyof typeof NODE_TYPE_CONFIG] return config?.minimapColor ?? '#6b7280' }, []) return (