/* ─────────────────────────────────────────────────────────────
   HeroViz — "Owned model" as a living intelligent system
   A large, tilted point-sphere bleeds off the right edge (only the
   left hemisphere shows). Fewer, larger dots are wired into a
   nearest-neighbour mesh; signal pulses fire and cascade along the
   edges — synapse-style — so it reads as an interconnected,
   thinking system rather than loose points. Linear-minimal.
   ───────────────────────────────────────────────────────────── */

const STAGES = ['Capture', 'Curate', 'Fine-tune', 'Eval', 'Route', 'Host', 'Improve'];

const HeroViz = () => {
  const wrapRef = React.useRef(null);
  const canvasRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    let W = 0, H = 0, DPR = 1, raf = 0, t0 = 0;
    const TILT = -0.40;
    const cosT = Math.cos(TILT), sinT = Math.sin(TILT);

    // ── geometry — big sphere pushed right, only left half on screen ─
    let cx, cy, R;
    const layout = () => {
      cx = W * 0.99;
      cy = H * 0.50;
      R  = Math.min(W * 0.46, H * 0.62);
    };

    // ── owned-model sphere (fewer, larger nodes) ────────────────
    const N = 320;
    const GA = Math.PI * (3 - Math.sqrt(5));
    const sphere = [];
    for (let i = 0; i < N; i++) {
      const y = 1 - (i / (N - 1)) * 2;
      const r = Math.sqrt(Math.max(0, 1 - y * y));
      const th = i * GA;
      sphere.push({ x: Math.cos(th) * r, y, z: Math.sin(th) * r, flash: 0 });
    }

    // ── connection mesh (nearest neighbours) + adjacency ────────
    const edges = [];
    const seen = new Set();
    const MAXD2 = 0.19 * 0.19;
    for (let i = 0; i < N; i++) {
      const a = sphere[i];
      const ds = [];
      for (let j = 0; j < N; j++) {
        if (j === i) continue;
        const b = sphere[j];
        const dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
        ds.push([dx * dx + dy * dy + dz * dz, j]);
      }
      ds.sort((p, q) => p[0] - q[0]);
      for (let k = 0; k < 3; k++) {
        const d2 = ds[k][0], j = ds[k][1];
        if (d2 > MAXD2) break;
        const key = i < j ? i * 2000 + j : j * 2000 + i;
        if (!seen.has(key)) { seen.add(key); edges.push([i, j, Math.random() * 6.2832]); }
      }
    }
    const adj = Array.from({ length: N }, () => []);
    edges.forEach((e, ei) => { adj[e[0]].push([ei, e[1]]); adj[e[1]].push([ei, e[0]]); });

    // ── signal pulses travelling along edges (synapses) ─────────
    const pulses = [];
    const BASE = 26, CAP = 70;
    const spawnFrom = (node) => {
      const a = adj[node];
      if (!a.length) return;
      const [, to] = a[(Math.random() * a.length) | 0];
      pulses.push({ a: node, b: to, t: 0, spd: 0.010 + Math.random() * 0.013 });
    };
    const activeEdge = new Set();      // edges lit by a live pulse this frame

    const lerp = (a, b, k) => a + (b - a) * k;

    const project = (s, cosR, sinR) => {
      const y1 = s.y * cosT - s.z * sinT;
      const z1 = s.y * sinT + s.z * cosT;
      const xr = s.x * cosR - z1 * sinR;
      const zr = s.x * sinR + z1 * cosR;
      const persp = 3.0 / (3.0 - zr);
      return { px: cx + xr * R * persp, py: cy - y1 * R * persp, zr };
    };

    const P = new Array(N);
    const order = Array.from({ length: N }, (_, i) => i);

    const draw = (now) => {
      const dt = Math.min(40, now - (t0 || now)); t0 = now;
      const rot = now * 0.00005;
      const cosR = Math.cos(rot), sinR = Math.sin(rot);
      ctx.clearRect(0, 0, W, H);

      const bloom = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 2.6);
      bloom.addColorStop(0, 'rgba(132,108,210,0.10)');
      bloom.addColorStop(0.35, 'rgba(108,114,205,0.035)');
      bloom.addColorStop(0.7, 'rgba(100,108,200,0.012)');
      bloom.addColorStop(1, 'rgba(10,10,11,0)');
      ctx.fillStyle = bloom;
      ctx.beginPath(); ctx.arc(cx, cy, R * 2.6, 0, 6.2832); ctx.fill();

      for (let i = 0; i < N; i++) { const p = project(sphere[i], cosR, sinR); p.s = sphere[i]; P[i] = p; }

      // advance pulses → mark active edges, flash + cascade on arrival
      activeEdge.clear();
      for (let i = pulses.length - 1; i >= 0; i--) {
        const p = pulses[i];
        p.t += p.spd * (dt / 16.67);
        // tag the edge between a & b as active (search adj — small)
        const al = adj[p.a];
        for (let q = 0; q < al.length; q++) if (al[q][1] === p.b) { activeEdge.add(al[q][0]); break; }
        if (p.t >= 1) {
          sphere[p.b].flash = 1;
          pulses.splice(i, 1);
          if (pulses.length < CAP && Math.random() < 0.62) {
            spawnFrom(p.b);
            if (Math.random() < 0.4) spawnFrom(p.b);
          }
        }
      }

      // ── connection mesh ──
      for (let e = 0; e < edges.length; e++) {
        const a = P[edges[e][0]], b = P[edges[e][1]];
        if (a.px > W + 4 && b.px > W + 4) continue;
        const depth = ((a.zr + b.zr) * 0.5 + 1) / 2;
        const shimmer = 0.72 + 0.28 * Math.sin(now * 0.0009 + edges[e][2]);
        let al = (0.055 + depth * 0.16) * shimmer;
        if (activeEdge.has(e)) al = Math.min(0.48, al + 0.18 * depth + 0.12);
        if (al < 0.02) continue;
        ctx.lineWidth = depth > 0.45 ? 1.25 : 1;
        ctx.strokeStyle = `rgba(175,185,230,${al})`;
        ctx.beginPath(); ctx.moveTo(a.px, a.py); ctx.lineTo(b.px, b.py); ctx.stroke();
      }

      // ── tilted equator ring ──
      ctx.beginPath();
      for (let a = 0; a <= 64; a++) {
        const t = (a / 64) * 6.2832;
        const e = project({ x: Math.cos(t), y: 0, z: Math.sin(t) }, cosR, sinR);
        a === 0 ? ctx.moveTo(e.px, e.py) : ctx.lineTo(e.px, e.py);
      }
      ctx.strokeStyle = 'rgba(255,255,255,0.09)'; ctx.lineWidth = 1; ctx.stroke();

      // ── travelling signal pulses (glow) ──
      ctx.globalCompositeOperation = 'lighter';
      for (let i = 0; i < pulses.length; i++) {
        const p = pulses[i];
        const a = P[p.a], b = P[p.b];
        const x = lerp(a.px, b.px, p.t), y = lerp(a.py, b.py, p.t);
        const depth = lerp((a.zr + 1) / 2, (b.zr + 1) / 2, p.t);
        if (depth < 0.18) continue;
        // short comet tail
        const tb = Math.max(0, p.t - 0.16);
        ctx.strokeStyle = `rgba(132,150,225,${0.14 * depth})`;
        ctx.lineWidth = 1.4;
        ctx.beginPath(); ctx.moveTo(lerp(a.px, b.px, tb), lerp(a.py, b.py, tb)); ctx.lineTo(x, y); ctx.stroke();
        ctx.beginPath(); ctx.arc(x, y, 1.0 + depth * 1.3, 0, 6.2832);
        ctx.fillStyle = `rgba(196,205,235,${0.28 + depth * 0.34})`; ctx.fill();
      }
      ctx.globalCompositeOperation = 'source-over';

      // ── owned-model nodes (depth-sorted, larger) ──
      order.sort((i, j) => P[i].zr - P[j].zr);
      for (let o = 0; o < N; o++) {
        const pt = P[order[o]];
        const depth = (pt.zr + 1) / 2;
        const a = 0.14 + depth * 0.5;
        const sz = 1.1 + depth * 2.1;
        pt.s.flash = Math.max(0, pt.s.flash - dt * 0.0014);
        const f = pt.s.flash;
        if (f > 0.01) {
          // firing node: accent flare + soft halo
          ctx.globalCompositeOperation = 'lighter';
          ctx.beginPath(); ctx.arc(pt.px, pt.py, (sz + 2.4) + f * 3.5, 0, 6.2832);
          ctx.fillStyle = `rgba(140,112,235,${0.12 * f})`; ctx.fill();
          ctx.globalCompositeOperation = 'source-over';
          ctx.beginPath(); ctx.arc(pt.px, pt.py, sz + f * 1.6, 0, 6.2832);
          ctx.fillStyle = `rgba(${190 + f * 50},${178 + f * 50},${235 + f * 20},${Math.min(0.92, a + f * 0.8)})`; ctx.fill();
        } else {
          ctx.beginPath(); ctx.arc(pt.px, pt.py, sz, 0, 6.2832);
          ctx.fillStyle = `rgba(198,204,224,${a})`; ctx.fill();
        }
      }

      if (!reduced) while (pulses.length < BASE) spawnFrom((Math.random() * N) | 0);
      raf = requestAnimationFrame(draw);
    };

    const drawStatic = () => {
      const cosR = Math.cos(0.5), sinR = Math.sin(0.5);
      ctx.clearRect(0, 0, W, H);
      const bloom = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 2.6);
      bloom.addColorStop(0, 'rgba(132,108,210,0.10)');
      bloom.addColorStop(0.35, 'rgba(108,114,205,0.035)');
      bloom.addColorStop(0.7, 'rgba(100,108,200,0.012)');
      bloom.addColorStop(1, 'rgba(10,10,11,0)');
      ctx.fillStyle = bloom; ctx.beginPath(); ctx.arc(cx, cy, R * 2.6, 0, 6.2832); ctx.fill();
      for (let i = 0; i < N; i++) { P[i] = project(sphere[i], cosR, sinR); P[i].s = sphere[i]; }
      for (const [i, j] of edges) {
        const a = P[i], b = P[j];
        const depth = ((a.zr + b.zr) * 0.5 + 1) / 2;
        ctx.lineWidth = depth > 0.45 ? 1.25 : 1;
        ctx.strokeStyle = `rgba(175,185,230,${0.055 + depth * 0.12})`;
        ctx.beginPath(); ctx.moveTo(a.px, a.py); ctx.lineTo(b.px, b.py); ctx.stroke();
      }
      for (let i = 0; i < N; i++) {
        const d = P[i], depth = (d.zr + 1) / 2;
        ctx.beginPath(); ctx.arc(d.px, d.py, 1.1 + depth * 2.1, 0, 6.2832);
        ctx.fillStyle = `rgba(198,204,224,${0.14 + depth * 0.5})`; ctx.fill();
      }
    };

    const resize = () => {
      const rect = wrap.getBoundingClientRect();
      W = rect.width; H = rect.height;
      DPR = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.round(W * DPR);
      canvas.height = Math.round(H * DPR);
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
      layout();
      if (reduced) drawStatic();
    };

    const ro = new ResizeObserver(resize);
    ro.observe(wrap);
    resize();
    if (!reduced) raf = requestAnimationFrame(draw);

    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, []);

  return (
    <div className="hero-viz" ref={wrapRef} aria-hidden="true">
      <canvas ref={canvasRef} className="hero-viz-canvas" />
    </div>
  );
};

window.HeroViz = HeroViz;
