/* global React */
const { useEffect: lhUseEffect, useRef: lhUseRef, useState: lhUseState } = React;

/* ============================================================
   LatticeHero — animated node-network canvas with subtle pulses
   Uses canvas for perf. Nodes drift on a soft sin field.
   Connections appear when nodes are < threshold distance.
   Periodic "pulse" travels along an edge to convey activity.
   ============================================================ */
function LatticeHero() {
  const canvasRef = lhUseRef(null);
  const animRef = lhUseRef(null);
  const stateRef = lhUseRef(null);

  lhUseEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    let W = 0, H = 0;
    const resize = () => {
      const rect = canvas.getBoundingClientRect();
      W = rect.width; H = rect.height;
      canvas.width = W * dpr;
      canvas.height = H * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      seed();
    };

    let nodes = [];
    let pulses = [];

    const seed = () => {
      nodes = [];
      // Hex-ish grid, jittered, then drifts
      const cols = Math.max(8, Math.floor(W / 90));
      const rows = Math.max(6, Math.floor(H / 90));
      const xStep = W / (cols - 1);
      const yStep = H / (rows - 1);
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const offset = r % 2 === 0 ? 0 : xStep / 2;
          nodes.push({
            ox: c * xStep + offset,
            oy: r * yStep,
            x: c * xStep + offset,
            y: r * yStep,
            phase: Math.random() * Math.PI * 2,
            speed: 0.3 + Math.random() * 0.6,
            amp: 6 + Math.random() * 8,
            // some nodes are "anchor" nodes — slightly bigger / hub
            hub: Math.random() < 0.08,
            highlight: 0,
          });
        }
      }
      // Pre-compute neighbor pairs by distance
      stateRef.current = { nodes };
    };

    const spawnPulse = () => {
      // Pick a random pair of close nodes and animate a dot along the line
      const a = nodes[Math.floor(Math.random() * nodes.length)];
      // Find nearby
      let best = null, bestD = Infinity;
      for (const b of nodes) {
        if (b === a) continue;
        const d = Math.hypot(a.x - b.x, a.y - b.y);
        if (d < bestD && d > 30) { bestD = d; best = b; }
      }
      if (best) {
        pulses.push({ a, b: best, t: 0, life: 1.6 + Math.random() * 0.8 });
        a.highlight = 1;
      }
    };

    let last = performance.now();
    let pulseTimer = 0;

    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      pulseTimer += dt;
      if (pulseTimer > 0.55) { pulseTimer = 0; spawnPulse(); }

      ctx.clearRect(0, 0, W, H);

      // Drift nodes
      const t = now / 1000;
      for (const n of nodes) {
        n.x = n.ox + Math.cos(n.phase + t * n.speed) * n.amp;
        n.y = n.oy + Math.sin(n.phase * 1.3 + t * n.speed * 0.9) * n.amp;
        n.highlight = Math.max(0, n.highlight - dt * 0.8);
      }

      // Mouse pull
      const mx = stateRef.current.mx, my = stateRef.current.my;
      if (mx != null) {
        for (const n of nodes) {
          const dx = mx - n.x, dy = my - n.y;
          const d = Math.hypot(dx, dy);
          if (d < 140) {
            const f = (1 - d / 140) * 12;
            n.x += (dx / d) * f;
            n.y += (dy / d) * f;
          }
        }
      }

      // Edges
      const maxD = 110;
      ctx.lineWidth = 1;
      for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
          const a = nodes[i], b = nodes[j];
          const d = Math.hypot(a.x - b.x, a.y - b.y);
          if (d < maxD) {
            const alpha = (1 - d / maxD) * 0.28;
            ctx.strokeStyle = `rgba(74, 88, 120, ${alpha})`;
            ctx.beginPath();
            ctx.moveTo(a.x, a.y);
            ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }

      // Pulses (data flowing)
      pulses = pulses.filter(p => p.t < p.life);
      for (const p of pulses) {
        p.t += dt;
        const k = Math.min(1, p.t / p.life);
        const eased = k < 0.5 ? 2 * k * k : 1 - Math.pow(-2 * k + 2, 2) / 2;
        const px = p.a.x + (p.b.x - p.a.x) * eased;
        const py = p.a.y + (p.b.y - p.a.y) * eased;
        // Trail line
        const grad = ctx.createLinearGradient(p.a.x, p.a.y, p.b.x, p.b.y);
        grad.addColorStop(Math.max(0, eased - 0.25), 'rgba(59, 130, 246, 0)');
        grad.addColorStop(eased, 'rgba(59, 130, 246, 0.65)');
        grad.addColorStop(Math.min(1, eased + 0.05), 'rgba(59, 130, 246, 0)');
        ctx.strokeStyle = grad;
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(p.a.x, p.a.y);
        ctx.lineTo(p.b.x, p.b.y);
        ctx.stroke();
        // Glow dot
        ctx.beginPath();
        ctx.fillStyle = 'rgba(59, 130, 246, 0.95)';
        ctx.arc(px, py, 2.2, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.fillStyle = 'rgba(59, 130, 246, 0.18)';
        ctx.arc(px, py, 7, 0, Math.PI * 2);
        ctx.fill();
        if (k > 0.95) p.b.highlight = 1;
      }

      // Nodes
      for (const n of nodes) {
        const r = n.hub ? 2.6 : 1.6;
        if (n.highlight > 0) {
          ctx.beginPath();
          ctx.fillStyle = `rgba(59, 130, 246, ${0.15 * n.highlight})`;
          ctx.arc(n.x, n.y, 10 + 6 * n.highlight, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.beginPath();
        ctx.fillStyle = n.highlight > 0
          ? `rgba(30, 95, 232, ${0.6 + 0.4 * n.highlight})`
          : (n.hub ? 'rgba(24, 34, 56, 0.85)' : 'rgba(74, 88, 120, 0.55)');
        ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
        ctx.fill();
      }

      animRef.current = requestAnimationFrame(tick);
    };

    stateRef.current = { nodes, mx: null, my: null };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      stateRef.current.mx = e.clientX - rect.left;
      stateRef.current.my = e.clientY - rect.top;
    };
    const onLeave = () => { stateRef.current.mx = null; stateRef.current.my = null; };
    canvas.addEventListener('mousemove', onMove);
    canvas.addEventListener('mouseleave', onLeave);

    animRef.current = requestAnimationFrame(tick);
    return () => {
      cancelAnimationFrame(animRef.current);
      ro.disconnect();
      canvas.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('mouseleave', onLeave);
    };
  }, []);

  return <canvas ref={canvasRef} className="lattice-canvas" aria-hidden="true" />;
}

window.LatticeHero = LatticeHero;
