'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 type { FlowNode, FlowEdge, FunnelNodeData, OutcomeNodeData } from '@/lib/types'
import { SPENDING_COLORS, OVERFLOW_COLORS } from '@/lib/presets'

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

// Generate edges with proportional Sankey-style widths
function generateEdges(nodes: FlowNode[]): FlowEdge[] {
  const edges: FlowEdge[] = []

  const flowValues: number[] = []
  nodes.forEach((node) => {
    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

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

    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,
        },
        label: `${alloc.percentage}%`,
        labelStyle: {
          fontSize: 11,
          fontWeight: 600,
          fill: alloc.color,
        },
        labelBgStyle: {
          fill: 'white',
          fillOpacity: 0.9,
        },
        labelBgPadding: [4, 2] as [number, number],
        labelBgBorderRadius: 4,
        data: {
          allocation: alloc.percentage,
          color: alloc.color,
          edgeType: 'overflow' as const,
        },
        type: 'smoothstep',
      })
    })

    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,
        },
        label: `${alloc.percentage}%`,
        labelStyle: {
          fontSize: 11,
          fontWeight: 600,
          fill: alloc.color,
        },
        labelBgStyle: {
          fill: 'white',
          fillOpacity: 0.9,
        },
        labelBgPadding: [4, 2] as [number, number],
        labelBgBorderRadius: 4,
        data: {
          allocation: alloc.percentage,
          color: alloc.color,
          edgeType: 'spending' as const,
        },
        type: 'smoothstep',
      })
    })
  })

  return edges
}

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

function FlowCanvasInner({ initialNodes: initNodes, mode, onNodesChange }: FlowCanvasInnerProps) {
  const [nodes, setNodes, onNodesChangeHandler] = useNodesState(initNodes)
  const [edges, setEdges, onEdgesChange] = useEdgesState(generateEdges(initNodes))
  const [isSimulating, setIsSimulating] = useState(mode === 'demo')
  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])

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

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

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

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

      if (!isOverflow && !isSpending) 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 {
          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],
                },
              ],
            },
          }
        }
      }))
    },
    [setNodes]
  )

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

      const oldTargetId = oldEdge.target
      const newTargetId = newConnection.target
      if (oldTargetId === newTargetId) 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 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) {
            setNodes((nds) => nds.map((node) => {
              if (node.id !== edge.source || node.type !== 'funnel') return node
              const data = node.data as FunnelNodeData

              if (edge.data!.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]
  )

  // 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])

  // Simulation
  useEffect(() => {
    if (!isSimulating) return

    const interval = setInterval(() => {
      setNodes((nds) =>
        nds.map((node) => {
          if (node.type === 'funnel') {
            const data = node.data as FunnelNodeData
            const change = (Math.random() - 0.45) * 300
            return {
              ...node,
              data: {
                ...data,
                currentValue: Math.max(0, Math.min(data.maxCapacity * 1.1, data.currentValue + change)),
              },
            }
          } else if (node.type === 'outcome') {
            const data = node.data as OutcomeNodeData
            const change = Math.random() * 80
            const newReceived = Math.min(data.fundingTarget * 1.05, data.fundingReceived + change)
            return {
              ...node,
              data: {
                ...data,
                fundingReceived: newReceived,
                status: newReceived >= data.fundingTarget ? 'completed' :
                        data.status === 'not-started' && newReceived > 0 ? 'in-progress' : data.status,
              },
            }
          }
          return node
        })
      )
    }, 500)

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

  return (
    <div className="w-full h-full">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChangeHandler}
        onEdgesChange={handleEdgesChange}
        onConnect={onConnect}
        onReconnect={onReconnect}
        nodeTypes={nodeTypes}
        edgesReconnectable={true}
        fitView
        fitViewOptions={{ padding: 0.15 }}
        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="bg-white rounded-lg shadow-lg border border-slate-200 p-4 m-4">
          <h1 className="text-lg font-bold text-slate-800">Threshold-Based Flow Funding</h1>
          <p className="text-xs text-slate-500 mt-1">
            <span className="text-emerald-600">Inflows</span> (top) &bull;
            <span className="text-amber-600 ml-1">Overflow</span> (sides) &bull;
            <span className="text-blue-600 ml-1">Spending</span> (bottom)
          </p>
          <p className="text-[10px] text-slate-400 mt-1">
            Drag handles to connect &bull; Double-click funnels to edit &bull; Select + Delete to remove edges
          </p>
        </Panel>

        {/* Top-right Controls */}
        <Panel position="top-right" className="m-4 flex gap-2">
          {mode === 'space' && (
            <>
              <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="bg-white rounded-lg shadow-lg border border-slate-200 p-3 m-4">
          <div className="text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-2">Flow Types</div>
          <div className="space-y-1.5 text-xs">
            <div className="flex items-center gap-2">
              <div className="w-3 h-3 rounded-full bg-emerald-500" />
              <span className="text-slate-600">Inflows (top)</span>
            </div>
            <div className="flex items-center gap-2">
              <div className="w-3 h-3 rounded-full bg-amber-500" />
              <span className="text-slate-600">Overflow (sides)</span>
            </div>
            <div className="flex items-center gap-2">
              <div className="w-3 h-3 rounded-full bg-blue-500" />
              <span className="text-slate-600">Spending (bottom)</span>
            </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 amount</span>
            </div>
          </div>
        </Panel>
      </ReactFlow>
    </div>
  )
}

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

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