<template>
  <div ref="container" class="codev-graph-wrap">

    <!-- Placeholder si aucune fiche -->
    <div v-if="fiches.length === 0" class="empty-state">
      <p class="empty-msg">Encore personne. Sois la premiere fiche !</p>
      <NuxtLink to="/codev/fiche" class="empty-link">Creer ma fiche &rarr;</NuxtLink>
    </div>

    <!-- SVG D3 -->
    <svg v-else ref="svgEl" class="codev-svg">
      <defs>
        <marker
          id="arrow-solution"
          viewBox="0 0 10 10"
          refX="18"
          refY="5"
          markerWidth="6"
          markerHeight="6"
          orient="auto-start-reverse"
        >
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#22c55e" />
        </marker>
      </defs>
    </svg>

  </div>
</template>

<script setup lang="ts">
import * as d3 from 'd3'
import type { CodevFiche, CodevMatch } from '~/types/codev'

// ── Props / Emits ──────────────────────────────────────────────────────────

const props = withDefaults(defineProps<{
  fiches: CodevFiche[]
  matches?: CodevMatch[]
  mode?: 'none' | 'solution' | 'alliance' | 'surprise'
  showLabels?: boolean
}>(), {
  matches: () => [],
  mode: 'none',
  showLabels: false,
})

const emit = defineEmits<{
  'select-fiche': [id: number]
}>()

// ── Refs ───────────────────────────────────────────────────────────────────

const container = ref<HTMLDivElement | null>(null)
const svgEl = ref<SVGSVGElement | null>(null)
const width = ref(800)
const height = ref(600)

// ── State interne ──────────────────────────────────────────────────────────

type SimNode = d3.SimulationNodeDatum & { id: number; nom: string; offre: string; besoin: string }
type SimLink = d3.SimulationLinkDatum<SimNode> & { score: number; mode: string }

let simulation: d3.Simulation<SimNode, SimLink> | null = null
let svgRoot: d3.Selection<SVGSVGElement, unknown, null, undefined> | null = null
let gLinks: d3.Selection<SVGGElement, unknown, null, undefined> | null = null
let gNodes: d3.Selection<SVGGElement, unknown, null, undefined> | null = null

const isMobile = computed(() => width.value < 600)
const nodeRadius = computed(() => isMobile.value ? 22 : 28)

// ── Helpers ────────────────────────────────────────────────────────────────

function truncate(str: string, max = 10): string {
  if (!str) return ''
  return str.length > max ? str.slice(0, max - 1) + '…' : str
}

function buildNodes(): SimNode[] {
  return props.fiches.map(f => ({
    id: f.id,
    nom: f.nom,
    offre: f.offre,
    besoin: f.besoin,
  }))
}

function buildLinks(nodes: SimNode[]): SimLink[] {
  if (!props.matches || props.matches.length === 0) return []
  const nodeById = new Map(nodes.map(n => [n.id, n]))
  return props.matches
    .filter(m => nodeById.has(m.fromId) && nodeById.has(m.toId))
    .map(m => ({
      source: nodeById.get(m.fromId)!,
      target: nodeById.get(m.toId)!,
      score: m.score,
      mode: m.mode,
    }))
}

function linkColor(mode: string): string {
  if (mode === 'solution') return '#22c55e'
  if (mode === 'alliance') return '#f97316'
  if (mode === 'surprise') return '#3b82f6'
  return '#ccc'
}

// ── Drag handler ───────────────────────────────────────────────────────────

function makeDrag(sim: d3.Simulation<SimNode, SimLink>): d3.DragBehavior<SVGGElement, SimNode, SimNode> {
  return d3.drag<SVGGElement, SimNode>()
    .on('start', (event, d) => {
      if (!event.active) sim.alphaTarget(0.3).restart()
      d.fx = d.x
      d.fy = d.y
    })
    .on('drag', (event, d) => {
      d.fx = event.x
      d.fy = event.y
    })
    .on('end', (event, d) => {
      if (!event.active) sim.alphaTarget(0)
      d.fx = null
      d.fy = null
    })
}

// ── Initialisation SVG ─────────────────────────────────────────────────────

function initSvg() {
  if (!svgEl.value) return

  svgRoot = d3.select(svgEl.value)
    .attr('width', width.value)
    .attr('height', height.value)

  svgRoot.selectAll('*').remove()

  gLinks = svgRoot.append('g').attr('class', 'links')
  gNodes = svgRoot.append('g').attr('class', 'nodes')
}

// ── Rebuild liens (hook pour M4) ───────────────────────────────────────────

let currentNodes: SimNode[] = []
let currentLinks: SimLink[] = []

