'use client'

import { useCallback, useState, useEffect, useMemo, useRef } from 'react'
import {
  ReactFlow,
  Controls,
  Background,
  BackgroundVariant,
  useNodesState,
  useEdgesState,
  Connection,
  MarkerType,
  Panel,
  useReactFlow,
  ReactFlowProvider,
} from '@xyflow/react'
import '@xyflow/react/dist/style.css'

import FunnelNode from './nodes/FunnelNode'
import OutcomeNode from './nodes/OutcomeNode'
import SourceNode from './nodes/SourceNode'
import AllocationEdge from './edges/AllocationEdge'
import StreamEdge from './edges/StreamEdge'
import IntegrationPanel from './IntegrationPanel'
import { ConnectionContext, type ConnectingFrom } from './ConnectionContext'
import type { FlowNode, FlowEdge, FunnelNodeData, OutcomeNodeData, SourceNodeData, IntegrationConfig, AllocationEdgeData } from '@/lib/types'
import { SPENDING_COLORS, OVERFLOW_COLORS, SOURCE_COLORS } from '@/lib/presets'
import { simulateTick } from '@/lib/simulation'

const nodeTypes = {
  funnel: FunnelNode,
  outcome: OutcomeNode,
  source: SourceNode,
}

const edgeTypes = {
  allocation: AllocationEdge,
  stream: StreamEdge,
}

