import { useEffect, useMemo, useRef, useState } from "react"
import ForceGraph2D from "react-force-graph-2d"
import { forceCollide, forceX, forceY } from "d3-force"
import { RefreshCw } from "lucide-react"
import { type MemoryGraph } from "@/lib/api"

const CAT_COLOR: Record<string, string> = {
  identity: "#00f5ff", knowledge: "#3b82f6", rules: "#d946ef", events: "#fbbf24",
}
const CAT_LABEL: Record<string, string> = {
  identity: "Identität", knowledge: "Wissen", rules: "Regeln", events: "Ereignisse",
}
const EDGE_HI = "rgba(99, 102, 241, 0.9)"
const DIM_EDGE = "rgba(255, 255, 255, 0.02)"

// Hilfsfunktion zur Konvertierung von Hex in RGBA für farbkodierte Verbindungen
function hexToRgba(hex: string, alpha: number): string {
  const r = parseInt(hex.slice(1, 3), 16)
  const g = parseInt(hex.slice(3, 5), 16)
  const b = parseInt(hex.slice(5, 7), 16)
  return `rgba(${r}, ${g}, ${b}, ${alpha})`
}

export function GraphView({ data, selectedNodeId, onNodeSelect }: {
  data: MemoryGraph
  selectedNodeId: string | null
  onNodeSelect: (id: string | null) => void
}) {
  const [hovered, setHovered] = useState<string | null>(null)
  const containerRef = useRef<HTMLDivElement>(null)
  const fgRef = useRef<any>(null)

  const [dimensions, setDimensions] = useState({ width: 500, height: 560 })

  // ResizeObserver für präzise Canvas-Dimensionen im Layout
  useEffect(() => {
    if (!containerRef.current) return
    const resizeObserver = new ResizeObserver((entries) => {
      for (let entry of entries) {
        if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
          setDimensions({
            width: Math.floor(entry.contentRect.width),
            height: Math.floor(entry.contentRect.height),
          })
        }
      }
    })
    resizeObserver.observe(containerRef.current)
    return () => resizeObserver.disconnect()
  }, [])

  // Graphology-Daten in react-force-graph-2d-kompatibles Format konvertieren
  const graphData = useMemo(() => {
    return {
      nodes: data.nodes.map((n) => ({ ...n })),
      links: data.edges.map((e) => ({
        source: e.source,
        target: e.target,
        weight: e.weight || 0,
      })),
    }
  }, [data])

  // Grad (Degree) berechnen – fängt D3-Objektmutationen robust ab!
  const degree = useMemo(() => {
    const d: Record<string, number> = {}
    data.edges.forEach((e) => {
      const sId = typeof e.source === "object" ? (e.source as any).id : e.source
      const tId = typeof e.target === "object" ? (e.target as any).id : e.target
      if (sId && tId) {
        d[sId] = (d[sId] || 0) + 1
        d[tId] = (d[tId] || 0) + 1
      }
    })
    return d
  }, [data])

  // Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen)
  const getClusterX = (category: string) => {
    return {
      identity: -240,
      knowledge: 240,
      rules: -240,
      events: 240,
    }[category] || 0
  }

  const getClusterY = (category: string) => {
    return {
      identity: -240,
      knowledge: -240,
      rules: 240,
      events: 240,
    }[category] || 0
  }

  // D3 Force Engine tunen (Luftige Struktur, kein Überlappen, Clustering)
  useEffect(() => {
    const fg = fgRef.current
    if (!fg) return
    
    // 1. Standard-Zentrumskraft entfernen (erlaubt freies Clustering in Quadranten)
    fg.d3Force("center", null)
    
    // 2. Clustering-Kräfte (Knoten zu ihren jeweiligen Sektions-Foci ziehen)
    fg.d3Force("x", forceX().x((node: any) => getClusterX(node.category)).strength(0.07))
    fg.d3Force("y", forceY().y((node: any) => getClusterY(node.category)).strength(0.07))
    
    // 3. Starke Abstoßung (Charge) für ein weit gefächertes, luftiges Netz
    const charge = fg.d3Force("charge")
    if (charge) charge.strength(-380)
    
    // 4. Kollisionskraft (Sicherer Radius, verhindert Überlappung)
    fg.d3Force("collide", forceCollide().radius((node: any) => {
      const isLarge = node.id === hovered || node.id === selectedNodeId
      return isLarge ? 50 : 25
    }).iterations(2))
    
    // 5. Federkraft (Links)
    const link = fg.d3Force("link")
    if (link) link.distance(85)

    // Simulation neu starten (Reheat)
    fg.d3ReheatSimulation()
  }, [graphData, hovered, selectedNodeId])

  const activeNode = hovered || selectedNodeId

  // Layout zurücksetzen/neu anschubsen
  const relayout = () => {
    if (!fgRef.current) return
    fgRef.current.zoomToFit(400, 40)
  }
  
  // Nachbarmenge für extrem schnellen Lookup – fängt D3-Objektmutationen robust ab!
  const connectedNodes = useMemo(() => {
    if (!activeNode) return new Set<string>()
    const set = new Set<string>()
    data.edges.forEach((e) => {
      const sId = typeof e.source === "object" ? (e.source as any).id : e.source
      const tId = typeof e.target === "object" ? (e.target as any).id : e.target
      if (sId === activeNode) set.add(tId)
      if (tId === activeNode) set.add(sId)
    })
    return set
  }, [activeNode, data])

  return (
    <div className="absolute inset-0" ref={containerRef}
      style={{ background: "radial-gradient(circle at center, rgba(59, 130, 246, 0.08) 0%, #030712 100%)" }}>
      
      <ForceGraph2D
        ref={fgRef}
        width={dimensions.width}
        height={dimensions.height}
        graphData={graphData}
        backgroundColor="rgba(0,0,0,0)"
        cooldownTicks={120}
        
        // Exakte Hit-Map auf unsichtbarem Canvas zeichnen – fängt D3-Objektmutationen robust ab!
        nodePointerAreaPaint={(node: any, color: string, ctx: CanvasRenderingContext2D) => {
          const x = node.x ?? 0
          const y = node.y ?? 0
          const nId = typeof node === "object" ? node.id : node
          const size = 6 + Math.min(degree[nId] || 0, 15) * 0.8
          ctx.fillStyle = color
          ctx.beginPath()
          ctx.arc(x, y, size + 5, 0, 2 * Math.PI, false) // 5px Puffer für einfaches Treffen
          ctx.fill()
        }}
        
        // Hover- und Klick-Logik
        onNodeClick={(node: any) => onNodeSelect(node.id)}
        onBackgroundClick={() => onNodeSelect(null)}
        onNodeHover={(node: any) => {
          setHovered(node ? node.id : null)
          if (containerRef.current) {
            containerRef.current.style.cursor = node ? "pointer" : "default"
          }
        }}
        
        // Kanten-Animationen & Partikelfluss
        linkColor={(link: any) => {
          const sId = typeof link.source === "object" ? link.source.id : link.source
          const tId = typeof link.target === "object" ? link.target.id : link.target
          
          if (activeNode === sId || activeNode === tId) return EDGE_HI
          if (activeNode) return DIM_EDGE
          
          const sourceNode = graphData.nodes.find(n => n.id === sId)
          const catColor = sourceNode ? CAT_COLOR[sourceNode.category] : "#64748b"
          return hexToRgba(catColor, 0.25)
        }}
        linkWidth={(link: any) => {
          const sId = typeof link.source === "object" ? link.source.id : link.source
          const tId = typeof link.target === "object" ? link.target.id : link.target
          return (activeNode === sId || activeNode === tId) ? 2.5 : 1.4
        }}
        
        // Daten-Partikel fließen auf den aktiven Verbindungen
        linkDirectionalParticles={(link: any) => {
          const sId = typeof link.source === "object" ? link.source.id : link.source
          const tId = typeof link.target === "object" ? link.target.id : link.target
          return (activeNode === sId || activeNode === tId) ? 4 : 0
        }}
        linkDirectionalParticleWidth={2.2}
        linkDirectionalParticleSpeed={0.006}
        
        // Custom Canvas Rendering für glühende Knoten und 100% lesbare Text-Pillen
        nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
          const x = node.x ?? 0
          const y = node.y ?? 0
          const size = 6 + Math.min(degree[node.id] || 0, 15) * 0.8
          const color = CAT_COLOR[node.category] || "#64748b"
          const isHovered = node.id === hovered
          const isSelected = node.id === selectedNodeId
          
          const isDimmed = activeNode && node.id !== activeNode && !connectedNodes.has(node.id)
          const finalColor = isDimmed ? "#1e293b" : color

          // 1. Zeichne den farbigen Knotenpunkt (Kreis)
          if (!isDimmed) {
            ctx.shadowColor = color
            ctx.shadowBlur = isHovered || isSelected ? 15 : 6
          }
          
          ctx.beginPath()
          ctx.arc(x, y, size, 0, 2 * Math.PI, false)
          ctx.fillStyle = finalColor
          ctx.fill()
          ctx.shadowBlur = 0

          if (isHovered || isSelected) {
            ctx.beginPath()
            ctx.arc(x, y, size + 2.5, 0, 2 * Math.PI, false)
            ctx.strokeStyle = isSelected ? "#ffffff" : "rgba(255, 255, 255, 0.7)"
            ctx.lineWidth = 1.5
            ctx.stroke()
          }

          // 2. Zeichne das Label nur unter bestimmten Bedingungen (Zoom-abhängig)
          // Verhindert Text-Clutter und macht Knotenpunkte permanent sichtbar.
          const showLabel = isHovered || isSelected || (globalScale >= 1.25) || (activeNode && connectedNodes.has(node.id))
          
          if (showLabel) {
            const label = node.content.length > 35 ? node.content.slice(0, 34) + "…" : node.content
            const baseFontSize = isHovered || isSelected ? 10.5 : 9.5
            const fontSize = baseFontSize / Math.max(0.5, globalScale)
            
            ctx.font = `${isHovered || isSelected ? "bold" : "500"} ${fontSize}px "Space Grotesk", sans-serif`
            const textWidth = ctx.measureText(label).width
            const bpad = 4.5 / Math.max(0.5, globalScale)
            
            ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"
            ctx.beginPath()
            
            const rx = x - textWidth / 2 - bpad
            const ry = y + size + (6 / Math.max(0.5, globalScale))
            const rw = textWidth + bpad * 2
            const rh = fontSize + bpad * 1.6
            const rad = 4 / Math.max(0.5, globalScale)
            
            ctx.beginPath()
            ctx.moveTo(rx + rad, ry)
            ctx.lineTo(rx + rw - rad, ry)
            ctx.quadraticCurveTo(rx + rw, ry, rx + rw, ry + rad)
            ctx.lineTo(rx + rw, ry + rh - rad)
            ctx.quadraticCurveTo(rx + rw, ry + rh, rx + rw - rad, ry + rh)
            ctx.lineTo(rx + rad, ry + rh)
            ctx.quadraticCurveTo(rx, ry + rh, rx, ry + rh - rad)
            ctx.lineTo(rx, ry + rad)
            ctx.quadraticCurveTo(rx, ry, rx + rad, ry)
            ctx.closePath()
            ctx.fill()

            ctx.strokeStyle = isHovered || isSelected ? "rgba(255, 255, 255, 0.2)" : "rgba(255, 255, 255, 0.08)"
            ctx.lineWidth = 0.6 / Math.max(0.5, globalScale)
            ctx.stroke()

            ctx.textAlign = "center"
            ctx.textBaseline = "top"
            ctx.fillStyle = isDimmed ? "#475569" : (isHovered || isSelected ? "#ffffff" : "#cbd5e1")
            
            ctx.fillText(label, x, y + size + (7 / Math.max(0.5, globalScale)))
          }
        }}
      />

      <button onClick={relayout} title="Zentrieren"
        className="absolute right-3 top-3 z-10 flex items-center gap-1.5 rounded-lg border border-border/60 bg-background/50 px-2.5 py-1.5 text-[11px] text-muted-foreground hover:text-foreground backdrop-blur-sm transition-colors cursor-pointer">
        <RefreshCw className="h-3.5 w-3.5" /> Zentrieren
      </button>

      {/* Legende */}
      <div className="absolute left-3 bottom-3 flex flex-wrap gap-2 rounded-lg bg-background/40 px-2.5 py-1.5 backdrop-blur-sm">
        {Object.entries(CAT_LABEL).map(([k, l]) => (
          <span key={k} className="flex items-center gap-1 text-[10px] text-muted-foreground">
            <span className="h-2 w-2 rounded-full" style={{ background: CAT_COLOR[k] }} />{l}
          </span>
        ))}
      </div>
    </div>
  )
}
