'use client'

import { useEffect, useRef } from 'react'
import * as d3 from 'd3'

interface Note {
  id: string
  title: string | null
  clusterId?: string | number
  isCentral?: boolean
}

interface NoteCluster {
  id: string | number
  name?: string
  noteIds: string[]
  color?: string
}

interface BridgeNote {
  noteId: string
  bridgeScore: number
  clustersConnected?: (string | number)[]
  clusterNames?: string[]
}

interface NetworkGraphProps {
  notes: Note[]
  clusters: NoteCluster[]
  bridgeNotes: BridgeNote[]
  onNoteSelect: (id: string) => void
  selectedClusterId?: string | null
  onClusterSelect?: (id: string | null) => void
  untitledLabel?: string
  resetFocusLabel?: string
  fitViewLabel?: string
}

export function NetworkGraph({
  notes,
  clusters,
  bridgeNotes,
  onNoteSelect,
  selectedClusterId = null,
  onClusterSelect,
  untitledLabel = 'Untitled',
  resetFocusLabel = 'Reset focus',
  fitViewLabel = 'Fit view',
}: NetworkGraphProps) {
  const svgRef = useRef<SVGSVGElement>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!svgRef.current || !containerRef.current) return

    const width = containerRef.current.clientWidth
    const height = containerRef.current.clientHeight

    const svg = d3.select(svgRef.current)
    svg.selectAll('*').remove()

    const g = svg.append('g')

    const zoom = d3.zoom<SVGSVGElement, unknown>()
      .scaleExtent([0.1, 4])
      .on('zoom', (event) => {
        g.attr('transform', event.transform)
      })

    svg.call(zoom as any)

    // Filter notes with cluster assignments
    const visibleNotes = notes.filter(n => n.clusterId !== undefined && n.clusterId !== null && String(n.clusterId) !== '-1')

    if (visibleNotes.length === 0) return

    interface D3Node extends d3.SimulationNodeDatum {
      id: string
      title: string | null
      clusterId: string | number
      color: string
      isBridge: boolean
      isCentral: boolean
      radius: number
    }

    interface D3Link extends d3.SimulationLinkDatum<D3Node> {
      source: string
      target: string
      strength: number
      type?: 'inner' | 'bridge'
    }

    const bridgeSet = new Set(bridgeNotes.map(b => b.noteId))

    // 1. Initialisation des nœuds avec rôles et diamètres distincts
    const nodes: D3Node[] = visibleNotes.map(n => {
      const cluster = clusters.find(c => String(c.id) === String(n.clusterId))
      const isBridge = bridgeSet.has(n.id)
      const isCentral = !!n.isCentral
      
      // Hiérarchie de tailles premium
      let radius = 6
      if (isCentral) radius = 13
      else if (isBridge) radius = 10

      return {
        id: n.id,
        title: n.title,
        clusterId: n.clusterId!,
        color: cluster?.color || '#cbd5e1',
        isBridge,
        isCentral,
        radius
      }
    })

    // Groupement des nœuds par cluster
    const clusterGroups = new Map<string | number, D3Node[]>()
    nodes.forEach(node => {
      const cid = node.clusterId
      if (!clusterGroups.has(cid)) {
        clusterGroups.set(cid, [])
      }
      clusterGroups.get(cid)!.push(node)
    })

    const links: D3Link[] = []

    // 2. Création de la structure en étoile (Star-Network) par cluster
    clusterGroups.forEach((groupNodes, cid) => {
      if (groupNodes.length <= 1) return

      // Trouver le nœud central existant, ou en désigner un par défaut (le premier)
      let hub = groupNodes.find(n => n.isCentral)
      if (!hub) {
        hub = groupNodes[0]
        hub.isCentral = true
        hub.radius = 13 // Augmenter sa taille pour la hiérarchie visuelle
      }

      // Relier chaque feuille du cluster UNIQUEMENT à son nœud central (Hub)
      groupNodes.forEach(node => {
        if (node.id !== hub!.id) {
          links.push({
            source: node.id,
            target: hub!.id,
            strength: 0.5,
            type: 'inner'
          })
        }
      })
    })

    // 3. Liaison de ponts dorées reliant les nœuds centraux (Hubs) (Garde-fou D3 contre les nœuds manquants)
    const nodeSet = new Set(nodes.map(n => n.id))

    bridgeNotes.forEach(b => {
      if (!b.clustersConnected) return
      if (!nodeSet.has(b.noteId)) return // Évite d'ajouter un lien si la note-pont n'est pas dans les nœuds affichés

      b.clustersConnected.forEach(cid => {
        const targetNodes = clusterGroups.get(cid) || []
        if (targetNodes.length > 0) {
          const targetHub = targetNodes.find(n => n.isCentral) || targetNodes[0]
          if (nodeSet.has(targetHub.id)) {
            links.push({
              source: b.noteId,
              target: targetHub.id,
              strength: 0.15,
              type: 'bridge'
            })
          }
        }
      })
    })

    // 4. Pré-positionnement géométrique des Hubs en cercle pour éviter toute superposition initiale
    const uniqueClusterIds = Array.from(clusterGroups.keys())
    const numClusters = uniqueClusterIds.length
    const radiusCircle = Math.min(width, height) * 0.28 // Rayon de répartition

    uniqueClusterIds.forEach((cid, index) => {
      const angle = (index * 2 * Math.PI) / numClusters
      const hubX = width / 2 + radiusCircle * Math.cos(angle)
      const hubY = height / 2 + radiusCircle * Math.sin(angle)

      const groupNodes = clusterGroups.get(cid) || []
      const hub = groupNodes.find(n => n.isCentral) || groupNodes[0]

      if (hub) {
        hub.x = hubX
        hub.y = hubY
      }

      // Positionner les feuilles autour de leur propre hub
      groupNodes.forEach(node => {
        if (node.id !== hub?.id) {
          const leafAngle = Math.random() * 2 * Math.PI
          const leafDist = 25 + Math.random() * 20
          node.x = hubX + leafDist * Math.cos(leafAngle)
          node.y = hubY + leafDist * Math.sin(leafAngle)
        }
      })
    })

    // D3 simulation — Paramètres de physique ultra-stables, centrés et étalés comme des galaxies
    const simulation = d3.forceSimulation<D3Node>(nodes)
      .force('link', d3.forceLink<D3Node, D3Link>(links).id(d => d.id).distance(d => d.type === 'bridge' ? 140 : 35)) // Feuilles proches du Hub (35px) pour des constellations compactes et lisibles
      .force('charge', d3.forceManyBody().strength(d => (d as D3Node).isCentral ? -500 : -80)) // Répulsion équilibrée pour éviter de projeter les Hubs contre les bords de l'écran
      .force('center', d3.forceCenter(width / 2, height / 2))
      .force('x', d3.forceX(width / 2).strength(0.12)) // Recentrage X renforcé pour l'équilibre central
      .force('y', d3.forceY(height / 2).strength(0.12)) // Recentrage Y renforcé
      .force('collision', d3.forceCollide<D3Node>().radius(d => d.radius + 14)) // Collision ajustée pour préserver la compacité


    // Liens avec couleur et opacité contextuelle
    const link = g.append('g')
      .selectAll('line')
      .data(links)
      .enter()
      .append('line')
      .attr('stroke', (d: any) => d.type === 'bridge' ? '#E2B13C' : '#cbd5e1')
      .attr('stroke-dasharray', (d: any) => d.type === 'bridge' ? '4,4' : 'none')
      .attr('stroke-opacity', (d: any) => {
        if (d.type === 'bridge') return selectedClusterId ? 0.15 : 0.6
        if (!selectedClusterId) return 0.4
        const sId = typeof d.source === 'string' ? d.source : (d.source as any).id
        const tId = typeof d.target === 'string' ? d.target : (d.target as any).id
        const sourceNode = nodes.find(n => n.id === sId)
        const targetNode = nodes.find(n => n.id === tId)
        const sCluster = String(sourceNode?.clusterId)
        const tCluster = String(targetNode?.clusterId)
        return sCluster === selectedClusterId && tCluster === selectedClusterId ? 0.7 : 0.04
      })
      .attr('stroke-width', (d: any) => d.type === 'bridge' ? 1.5 : 1)

    // Nœuds avec opacité focus
    const node = g.append('g')
      .selectAll('.node')
      .data(nodes)
      .enter()
      .append('g')
      .attr('class', 'node cursor-pointer')
      .attr('opacity', d => {
        if (!selectedClusterId) return 1
        return String(d.clusterId) === selectedClusterId ? 1 : 0.15
      })
      .on('click', (event, d) => onNoteSelect(d.id))
      .on('dblclick', (event, d) => {
        d.fx = null
        d.fy = null
        simulation.alphaTarget(0.1).restart()
      })
      .call(d3.drag<SVGGElement, D3Node>()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended) as any)

    // Cercles avec tailles hiérarchiques et halos
    node.append('circle')
      .attr('r', d => d.radius)
      .attr('fill', d => d.color)
      .attr('stroke', d => d.isCentral ? 'rgba(255,255,255,0.9)' : d.isBridge ? '#D4AF37' : '#fff')
      .attr('stroke-width', d => d.isCentral ? 3 : d.isBridge ? 2.5 : 1.5)
      .style('filter', d => d.isBridge ? 'drop-shadow(0 0 6px rgba(212, 175, 55, 0.5))' : 'none')

    // Labels de textes ultra-lisibles claire/sombre sans chevauchement
    node.append('text')
      .attr('dy', d => d.radius + 13)
      .attr('text-anchor', 'middle')
      .attr('font-size', d => d.isCentral ? '10px' : '9px')
      .attr('font-weight', d => d.isCentral ? '700' : '500')
      .attr('fill', '#4b5563')
      .attr('class', 'dark:fill-zinc-300 font-sans pointer-events-none')
      .text(d => {
        const title = d.title || untitledLabel
        return title.length > 20 ? title.substring(0, 18) + '...' : title
      })

    simulation.on('tick', () => {
      link
        .attr('x1', d => (d.source as any).x)
        .attr('y1', d => (d.source as any).y)
        .attr('x2', d => (d.target as any).x)
        .attr('y2', d => (d.target as any).y)

      node
        .attr('transform', d => {
          // Bounding box rigide : maintient à 100% les clusters sur l'écran
          const padding = 35
          d.x = Math.max(padding, Math.min(width - padding, d.x || width / 2))
          d.y = Math.max(padding, Math.min(height - padding, d.y || height / 2))
          return `translate(${d.x},${d.y})`
        })
    })

    // Zoom automatique sur le cluster sélectionné (800ms)
    if (selectedClusterId && width && height) {
      const clusterNodes = nodes.filter(n => String(n.clusterId) === selectedClusterId)
      if (clusterNodes.length > 0) {
        // Avancer la simulation pour obtenir des coordonnées stabilisées
        for (let i = 0; i < 60; ++i) simulation.tick()

        const xCoords = clusterNodes.map(cn => cn.x).filter((x): x is number => x !== undefined)
        const yCoords = clusterNodes.map(cn => cn.y).filter((y): y is number => y !== undefined)

        if (xCoords.length > 0 && yCoords.length > 0) {
          const avgX = d3.mean(xCoords) || width / 2
          const avgY = d3.mean(yCoords) || height / 2

          svg.transition()
            .duration(800)
            .call(
              zoom.transform,
              d3.zoomIdentity
                .translate(width / 2, height / 2)
                .scale(1.3)
                .translate(-avgX, -avgY)
            )
        }
      }
    } else if (!selectedClusterId) {
      svg.transition()
        .duration(800)
        .call(zoom.transform, d3.zoomIdentity)
    }

    function dragstarted(event: any, d: D3Node) {
      if (!event.active) simulation.alphaTarget(0.3).restart()
      d.fx = d.x
      d.fy = d.y
    }

    function dragged(event: any, d: D3Node) {
      d.fx = event.x
      d.fy = event.y
    }

    function dragended(event: any, d: D3Node) {
      if (!event.active) simulation.alphaTarget(0)
      // Garder le nœud épinglé à sa position après drag (fx/fy non remis à null)
      // Double-clic pour désépingler → géré via dblclick ci-dessous
    }

    return () => {
      simulation.stop()
    }
  }, [notes, clusters, bridgeNotes, onNoteSelect, selectedClusterId])

  return (
    <div ref={containerRef} className="w-full h-full bg-paper dark:bg-[#121212] rounded-3xl overflow-hidden border border-border/40 relative">
      {/* Pastilles de cluster — cliquables pour activer le focus */}
      <div className="absolute top-6 left-6 z-10 flex flex-wrap gap-2 max-w-[90%]">
        {clusters.map(c => {
          const isSelected = String(c.id) === selectedClusterId
          return (
            <button
              key={c.id}
              onClick={() => onClusterSelect?.(isSelected ? null : String(c.id))}
              className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full border shadow-sm transition-all text-[9px] font-bold uppercase tracking-wider ${
                isSelected
                  ? 'bg-ink text-white dark:bg-white dark:text-black border-ink dark:border-white scale-105 shadow-md'
                  : 'bg-white/90 dark:bg-black/80 text-concrete hover:text-ink hover:border-concrete/40 border-border'
              }`}
            >
              <div className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.color }} />
              <span>{c.name ?? String(c.id)}</span>
            </button>
          )
        })}
        {selectedClusterId && (
          <button
            onClick={() => onClusterSelect?.(null)}
            className="px-3 py-1.5 rounded-full border border-rose-200 bg-rose-50 dark:bg-rose-950/20 dark:border-rose-900/40 text-rose-500 text-[9px] font-bold uppercase tracking-wider hover:bg-rose-100 dark:hover:bg-rose-950/30 transition-all shadow-sm"
          >
            {resetFocusLabel}
          </button>
        )}
      </div>
      <svg ref={svgRef} className="w-full h-full" />
    </div>
  )
}