// Generate edges with proportional Sankey-style widths
function generateEdges(
  nodes: FlowNode[],
  onAdjust?: (sourceId: string, targetId: string, edgeType: 'overflow' | 'spending' | 'source', delta: number) => void
): FlowEdge[] {
  const edges: FlowEdge[] = []

  const flowValues: number[] = []
  nodes.forEach((node) => {
    if (node.type === 'source') {
      const data = node.data as SourceNodeData
      const rate = data.flowRate || 1
      data.targetAllocations?.forEach((alloc) => {
        flowValues.push((alloc.percentage / 100) * rate)
      })
      return
    }
    if (node.type !== 'funnel') return
    const data = node.data as FunnelNodeData
    const rate = data.inflowRate || 1

    data.overflowAllocations?.forEach((alloc) => {
      flowValues.push((alloc.percentage / 100) * rate)
    })
    data.spendingAllocations?.forEach((alloc) => {
      flowValues.push((alloc.percentage / 100) * rate)
    })
  })

  const maxFlow = Math.max(...flowValues, 1)
  const MIN_WIDTH = 3
  const MAX_WIDTH = 24

  // Source node edges
  nodes.forEach((node) => {
    if (node.type !== 'source') return
    const data = node.data as SourceNodeData
    const rate = data.flowRate || 1
    const allocCount = data.targetAllocations?.length ?? 0

    data.targetAllocations?.forEach((alloc) => {
      const flowValue = (alloc.percentage / 100) * rate
      const strokeWidth = MIN_WIDTH + (flowValue / maxFlow) * (MAX_WIDTH - MIN_WIDTH)

      edges.push({
        id: `source-${node.id}-${alloc.targetId}`,
        source: node.id,
        target: alloc.targetId,
        sourceHandle: 'source-out',
        animated: true,
        style: {
          stroke: alloc.color,
          strokeWidth,
          opacity: 0.8,
        },
        markerEnd: {
          type: MarkerType.ArrowClosed,
          color: alloc.color,
          width: 16,
          height: 16,
        },
        data: {
          allocation: alloc.percentage,
          color: alloc.color,
          edgeType: 'source' as const,
          sourceId: node.id,
          targetId: alloc.targetId,
          siblingCount: allocCount,
          onAdjust,
        },
        type: 'allocation',
      })
    })
  })

  nodes.forEach((node) => {
    if (node.type !== 'funnel') return
    const data = node.data as FunnelNodeData
    const sourceX = node.position.x
    const rate = data.inflowRate || 1

    const overflowCount = data.overflowAllocations?.length ?? 0
    data.overflowAllocations?.forEach((alloc) => {
      const flowValue = (alloc.percentage / 100) * rate
      const strokeWidth = MIN_WIDTH + (flowValue / maxFlow) * (MAX_WIDTH - MIN_WIDTH)

      const targetNode = nodes.find(n => n.id === alloc.targetId)
      if (!targetNode) return

      const targetX = targetNode.position.x
      const goingRight = targetX > sourceX
      const sourceHandle = goingRight ? 'outflow-right' : 'outflow-left'

      edges.push({
        id: `outflow-${node.id}-${alloc.targetId}`,
        source: node.id,
        target: alloc.targetId,
        sourceHandle,
        targetHandle: undefined,
        animated: true,
        style: {
          stroke: alloc.color,
          strokeWidth,
          opacity: 0.7,
        },
        markerEnd: {
          type: MarkerType.ArrowClosed,
          color: alloc.color,
          width: 16,
          height: 16,
        },
        data: {
          allocation: alloc.percentage,
          color: alloc.color,
          edgeType: 'overflow' as const,
          sourceId: node.id,
          targetId: alloc.targetId,
          siblingCount: overflowCount,
          onAdjust,
        },
        type: 'allocation',
      })
    })

    const spendingCount = data.spendingAllocations?.length ?? 0
    data.spendingAllocations?.forEach((alloc) => {
      const flowValue = (alloc.percentage / 100) * rate
      const strokeWidth = MIN_WIDTH + (flowValue / maxFlow) * (MAX_WIDTH - MIN_WIDTH)

      edges.push({
        id: `spending-${node.id}-${alloc.targetId}`,
        source: node.id,
        target: alloc.targetId,
        sourceHandle: 'spending-out',
        animated: true,
        style: {
          stroke: alloc.color,
          strokeWidth,
          opacity: 0.8,
        },
        markerEnd: {
          type: MarkerType.ArrowClosed,
          color: alloc.color,
          width: 16,
          height: 16,
        },
        data: {
          allocation: alloc.percentage,
          color: alloc.color,
          edgeType: 'spending' as const,
          sourceId: node.id,
          targetId: alloc.targetId,
          siblingCount: spendingCount,
          onAdjust,
        },
        type: 'allocation',
      })
    })

    // Stream edges (Superfluid planning)
    data.streamAllocations?.forEach((stream) => {
      const statusColor = stream.status === 'active' ? '#3b82f6' : stream.status === 'paused' ? '#f97316' : '#22c55e'
      edges.push({
        id: `stream-${node.id}-${stream.targetId}`,
        source: node.id,
        target: stream.targetId,
        sourceHandle: 'stream-out',
        animated: false,
        style: {
          stroke: statusColor,
          strokeWidth: 3,
        },
        markerEnd: {
          type: MarkerType.ArrowClosed,
          color: statusColor,
          width: 14,
          height: 14,
        },
        data: {
          flowRate: stream.flowRate,
          tokenSymbol: stream.tokenSymbol,
          status: stream.status,
          sourceId: node.id,
          targetId: stream.targetId,
        },
        type: 'stream',
      })
    })
  })

  // Source node edges
  nodes.forEach((node) => {
    if (node.type !== 'source') return
    const data = node.data as SourceNodeData
    const rate = data.flowRate || 1
    const allocCount = data.targetAllocations?.length ?? 0

    data.targetAllocations?.forEach((alloc) => {
      const flowValue = (alloc.percentage / 100) * rate
      const strokeWidth = MIN_WIDTH + (flowValue / maxFlow) * (MAX_WIDTH - MIN_WIDTH)

      edges.push({
        id: `source-${node.id}-${alloc.targetId}`,
        source: node.id,
        target: alloc.targetId,
        sourceHandle: 'source-out',
        animated: true,
        style: {
          stroke: alloc.color || '#10b981',
          strokeWidth,
          opacity: 0.8,
        },
        markerEnd: {
          type: MarkerType.ArrowClosed,
          color: alloc.color || '#10b981',
          width: 16,
          height: 16,
        },
        data: {
          allocation: alloc.percentage,
          color: alloc.color || '#10b981',
          edgeType: 'spending' as const,
          sourceId: node.id,
          targetId: alloc.targetId,
          siblingCount: allocCount,
          onAdjust,
        },
        type: 'allocation',
      })
    })
  })

  return edges
}

interface FlowCanvasInnerProps {
  initialNodes: FlowNode[]
  mode: 'demo' | 'space'
  onNodesChange?: (nodes: FlowNode[]) => void
  integrations?: IntegrationConfig
  onIntegrationsChange?: (config: IntegrationConfig) => void
}

