// KnowledgeGraphBG, animated "living memory graph": drifting nodes, proximity
// edges, and data pulses travelling between nodes. On-brand background for a
// temporal-graph-memory product. Pure canvas, no dependencies.
const { useEffect: useEffectKG, useRef: useRefKG } = React;

function KnowledgeGraphBG() {
  const ref = useRefKG(null);

  useEffectKG(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    // Candy-tech accent set: blue (code), teal (docs), violet (research), pink (candy)
    const COLORS = ['#6E9BFF', '#3FE0AE', '#B49BFF', '#FF6FB0'];
    const MAXD = 168;

    let w = 0, h = 0;
    // Perf: on ultrawide screens a retina-resolution canvas redraws
    // 15-20M pixels per frame. Render at 1x beyond 1800px wide.
    const dpr = (window.innerWidth > 1800) ? 1 : Math.min(window.devicePixelRatio || 1, 1.5);
    let nodes = [];
    let pulses = [];
    let raf = 0;
    const mouse = { x: -9999, y: -9999, active: false };

    const hexA = (hex, a) => {
      const n = parseInt(hex.slice(1), 16);
      return `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${a})`;
    };

    const build = () => {
      w = canvas.clientWidth;
      h = canvas.clientHeight;
      canvas.width = Math.max(1, Math.floor(w * dpr));
      canvas.height = Math.max(1, Math.floor(h * dpr));
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const count = Math.max(26, Math.min(60, Math.floor((w * h) / 26000)));
      nodes = Array.from({ length: count }, () => ({
        x: Math.random() * w,
        y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.12,
        vy: (Math.random() - 0.5) * 0.12,
        r: Math.random() * 1.8 + 1.4,
        c: COLORS[(Math.random() * COLORS.length) | 0],
        tw: Math.random() * Math.PI * 2
      }));
      pulses = [];
    };

    const spawnPulse = () => {
      if (nodes.length < 2) return;
      const a = (Math.random() * nodes.length) | 0;
      let b = -1, best = 1e9;
      for (let i = 0; i < nodes.length; i++) {
        if (i === a) continue;
        const dx = nodes[i].x - nodes[a].x;
        const dy = nodes[i].y - nodes[a].y;
        const d = dx * dx + dy * dy;
        if (d < best && d > 400) { best = d; b = i; }
      }
      if (b >= 0 && best < MAXD * MAXD) {
        pulses.push({ a, b, t: 0, sp: 0.004 + Math.random() * 0.006, c: nodes[a].c });
      }
    };

    let skip = false;
    const frame = (time) => {
      // 30fps is indistinguishable for a drifting background and halves the cost
      skip = !skip;
      if (skip) { raf = requestAnimationFrame(frame); return; }
      ctx.clearRect(0, 0, w, h);
      ctx.globalCompositeOperation = 'lighter';

      // Move + draw edges
      for (let i = 0; i < nodes.length; i++) {
        const n = nodes[i];
        if (!reduce) {
          n.x += n.vx; n.y += n.vy;
          if (n.x < 0 || n.x > w) n.vx *= -1;
          if (n.y < 0 || n.y > h) n.vy *= -1;
          if (mouse.active) {
            const dx = n.x - mouse.x, dy = n.y - mouse.y;
            const d = Math.hypot(dx, dy);
            if (d < 150 && d > 0.1) {
              const f = (150 - d) / 150 * 0.4;
              n.x += (dx / d) * f; n.y += (dy / d) * f;
            }
          }
        }
        for (let j = i + 1; j < nodes.length; j++) {
          const m = nodes[j];
          const dx = n.x - m.x, dy = n.y - m.y;
          const d = Math.hypot(dx, dy);
          if (d < MAXD) {
            const a = (1 - d / MAXD) * 0.2;
            const g = ctx.createLinearGradient(n.x, n.y, m.x, m.y);
            g.addColorStop(0, hexA(n.c, a));
            g.addColorStop(1, hexA(m.c, a));
            ctx.strokeStyle = g;
            ctx.lineWidth = 1;
            ctx.beginPath();
            ctx.moveTo(n.x, n.y);
            ctx.lineTo(m.x, m.y);
            ctx.stroke();
          }
        }
      }

      // Nodes with glow
      for (const n of nodes) {
        const tw = reduce ? 0.85 : (0.6 + 0.4 * Math.sin(time * 0.002 + n.tw));
        ctx.fillStyle = hexA(n.c, 0.9 * tw);
        ctx.shadowColor = n.c;
        ctx.shadowBlur = 12;
        ctx.beginPath();
        ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.shadowBlur = 0;

      // Travelling data pulses
      if (!reduce) {
        for (let i = pulses.length - 1; i >= 0; i--) {
          const p = pulses[i];
          p.t += p.sp;
          if (p.t >= 1) { pulses.splice(i, 1); continue; }
          const A = nodes[p.a], B = nodes[p.b];
          if (!A || !B) { pulses.splice(i, 1); continue; }
          const x = A.x + (B.x - A.x) * p.t;
          const y = A.y + (B.y - A.y) * p.t;
          ctx.fillStyle = hexA(p.c, 0.95);
          ctx.shadowColor = p.c;
          ctx.shadowBlur = 10;
          ctx.beginPath();
          ctx.arc(x, y, 1.8, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.shadowBlur = 0;
        if (pulses.length < Math.min(11, nodes.length / 6) && Math.random() < 0.09) spawnPulse();
      }

      ctx.globalCompositeOperation = 'source-over';
      if (!reduce) raf = requestAnimationFrame(frame);
    };

    build();
    frame(performance.now());
    if (!reduce) raf = requestAnimationFrame(frame);

    const onResize = () => build();
    const onMove = (e) => { mouse.x = e.clientX; mouse.y = e.clientY; mouse.active = true; };
    const onLeave = () => { mouse.active = false; };
    const onVis = () => {
      if (document.hidden) { cancelAnimationFrame(raf); }
      else if (!reduce) { cancelAnimationFrame(raf); raf = requestAnimationFrame(frame); }
    };

    window.addEventListener('resize', onResize);
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerout', onLeave);
    document.addEventListener('visibilitychange', onVis);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', onResize);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerout', onLeave);
      document.removeEventListener('visibilitychange', onVis);
    };
  }, []);

  return <canvas ref={ref} className="bg-graph"></canvas>;
}

Object.assign(window, { KnowledgeGraphBG });