function rebuildLinks() {
  currentLinks = buildLinks(currentNodes)
  if (!gLinks || !simulation) return

  // .join() moderne D3 pour garantir le re-rendu complet
  gLinks
    .selectAll<SVGLineElement, SimLink>('line')
    .data(currentLinks)
    .join(
      enter => enter.append('line'),
      update => update,
      exit => exit.remove()
    )
    .attr('stroke', d => linkColor(d.mode))
    .attr('stroke-width', d => 1 + d.score * 3)
    .attr('stroke-opacity', 0.7)
    .attr('marker-end', d => d.mode === 'solution' ? 'url(#arrow-solution)' : null)
}

// ── Rendu complet ──────────────────────────────────────────────────────────

function render() {
  if (!svgEl.value || props.fiches.length === 0) return

  initSvg()

  currentNodes = buildNodes()
  currentLinks = buildLinks(currentNodes)

  const r = nodeRadius.value
  const fontSize = isMobile.value ? 10 : 12

  // Liens
  gLinks!
    .selectAll<SVGLineElement, SimLink>('line')
    .data(currentLinks)
    .join('line')
    .attr('stroke', d => linkColor(d.mode))
    .attr('stroke-width', d => 1 + d.score * 3)
    .attr('stroke-opacity', 0.7)
    .attr('marker-end', d => d.mode === 'solution' ? 'url(#arrow-solution)' : null)

  // Noeuds = groupe <g> par personne
  const nodeGroups = gNodes!
    .selectAll<SVGGElement, SimNode>('g.node')
    .data(currentNodes, d => String(d.id))
    .join('g')
    .attr('class', 'node')
    .style('cursor', 'pointer')
    .call(makeDrag(simulation!) as any)
    .on('click', (_event, d) => emit('select-fiche', d.id))

  // Cercle principal
  nodeGroups.append('circle')
    .attr('r', r)
    .attr('fill', '#ffffff')
    .attr('stroke', '#1B4436')
    .attr('stroke-width', 2)

  // Label nom
  nodeGroups.append('text')
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'central')
    .attr('font-size', fontSize)
    .attr('font-weight', '700')
    .attr('fill', '#1a1a2e')
    .attr('pointer-events', 'none')
    .text(d => truncate(d.nom, 10))

  // Pastille offre (haut-droite, vert)
  nodeGroups.append('circle')
    .attr('r', 6)
    .attr('cx', r * 0.65)
    .attr('cy', -r * 0.65)
    .attr('fill', '#22c55e')
    .attr('stroke', '#fff')
    .attr('stroke-width', 1.5)

  // Pastille besoin (bas-droite, bleu)
  nodeGroups.append('circle')
    .attr('r', 6)
    .attr('cx', r * 0.65)
    .attr('cy', r * 0.65)
    .attr('fill', '#3b82f6')
    .attr('stroke', '#fff')
    .attr('stroke-width', 1.5)

  // Tooltip SVG natif <title>
  nodeGroups.append('title')
    .text(d => `${d.nom}\nOffre : ${d.offre}\nBesoin : ${d.besoin}`)

  // Groupe label bulle (affiche si showLabels)
  const labelGroups = nodeGroups.append('g')
    .attr('class', 'label-bubble')
    .attr('visibility', props.showLabels ? 'visible' : 'hidden')

  // Fond bulle besoin (dessous du noeud)
  labelGroups.append('rect')
    .attr('class', 'bubble-besoin-bg')
    .attr('x', -(r + 50))
    .attr('y', r + 4)
    .attr('width', 100)
    .attr('height', 28)
    .attr('rx', 6)
    .attr('fill', '#eff6ff')
    .attr('stroke', '#3b82f6')
    .attr('stroke-width', 1)

  // Texte besoin
  labelGroups.append('text')
    .attr('class', 'bubble-besoin-txt')
    .attr('x', -(r) + 50)
    .attr('y', r + 22)
    .attr('text-anchor', 'middle')
    .attr('font-size', 9)
    .attr('fill', '#1e40af')
    .attr('pointer-events', 'none')
    .text(d => truncate(d.besoin, 18))

  // Fond bulle offre (dessus du noeud)
  labelGroups.append('rect')
    .attr('class', 'bubble-offre-bg')
    .attr('x', -(r + 50))
    .attr('y', -(r + 32))
    .attr('width', 100)
    .attr('height', 28)
    .attr('rx', 6)
    .attr('fill', '#f0fdf4')
    .attr('stroke', '#22c55e')
    .attr('stroke-width', 1)

  // Texte offre
  labelGroups.append('text')
    .attr('class', 'bubble-offre-txt')
    .attr('x', -(r) + 50)
    .attr('y', -(r + 14))
    .attr('text-anchor', 'middle')
    .attr('font-size', 9)
    .attr('fill', '#166534')
    .attr('pointer-events', 'none')
    .text(d => truncate(d.offre, 18))

  // Simulation
  simulation = d3.forceSimulation<SimNode, SimLink>(currentNodes)
    .force('link', d3.forceLink<SimNode, SimLink>(currentLinks)
      .id(d => d.id)
      .distance(120)
      .strength(0.3))
    .force('charge', d3.forceManyBody<SimNode>().strength(-400))
    .force('center', d3.forceCenter(width.value / 2, height.value / 2))
    .force('collide', d3.forceCollide<SimNode>().radius(r + 12))
    .force('x', d3.forceX(width.value / 2).strength(0.05))
    .force('y', d3.forceY(height.value / 2).strength(0.05))
    .alphaDecay(0.02)
    .on('tick', tick)

  // Re-bind drag avec la nouvelle simulation
  gNodes!.selectAll<SVGGElement, SimNode>('g.node')
    .call(makeDrag(simulation) as any)
}

