/* ============================================================
   HERO BACKGROUND — the animated causal net from the live site
   (src/landing.jsx), copied verbatim into the dev build and renamed so
   the two page sets can never collide. Canvas particle network: ink
   nodes drifting on a rotating 3D shell, cyan signals travelling
   through it. prefers-reduced-motion falls back to the static dot grid.
   ============================================================ */

/* GraphFlow — canvas particle network in the hero background.
   Faint ink nodes drift slowly; edges appear between close nodes; a few
   cyan "signal" particles travel left → right toward a sink (feedback
   flowing through the causal net to insight). Pauses when the tab is
   hidden. prefers-reduced-motion → static dot grid (no canvas).        */
const { useEffect: dgfUseEffect, useRef: dgfUseRef, useState: dgfUseState } = React;

function DevGraphFlowCanvas() {
  const canvasRef = dgfUseRef(null);

  dgfUseEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    /* No 2d context (canvas blocked, or a renderer without one): leave the
       backdrop empty rather than taking the page down with it. */
    if (!ctx) return;
    let raf = null;
    let running = true;
    let W = 0, H = 0;
    const DPR = Math.min(window.devicePixelRatio || 1, 2);

    /* Brand palette, theme-aware. Navy -> cyan is the one allowed gradient
       (same as .grad-word), so the net reads as part of the system. */
    const isDark = () => document.documentElement.getAttribute('data-theme') === 'dark';
    let PAL = null;
    const readColors = () => {
      PAL = isDark()
        ? { edge: [96, 165, 250],  node: [170, 190, 220], cyan: [34, 211, 238] }
        : { edge: [30, 58, 138],   node: [30, 58, 138],   cyan: [8, 145, 178] };
    };
    readColors();
    const themeObs = new MutationObserver(readColors);
    themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });

    const rand = (a, b) => a + Math.random() * (b - a);

    /* ── 3D network cloud ──
       Points live on a loose ellipsoid shell in 3D. Topology is fixed
       (2-nearest neighbours + component stitching = ONE connected net).
       The whole cloud rotates slowly around Y with a slight X wobble;
       perspective projection gives genuine depth: near nodes are larger
       and brighter, far ones recede. */
    let nodes = [], edges = [], adj = [];
    let signals = [];
    const SIGNALS = 12;
    let CX = 0, CY = 0, RX = 0, RY = 0, RZ = 0, F = 1;

    const buildGraph = () => {
      const N = 150;
      nodes = [];
      for (let i = 0; i < N; i++) {
        /* direction on unit sphere, radius biased to a shell band */
        const th = rand(0, Math.PI * 2);
        const ph = Math.acos(rand(-1, 1));
        const r = 0.55 + 0.45 * Math.random();
        nodes.push({
          x0: Math.sin(ph) * Math.cos(th) * r,
          y0: Math.cos(ph) * r,
          z0: Math.sin(ph) * Math.sin(th) * r,
          x: 0, y: 0, s: 1, d: 0,
        });
      }
      const d2 = (a, b) => {
        const dx = a.x0 - b.x0, dy = a.y0 - b.y0, dz = a.z0 - b.z0;
        return dx * dx + dy * dy + dz * dz;
      };
      const key = (i, j) => (i < j ? i * 100000 + j : j * 100000 + i);
      const seen = new Set();
      edges = [];
      const pushEdge = (i, j) => {
        if (i === j) return;
        const k = key(i, j);
        if (seen.has(k)) return;
        seen.add(k); edges.push([i, j]);
      };
      for (let i = 0; i < N; i++) {
        const order = nodes.map((n, j) => [d2(nodes[i], n), j]).sort((a, b) => a[0] - b[0]);
        pushEdge(i, order[1][1]);
        pushEdge(i, order[2][1]);
      }
      const parent = Array.from({ length: N }, (_, i) => i);
      const find = (x) => { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; };
      const union = (a, b) => { parent[find(a)] = find(b); };
      for (const [i, j] of edges) union(i, j);
      let guard = 0;
      while (guard++ < N) {
        const roots = new Set();
        for (let i = 0; i < N; i++) roots.add(find(i));
        if (roots.size <= 1) break;
        const rootA = Array.from(roots)[0];
        let best = null;
        for (let i = 0; i < N; i++) {
          if (find(i) !== rootA) continue;
          for (let j = 0; j < N; j++) {
            if (find(j) === rootA) continue;
            const d = d2(nodes[i], nodes[j]);
            if (!best || d < best[0]) best = [d, i, j];
          }
        }
        pushEdge(best[1], best[2]); union(best[1], best[2]);
      }
      adj = Array.from({ length: nodes.length }, () => []);
      for (const [i, j] of edges) { adj[i].push(j); adj[j].push(i); }
    };

    /* Bubbles: random walk along the edges, in 3D */
    const newWalk = (s) => {
      s.from = Math.floor(Math.random() * nodes.length);
      const nb = adj[s.from];
      s.to = nb[Math.floor(Math.random() * nb.length)];
      s.t = Math.random() * 0.6;
      s.v = rand(0.10, 0.16);                     /* edge fractions per second */
    };
    const buildSignals = () => {
      signals = [];
      for (let i = 0; i < SIGNALS; i++) { const s = {}; newWalk(s); signals.push(s); }
    };
    const advanceWalk = (s) => {
      const nb = adj[s.to].filter(n => n !== s.from);
      const pool = nb.length ? nb : adj[s.to];
      const next = pool[Math.floor(Math.random() * pool.length)];
      s.from = s.to; s.to = next; s.t = 0;
    };

    const resize = () => {
      const rect = canvas.parentElement.getBoundingClientRect();
      W = rect.width; H = rect.height;
      canvas.width = W * DPR; canvas.height = H * DPR;
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
      /* Large globe anchored AT the right screen edge, reaching into the
         page — partially clipped right, dissolving toward the centre. */
      CX = W * 0.88; CY = H * 0.47;
      RX = Math.max(W * 0.48, H * 0.60);
      RY = H * 0.55;
      RZ = RX;
      F  = RX * 2.6;                              /* perspective focal length */
      buildGraph();
      buildSignals();
    };

    const project = (n, sinA, cosA, sinB, cosB) => {
      /* rotate around Y, then slight X wobble */
      const x1 = n.x0 * cosA + n.z0 * sinA;
      const z1 = -n.x0 * sinA + n.z0 * cosA;
      const y1 = n.y0 * cosB - z1 * sinB;
      const z2 = n.y0 * sinB + z1 * cosB;
      const per = F / (F + z2 * RZ);
      n.x = CX + x1 * RX * per;
      n.y = CY + y1 * RY * per;
      n.s = per;
      n.d = Math.min(1, Math.max(0, (1.18 - per) * -4 + 1));  /* 0 far .. 1 near */
      /* simpler + stable: normalize via z2 in [-1,1] */
      n.d = 1 - (z2 + 1) / 2;
    };

    let last = performance.now();
    const step = (now) => {
      if (!running) return;
      const dt = Math.min(50, now - last); last = now;
      ctx.clearRect(0, 0, W, H);

      const a = now * 0.000055;                    /* ~1 rev / 115 s */
      const b = Math.sin(now * 0.000031) * 0.16;   /* gentle wobble */
      const sinA = Math.sin(a), cosA = Math.cos(a);
      const sinB = Math.sin(b), cosB = Math.cos(b);
      for (const n of nodes) project(n, sinA, cosA, sinB, cosB);

      /* edges — depth-cued, navy base with a whisper of cyan on near edges */
      ctx.lineWidth = 1;
      const [er, eg, eb] = PAL.edge;
      const [cr, cg, cb] = PAL.cyan;
      for (const [i, j] of edges) {
        const A = nodes[i], B = nodes[j];
        const depth = (A.d + B.d) / 2;             /* 0 far .. 1 near */
        const mix = depth * 0.45;                  /* cyan share on near edges */
        const R2 = Math.round(er + (cr - er) * mix);
        const G2 = Math.round(eg + (cg - eg) * mix);
        const B2 = Math.round(eb + (cb - eb) * mix);
        const alpha = 0.025 + 0.105 * depth * depth;
        ctx.strokeStyle = `rgba(${R2}, ${G2}, ${B2}, ${alpha.toFixed(3)})`;
        ctx.beginPath();
        ctx.moveTo(A.x, A.y);
        ctx.lineTo(B.x, B.y);
        ctx.stroke();
      }

      /* nodes — size + brightness by depth */
      const [nr, ng, nb2] = PAL.node;
      for (const n of nodes) {
        const alpha = 0.05 + 0.26 * n.d * n.d;
        const rad = 0.7 + 1.5 * n.d * n.s;
        ctx.fillStyle = `rgba(${nr}, ${ng}, ${nb2}, ${alpha.toFixed(3)})`;
        ctx.beginPath();
        ctx.arc(n.x, n.y, rad, 0, Math.PI * 2);
        ctx.fill();
      }

      /* bubbles — cyan, flowing through the net, depth-scaled */
      for (const s of signals) {
        s.t += s.v * dt / 1000;
        if (s.t >= 1) { advanceWalk(s); continue; }
        const A = nodes[s.from], B = nodes[s.to];
        const x = A.x + (B.x - A.x) * s.t;
        const y = A.y + (B.y - A.y) * s.t;
        const d = A.d + (B.d - A.d) * s.t;
        const alpha = 0.20 + 0.45 * d;
        const rad = 1.2 + 1.5 * d;
        ctx.save();
        ctx.shadowColor = `rgba(${cr}, ${cg}, ${cb}, 0.65)`;
        ctx.shadowBlur = 9;
        ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${alpha.toFixed(3)})`;
        ctx.beginPath();
        ctx.arc(x, y, rad, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      raf = requestAnimationFrame(step);
    };

    const onVisibility = () => {
      if (document.hidden) {
        running = false;
        if (raf) cancelAnimationFrame(raf);
      } else if (!running) {
        running = true;
        last = performance.now();
        raf = requestAnimationFrame(step);
      }
    };

    resize();
    /* Observe the ELEMENT, not the window: fonts/layout can grow the hero
       after mount, which used to leave a tiny stale canvas in the corner. */
    const ro = new ResizeObserver(() => resize());
    ro.observe(canvas.parentElement);
    document.addEventListener('visibilitychange', onVisibility);
    raf = requestAnimationFrame(step);

    return () => {
      running = false;
      if (raf) cancelAnimationFrame(raf);
      ro.disconnect();
      document.removeEventListener('visibilitychange', onVisibility);
      themeObs.disconnect();
    };
  }, []);

  /* Full presence at the right edge, dissolving toward the page centre. */
  return (
    <canvas
      ref={canvasRef}
      className="absolute inset-0"
      style={{
        WebkitMaskImage: 'linear-gradient(to left, black 0%, black 34%, transparent 92%)',
        maskImage: 'linear-gradient(to left, black 0%, black 34%, transparent 92%)',
      }}
    />
  );
}

function DevHeroDotNet() {
  // Right-side hero backdrop. Animated graph net when motion is allowed,
  // the original static dot grid otherwise. Both sit under the same cyan
  // whisper glow and dissolve toward the right edge.
  const reduced = typeof window !== 'undefined'
    && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  return (
    <div aria-hidden
      className="absolute pointer-events-none hidden md:block"
      style={{ top: 0, right: 0, bottom: 0, width: '58%', zIndex: 0 }}>
      {/* Whisper of cyan glow — anchored upper-right, ~5% opacity */}
      <div className="absolute inset-0" style={{
        background: 'radial-gradient(ellipse 60% 70% at 72% 46%, rgba(34, 211, 238, 0.06), transparent 72%)',
      }}/>
      {reduced ? (
        <div className="absolute inset-0" style={{
          backgroundImage: 'radial-gradient(rgb(var(--c-ink) / 0.16) 1.2px, transparent 1.2px)',
          backgroundSize: '22px 22px',
          WebkitMaskImage: 'linear-gradient(to right, transparent 0%, black 32%, black 58%, transparent 100%)',
          maskImage: 'linear-gradient(to right, transparent 0%, black 32%, black 58%, transparent 100%)',
        }}/>
      ) : (
        <DevGraphFlowCanvas/>
      )}
    </div>
  );
}


window.DevHeroDotNet = DevHeroDotNet;