function FlowCanvasInner({ initialNodes: initNodes, mode, onNodesChange, integrations, onIntegrationsChange }: FlowCanvasInnerProps) {
  const [showIntegrations, setShowIntegrations] = useState(false)
  const [nodes, setNodes, onNodesChangeHandler] = useNodesState(initNodes)
  const [edges, setEdges, onEdgesChange] = useEdgesState([] as FlowEdge[])
  const [isSimulating, setIsSimulating] = useState(mode === 'demo')
  const [panelsCollapsed, setPanelsCollapsed] = useState(false)
  const [connectingFrom, setConnectingFrom] = useState<ConnectingFrom | null>(null)
  const edgesRef = useRef(edges)
  edgesRef.current = edges
  const { screenToFlowPosition } = useReactFlow()

  // Notify parent of node changes for save/share
  const nodesRef = useRef(nodes)
  nodesRef.current = nodes
  useEffect(() => {
    if (onNodesChange) {
      onNodesChange(nodes as FlowNode[])
    }
  }, [nodes, onNodesChange])

  // Adjust allocation percentage inline from edge +/- buttons
  const onAdjustAllocation = useCallback(
    (sourceId: string, targetId: string, edgeType: 'overflow' | 'spending' | 'source', delta: number) => {
      setNodes((nds) => nds.map((node) => {
        if (node.id !== sourceId) return node

        // Source node allocation adjustment
        if (edgeType === 'source' && node.type === 'source') {
          const data = node.data as SourceNodeData
          const allocs = [...data.targetAllocations]
          if (allocs.length <= 1) return node

          const idx = allocs.findIndex(a => a.targetId === targetId)
          if (idx === -1) return node

          const current = allocs[idx].percentage
          const newPct = Math.max(5, Math.min(95, current + delta))
          const actualDelta = newPct - current
          if (actualDelta === 0) return node

          const siblings = allocs.filter((_, i) => i !== idx)
          const siblingTotal = siblings.reduce((s, a) => s + a.percentage, 0)

          const updated = allocs.map((a, i) => {
            if (i === idx) return { ...a, percentage: newPct }
            const share = siblingTotal > 0 ? a.percentage / siblingTotal : 1 / siblings.length
            return { ...a, percentage: Math.max(1, Math.round(a.percentage - actualDelta * share)) }
          })

          const sum = updated.reduce((s, a) => s + a.percentage, 0)
          if (sum !== 100 && updated.length > 1) {
            const diff = 100 - sum
            const largestSibIdx = updated.reduce((best, a, i) =>
              i !== idx && a.percentage > updated[best].percentage ? i : best, idx === 0 ? 1 : 0)
            updated[largestSibIdx] = { ...updated[largestSibIdx], percentage: updated[largestSibIdx].percentage + diff }
          }

          return { ...node, data: { ...data, targetAllocations: updated } }
        }

        // Funnel node allocation adjustment
        if (node.type !== 'funnel') return node
        const data = node.data as FunnelNodeData
        const allocKey = edgeType === 'overflow' ? 'overflowAllocations' : 'spendingAllocations'
        const allocs = [...data[allocKey]]
        if (allocs.length <= 1) return node

        const idx = allocs.findIndex(a => a.targetId === targetId)
        if (idx === -1) return node

        const current = allocs[idx].percentage
        const newPct = Math.max(5, Math.min(95, current + delta))
        const actualDelta = newPct - current
        if (actualDelta === 0) return node

        // Apply delta to target, distribute inverse across siblings
        const siblings = allocs.filter((_, i) => i !== idx)
        const siblingTotal = siblings.reduce((s, a) => s + a.percentage, 0)

        const updated = allocs.map((a, i) => {
          if (i === idx) return { ...a, percentage: newPct }
          // Proportionally adjust siblings
          const share = siblingTotal > 0 ? a.percentage / siblingTotal : 1 / siblings.length
          return { ...a, percentage: Math.max(1, Math.round(a.percentage - actualDelta * share)) }
        })

        // Normalize to exactly 100
        const sum = updated.reduce((s, a) => s + a.percentage, 0)
        if (sum !== 100 && updated.length > 1) {
          const diff = 100 - sum
          // Apply rounding correction to largest sibling
          const largestSibIdx = updated.reduce((best, a, i) =>
            i !== idx && a.percentage > updated[best].percentage ? i : best, idx === 0 ? 1 : 0)
          updated[largestSibIdx] = { ...updated[largestSibIdx], percentage: updated[largestSibIdx].percentage + diff }
        }

        return {
          ...node,
          data: { ...data, [allocKey]: updated },
        }
      }))
    },
    [setNodes]
  )

  // Smart edge regeneration
  const allocationsKey = useMemo(() => {
    const funnels = nodes
      .filter(n => n.type === 'funnel')
      .map(n => {
        const d = n.data as FunnelNodeData
        return {
          id: n.id,
          overflow: d.overflowAllocations,
          spending: d.spendingAllocations,
          streams: d.streamAllocations,
          rate: d.inflowRate,
        }
      })
    const sources = nodes
      .filter(n => n.type === 'source')
      .map(n => {
        const d = n.data as SourceNodeData
        return {
          id: n.id,
          allocations: d.targetAllocations,
          rate: d.flowRate,
        }
      })
    return JSON.stringify({ funnels, sources })
  }, [nodes])

  useEffect(() => {
    setEdges(generateEdges(nodes as FlowNode[], onAdjustAllocation))
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [allocationsKey, onAdjustAllocation])

  const onConnect = useCallback(
    (params: Connection) => {
      if (!params.source || !params.target) return

      const isSourceOut = params.sourceHandle === 'source-out'
      const isOverflow = params.sourceHandle?.startsWith('outflow')
      const isSpending = params.sourceHandle === 'spending-out'
      const isStream = params.sourceHandle === 'stream-out'

      if (!isSourceOut && !isOverflow && !isSpending && !isStream) return

      // Source node connections
      if (isSourceOut) {
        setNodes((nds) => nds.map((node) => {
          if (node.id !== params.source || node.type !== 'source') return node
          const data = node.data as SourceNodeData
          const existing = data.targetAllocations || []
          if (existing.some(a => a.targetId === params.target)) return node
          const newPct = existing.length === 0 ? 100 : Math.floor(100 / (existing.length + 1))
          const redistributed = existing.map(a => ({
            ...a,
            percentage: Math.floor(a.percentage * existing.length / (existing.length + 1))
          }))
          return {
            ...node,
            data: {
              ...data,
              targetAllocations: [
                ...redistributed,
                {
                  targetId: params.target!,
                  percentage: newPct,
                  color: SOURCE_COLORS[existing.length % SOURCE_COLORS.length],
                },
              ],
            },
          }
        }))
        return
      }

      setNodes((nds) => nds.map((node) => {
        if (node.id !== params.source || node.type !== 'funnel') return node
        const data = node.data as FunnelNodeData

        if (isOverflow) {
          const existing = data.overflowAllocations || []
          if (existing.some(a => a.targetId === params.target)) return node
          const newPct = existing.length === 0 ? 100 : Math.floor(100 / (existing.length + 1))
          const redistributed = existing.map(a => ({
            ...a,
            percentage: Math.floor(a.percentage * existing.length / (existing.length + 1))
          }))
          return {
            ...node,
            data: {
              ...data,
              overflowAllocations: [
                ...redistributed,
                {
                  targetId: params.target!,
                  percentage: newPct,
                  color: OVERFLOW_COLORS[existing.length % OVERFLOW_COLORS.length],
                },
              ],
            },
          }
        } else if (isSpending) {
          const existing = data.spendingAllocations || []
          if (existing.some(a => a.targetId === params.target)) return node
          const newPct = existing.length === 0 ? 100 : Math.floor(100 / (existing.length + 1))
          const redistributed = existing.map(a => ({
            ...a,
            percentage: Math.floor(a.percentage * existing.length / (existing.length + 1))
          }))
          return {
            ...node,
            data: {
              ...data,
              spendingAllocations: [
                ...redistributed,
                {
                  targetId: params.target!,
                  percentage: newPct,
                  color: SPENDING_COLORS[existing.length % SPENDING_COLORS.length],
                },
              ],
            },
          }
        } else if (isStream) {
          const existing = data.streamAllocations || []
          if (existing.some(s => s.targetId === params.target)) return node
          return {
            ...node,
            data: {
              ...data,
              streamAllocations: [
                ...existing,
                {
                  targetId: params.target!,
                  flowRate: 100,
                  tokenSymbol: 'DAIx',
                  status: 'planned' as const,
                  color: '#22c55e',
                },
              ],
            },
          }
        } else {
          return node
        }
      }))
    },
    [setNodes]
  )

  const onReconnect = useCallback(
    (oldEdge: FlowEdge, newConnection: Connection) => {
      const edgeData = oldEdge.data as AllocationEdgeData | undefined
      if (!edgeData || !newConnection.target) return

      const oldTargetId = oldEdge.target
      const newTargetId = newConnection.target
      if (oldTargetId === newTargetId) return

      // Source edges: reconnect source allocation
      if (edgeData.edgeType === 'source') {
        setNodes((nds) => nds.map((node) => {
          if (node.id !== oldEdge.source || node.type !== 'source') return node
          const data = node.data as SourceNodeData
          return {
            ...node,
            data: {
              ...data,
              targetAllocations: data.targetAllocations.map(a =>
                a.targetId === oldTargetId ? { ...a, targetId: newTargetId } : a
              ),
            },
          }
        }))
        return
      }

      // Stream edges: reconnect stream allocation
      if (oldEdge.type === 'stream') {
        setNodes((nds) => nds.map((node) => {
          if (node.id !== oldEdge.source || node.type !== 'funnel') return node
          const data = node.data as FunnelNodeData
          return {
            ...node,
            data: {
              ...data,
              streamAllocations: (data.streamAllocations || []).map(s =>
                s.targetId === oldTargetId ? { ...s, targetId: newTargetId } : s
              ),
            },
          }
        }))
        return
      }

      setNodes((nds) => nds.map((node) => {
        if (node.id !== oldEdge.source || node.type !== 'funnel') return node
        const data = node.data as FunnelNodeData

        if (edgeData.edgeType === 'overflow') {
          return {
            ...node,
            data: {
              ...data,
              overflowAllocations: data.overflowAllocations.map(a =>
                a.targetId === oldTargetId ? { ...a, targetId: newTargetId } : a
              ),
            },
          }
        } else {
          return {
            ...node,
            data: {
              ...data,
              spendingAllocations: data.spendingAllocations.map(a =>
                a.targetId === oldTargetId ? { ...a, targetId: newTargetId } : a
              ),
            },
          }
        }
      }))
    },
    [setNodes]
  )

  const onConnectStart = useCallback(
    (_: unknown, params: { nodeId: string | null; handleId: string | null; handleType: string | null }) => {
      if (params.nodeId && params.handleId && params.handleType) {
        setConnectingFrom({ nodeId: params.nodeId, handleId: params.handleId, handleType: params.handleType })
      }
    },
    []
  )

  const onConnectEnd = useCallback(() => {
    setConnectingFrom(null)
  }, [])

  const handleEdgesChange = useCallback(
    (changes: Parameters<typeof onEdgesChange>[0]) => {
      changes.forEach((change) => {
        if (change.type === 'remove') {
          const edge = edgesRef.current.find(e => e.id === change.id)
          if (edge?.data) {
            const allocData = edge.data as AllocationEdgeData

            // Source edge removal
            if (allocData.edgeType === 'source') {
              setNodes((nds) => nds.map((node) => {
                if (node.id !== edge.source || node.type !== 'source') return node
                const data = node.data as SourceNodeData
                const filtered = data.targetAllocations.filter(a => a.targetId !== edge.target)
                const total = filtered.reduce((s, a) => s + a.percentage, 0)
                return {
                  ...node,
                  data: {
                    ...data,
                    targetAllocations: total > 0
                      ? filtered.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
                      : filtered,
                  },
                }
              }))
              return
            }

            setNodes((nds) => nds.map((node) => {
              if (node.id !== edge.source || node.type !== 'funnel') return node
              const data = node.data as FunnelNodeData

              // Stream edge removal
              if (edge.type === 'stream') {
                return {
                  ...node,
                  data: {
                    ...data,
                    streamAllocations: (data.streamAllocations || []).filter(s => s.targetId !== edge.target),
                  },
                }
              }

              if (allocData.edgeType === 'overflow') {
                const filtered = data.overflowAllocations.filter(a => a.targetId !== edge.target)
                const total = filtered.reduce((s, a) => s + a.percentage, 0)
                return {
                  ...node,
                  data: {
                    ...data,
                    overflowAllocations: total > 0
                      ? filtered.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
                      : filtered,
                  },
                }
              } else {
                const filtered = data.spendingAllocations.filter(a => a.targetId !== edge.target)
                const total = filtered.reduce((s, a) => s + a.percentage, 0)
                return {
                  ...node,
                  data: {
                    ...data,
                    spendingAllocations: total > 0
                      ? filtered.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
                      : filtered,
                  },
                }
              }
            }))
          }
        }
      })

      onEdgesChange(changes)
    },
    [onEdgesChange, setNodes]
  )

  // Import nodes from integrations panel
  const handleImportNodes = useCallback((newNodes: FlowNode[]) => {
    setNodes((nds) => [...nds, ...newNodes])
  }, [setNodes])

  // Add funnel node at viewport center
  const addFunnel = useCallback(() => {
    const pos = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 })
    const newId = `funnel-${Date.now()}`
    setNodes((nds) => [
      ...nds,
      {
        id: newId,
        type: 'funnel',
        position: pos,
        data: {
          label: 'New Funnel',
          currentValue: 0,
          minThreshold: 10000,
          maxThreshold: 40000,
          maxCapacity: 50000,
          inflowRate: 0,
          overflowAllocations: [],
          spendingAllocations: [],
        } as FunnelNodeData,
      },
    ])
  }, [setNodes, screenToFlowPosition])

  // Add outcome node at viewport center
  const addOutcome = useCallback(() => {
    const pos = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 + 100 })
    const newId = `outcome-${Date.now()}`
    setNodes((nds) => [
      ...nds,
      {
        id: newId,
        type: 'outcome',
        position: pos,
        data: {
          label: 'New Outcome',
          description: '',
          fundingReceived: 0,
          fundingTarget: 20000,
          status: 'not-started',
        } as OutcomeNodeData,
      },
    ])
  }, [setNodes, screenToFlowPosition])

  // Add source node at viewport center
  const addSource = useCallback(() => {
    const pos = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 - 100 })
    const newId = `source-${Date.now()}`
    setNodes((nds) => [
      ...nds,
      {
        id: newId,
        type: 'source',
        position: pos,
        data: {
          label: 'New Source',
          sourceType: 'unconfigured',
          flowRate: 0,
          targetAllocations: [],
        } as SourceNodeData,
      },
    ])
  }, [setNodes, screenToFlowPosition])

  // Handle node deletion — clean up references in other nodes
  const onNodesDelete = useCallback((deletedNodes: FlowNode[]) => {
    const deletedIds = new Set(deletedNodes.map(n => n.id))

    setNodes((nds) => nds.map((node) => {
      if (node.type === 'source') {
        const data = node.data as SourceNodeData
        const filtered = data.targetAllocations.filter(a => !deletedIds.has(a.targetId))
        if (filtered.length === data.targetAllocations.length) return node
        const total = filtered.reduce((s, a) => s + a.percentage, 0)
        return {
          ...node,
          data: {
            ...data,
            targetAllocations: total > 0
              ? filtered.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
              : filtered,
          },
        }
      }
      if (node.type === 'funnel') {
        const data = node.data as FunnelNodeData
        let changed = false

        let overflow = data.overflowAllocations || []
        const filteredOverflow = overflow.filter(a => !deletedIds.has(a.targetId))
        if (filteredOverflow.length !== overflow.length) {
          changed = true
          const total = filteredOverflow.reduce((s, a) => s + a.percentage, 0)
          overflow = total > 0
            ? filteredOverflow.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
            : filteredOverflow
        }

        let spending = data.spendingAllocations || []
        const filteredSpending = spending.filter(a => !deletedIds.has(a.targetId))
        if (filteredSpending.length !== spending.length) {
          changed = true
          const total = filteredSpending.reduce((s, a) => s + a.percentage, 0)
          spending = total > 0
            ? filteredSpending.map(a => ({ ...a, percentage: Math.round((a.percentage / total) * 100) }))
            : filteredSpending
        }

        if (!changed) return node
        return {
          ...node,
          data: {
            ...data,
            overflowAllocations: overflow,
            spendingAllocations: spending,
          },
        }
      }
      return node
    }))
  }, [setNodes])

  // Simulation — real flow logic (inflow → overflow → spending → outcomes)
  useEffect(() => {
    if (!isSimulating) return

    const interval = setInterval(() => {
      setNodes((nds) => simulateTick(nds as FlowNode[]))
    }, 1000)

    return () => clearInterval(interval)
  }, [isSimulating, setNodes])

  // Auto-collapse title & legend panels after 5 seconds
  useEffect(() => {
    const timer = setTimeout(() => setPanelsCollapsed(true), 5000)
    return () => clearTimeout(timer)
  }, [])

  return (
    <div className="w-full h-full">
      <ConnectionContext.Provider value={connectingFrom}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChangeHandler}
        onEdgesChange={handleEdgesChange}
        onNodesDelete={onNodesDelete}
        onConnect={onConnect}
        onReconnect={onReconnect}
        onConnectStart={onConnectStart}
        onConnectEnd={onConnectEnd}
        nodeTypes={nodeTypes}
        edgeTypes={edgeTypes}
        edgesReconnectable={true}
        deleteKeyCode={['Backspace', 'Delete']}
        fitView
        fitViewOptions={{ padding: 0.15 }}
        minZoom={0.005}
        maxZoom={4}
        panOnScroll={true}
        zoomOnPinch={true}
        zoomActivationKeyCode="Meta"
        className="bg-slate-50"
        connectionLineStyle={{ stroke: '#94a3b8', strokeWidth: 3 }}
        isValidConnection={(connection) => connection.source !== connection.target}
        defaultEdgeOptions={{ type: 'smoothstep' }}
      >
        <Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#e2e8f0" />
        <Controls className="bg-white border border-slate-200 rounded-lg shadow-sm" />

        {/* Title Panel */}
        <Panel position="top-left" className="m-4">
          <div
            className="bg-white rounded-lg shadow-lg border border-slate-200 cursor-pointer transition-all duration-300 overflow-hidden"
            onClick={() => setPanelsCollapsed((c) => !c)}
          >
            <div className="px-4 py-2 flex items-center gap-2">
              <h1 className="text-sm font-bold text-slate-800">TBFF</h1>
              <svg className={`w-3 h-3 text-slate-400 transition-transform duration-300 ${panelsCollapsed ? '' : 'rotate-180'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
              </svg>
            </div>
            {!panelsCollapsed && (
              <div className="px-4 pb-3 border-t border-slate-100 pt-2">
                <p className="text-xs text-slate-500">
                  <span className="text-emerald-600">Sources</span> &rarr;
                  <span className="text-amber-600 ml-1">Funnels</span> &rarr;
                  <span className="text-pink-600 ml-1">Outcomes</span>
                </p>
                <p className="text-[10px] text-slate-400 mt-1">
                  Drag handles to connect &bull; Double-click to edit &bull; Select + Delete to remove
                </p>
              </div>
            )}
          </div>
        </Panel>

        {/* Top-right Controls */}
        <Panel position="top-right" className="m-4 flex gap-2">
          {mode === 'space' && (
            <>
              <button
                onClick={() => setShowIntegrations(true)}
                className="px-3 py-2 rounded-lg font-medium shadow-sm text-sm bg-indigo-500 text-white hover:bg-indigo-600 transition-all"
              >
                Link Data
              </button>
              <button
                onClick={addSource}
                className="px-3 py-2 rounded-lg font-medium shadow-sm text-sm bg-emerald-500 text-white hover:bg-emerald-600 transition-all"
              >
                + Source
              </button>
              <button
                onClick={addFunnel}
                className="px-3 py-2 rounded-lg font-medium shadow-sm text-sm bg-amber-500 text-white hover:bg-amber-600 transition-all"
              >
                + Funnel
              </button>
              <button
                onClick={addOutcome}
                className="px-3 py-2 rounded-lg font-medium shadow-sm text-sm bg-pink-500 text-white hover:bg-pink-600 transition-all"
              >
                + Outcome
              </button>
            </>
          )}
          <button
            onClick={() => setIsSimulating(!isSimulating)}
            className={`px-4 py-2 rounded-lg font-medium shadow-sm transition-all text-sm ${
              isSimulating
                ? 'bg-emerald-500 text-white hover:bg-emerald-600'
                : 'bg-slate-200 text-slate-700 hover:bg-slate-300'
            }`}
          >
            {isSimulating ? 'Pause' : 'Start'}
          </button>
        </Panel>

        {/* Legend */}
        <Panel position="bottom-left" className="m-4">
          <div
            className="bg-white rounded-lg shadow-lg border border-slate-200 cursor-pointer transition-all duration-300 overflow-hidden"
            onClick={() => setPanelsCollapsed((c) => !c)}
          >
            <div className="px-3 py-2 flex items-center gap-2">
              <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide">Legend</div>
              {panelsCollapsed && (
                <div className="flex items-center gap-1 ml-1">
                  <div className="w-2 h-2 rounded-sm bg-emerald-500" />
                  <div className="w-2 h-2 rounded-sm bg-amber-500" />
                  <div className="w-2 h-2 rounded-sm bg-pink-500" />
                </div>
              )}
              <svg className={`w-3 h-3 text-slate-400 transition-transform duration-300 ${panelsCollapsed ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
              </svg>
            </div>
            {!panelsCollapsed && (
              <div className="px-3 pb-3 border-t border-slate-100 pt-2">
                <div className="space-y-1.5 text-xs">
                  <div className="flex items-center gap-2">
                    <div className="w-3 h-3 rounded bg-emerald-500" />
                    <span className="text-slate-600">Source (funding origin)</span>
                  </div>
                  <div className="flex items-center gap-2">
                    <div className="w-3 h-3 rounded bg-amber-500" />
                    <span className="text-slate-600">Funnel (threshold pool)</span>
                  </div>
                  <div className="flex items-center gap-2">
                    <div className="w-3 h-3 rounded bg-pink-500" />
                    <span className="text-slate-600">Outcome (deliverable)</span>
                  </div>
                  <div className="border-t border-slate-200 pt-1.5 mt-1.5 space-y-1.5">
                    <div className="flex items-center gap-2">
                      <div className="w-3 h-0.5 bg-emerald-500" />
                      <span className="text-slate-600">Source flow</span>
                    </div>
                    <div className="flex items-center gap-2">
                      <div className="w-3 h-0.5 bg-amber-500" />
                      <span className="text-slate-600">Overflow (sides)</span>
                    </div>
                    <div className="flex items-center gap-2">
                      <div className="w-3 h-0.5 bg-blue-500" />
                      <span className="text-slate-600">Spending (down)</span>
                    </div>
                  </div>
                  <div className="border-t border-slate-200 pt-1.5 mt-1.5">
                    <span className="text-[10px] text-slate-400">Edge width = relative flow &bull; Select + Delete to remove</span>
                  </div>
                </div>
              </div>
            )}
          </div>
        </Panel>
      </ReactFlow>
      </ConnectionContext.Provider>
      {mode === 'space' && (
        <IntegrationPanel
          isOpen={showIntegrations}
          onClose={() => setShowIntegrations(false)}
          onImportNodes={handleImportNodes}
          integrations={integrations}
          onIntegrationsChange={onIntegrationsChange || (() => {})}
        />
      )}
    </div>
  )
}

// Props for the exported component
interface FlowCanvasProps {
  initialNodes: FlowNode[]
  mode?: 'demo' | 'space'
  onNodesChange?: (nodes: FlowNode[]) => void
  integrations?: IntegrationConfig
  onIntegrationsChange?: (config: IntegrationConfig) => void
}

export default function FlowCanvas({ initialNodes, mode = 'demo', onNodesChange, integrations, onIntegrationsChange }: FlowCanvasProps) {
  return (
    <ReactFlowProvider>
      <FlowCanvasInner
        initialNodes={initialNodes}
        mode={mode}
        onNodesChange={onNodesChange}
        integrations={integrations}
        onIntegrationsChange={onIntegrationsChange}
      />
    </ReactFlowProvider>
  )
}