function tick() {
  const r = nodeRadius.value
  if (!gLinks || !gNodes) return

  gLinks.selectAll<SVGLineElement, SimLink>('line')
    .attr('x1', d => Math.max(r, Math.min(width.value - r, (d.source as SimNode).x ?? 0)))
    .attr('y1', d => Math.max(r, Math.min(height.value - r, (d.source as SimNode).y ?? 0)))
    .attr('x2', d => Math.max(r, Math.min(width.value - r, (d.target as SimNode).x ?? 0)))
    .attr('y2', d => Math.max(r, Math.min(height.value - r, (d.target as SimNode).y ?? 0)))

  gNodes.selectAll<SVGGElement, SimNode>('g.node')
    .attr('transform', d => {
      const x = Math.max(r, Math.min(width.value - r, d.x ?? 0))
      const y = Math.max(r, Math.min(height.value - r, d.y ?? 0))
      return `translate(${x},${y})`
    })
}

// ── Watch matches/mode (hook pour M4) ─────────────────────────────────────

watch(() => [props.matches, props.mode] as const, () => {
  if (!simulation) return
  rebuildLinks()
  const newForce = d3.forceLink<SimNode, SimLink>(currentLinks)
    .id(d => String(d.id))
    .distance(120)
    .strength(0.5)
  simulation.force('link', newForce)
  simulation.alpha(0.8).restart()
}, { deep: true })

// ── Watch showLabels ──────────────────────────────────────────────────────

watch(() => props.showLabels, (val) => {
  if (!svgEl.value) return
  d3.select(svgEl.value).selectAll('.label-bubble').attr('visibility', val ? 'visible' : 'hidden')
})

// ── Watch fiches (re-render si nouvelles fiches) ───────────────────────────

watch(() => props.fiches, () => {
  if (simulation) {
    simulation.stop()
    simulation = null
  }
  render()
}, { deep: true })

// ── ResizeObserver ─────────────────────────────────────────────────────────

let ro: ResizeObserver | null = null

onMounted(() => {
  if (!container.value) return
  width.value = container.value.clientWidth || 800
  height.value = container.value.clientHeight || 600

  render()

  ro = new ResizeObserver(() => {
    if (!container.value) return
    width.value = container.value.clientWidth || 800
    height.value = container.value.clientHeight || 600
    if (svgRoot) {
      svgRoot.attr('width', width.value).attr('height', height.value)
    }
    if (simulation) {
      simulation.force('center', d3.forceCenter(width.value / 2, height.value / 2))
      simulation.alpha(0.3).restart()
    }
  })
  ro.observe(container.value!)
})

onUnmounted(() => {
  if (simulation) simulation.stop()
  if (ro) ro.disconnect()
})
</script>

<style scoped>
.codev-graph-wrap {
  width: 100%;
  height: 70vh;
  min-height: 320px;
  position: relative;
  background: var(--nav-bg, #fafafa);
  border-radius: 12px;
  overflow: hidden;
}

.codev-svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* ── Etat vide ── */

.empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 2rem;
  text-align: center;
}

.empty-msg {
  font-size: 1.125rem;
  color: var(--nav-text-muted, #6b7280);
  margin: 0;
}

.empty-link {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--nav-primary-solid, #1B4436);
  text-decoration: none;
  border: 1.5px solid var(--nav-primary-solid, #1B4436);
  border-radius: 8px;
  padding: 0.5rem 1.25rem;
  transition: background 0.15s, color 0.15s;
}

.empty-link:hover {
  background: var(--nav-primary-solid, #1B4436);
  color: #fff;
}

/* ── Mobile ── */

@media (max-width: 600px) {
  .codev-graph-wrap {
    height: 65vh;
    min-height: 260px;
    border-radius: 8px;
  }
}
</style>
