/* global React */
const { useEffect: useHeroEffect, useRef: useHeroRef } = React;

/* ============================================================================
   HeroBackground — branded honeycomb field on near-black.
   Modes: "lattice" (forms/dissolves, accent cells light in sequence),
          "ripple"  (hex cells ripple from cursor / load),
          "settle"  (particles settle into a hex grid, then drift apart).
   Reduced-motion → one calm static frame. Headline zone kept dim for contrast.
   ============================================================================ */
function HeroBackground({ mode = "lattice" }) {
  const ref = useHeroRef(null);

  useHeroEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d", { alpha: false });
    const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;

    let w = 0, h = 0, dpr = 1, raf = 0, visible = true, t = 0;
    let cells = [], igs = [], particles = [], lastIg = 0, lastSpawn = 0;
    const pointer = { x: -1, y: -1, active: false };
    // accent in rgb — kept in sync with Tweaks via apis:accent
    let aB = (window.__accentRGB && window.__accentRGB.bright) || [110, 151, 255];
    let aBase = (window.__accentRGB && window.__accentRGB.base) || [79, 124, 255];
    const R = 30;                       // hex circumradius
    const colW = 1.5 * R, rowH = Math.sqrt(3) * R;
    let curMode = mode;

    // unit flat-top hex vertices
    const VERT = [];
    for (let i = 0; i < 6; i++) { const a = Math.PI / 180 * 60 * i; VERT.push([Math.cos(a) * R, Math.sin(a) * R]); }

    function hexPath(x, y, scale = 1) {
      ctx.beginPath();
      for (let i = 0; i < 6; i++) {
        const vx = x + VERT[i][0] * scale, vy = y + VERT[i][1] * scale;
        i ? ctx.lineTo(vx, vy) : ctx.moveTo(vx, vy);
      }
      ctx.closePath();
    }

    // calm-zone mask: dim activity where the headline sits (left-center)
    function calm(nx, ny) {
      const d = ((nx - 0.27) / 0.44) ** 2 + ((ny - 0.5) / 0.4) ** 2;
      return 1 - 0.86 * Math.exp(-d);
    }

    function build() {
      cells = [];
      const cols = Math.ceil(w / colW) + 2;
      const rows = Math.ceil(h / rowH) + 2;
      for (let c = -1; c < cols; c++) {
        for (let r = -1; r < rows; r++) {
          const x = c * colW;
          const y = r * rowH + (c & 1 ? rowH / 2 : 0);
          const nx = x / w, ny = y / h;
          cells.push({ x, y, nx, ny, calm: calm(nx, ny), ph: Math.random() * Math.PI * 2 });
        }
      }
      // settle-mode particles: each targets a cell center
      particles = cells.filter((_, i) => i % 2 === 0).map((cell) => ({
        cell, x: Math.random() * w, y: Math.random() * h,
        sx: Math.random() * w, sy: Math.random() * h,
        seed: Math.random() * Math.PI * 2,
      }));
    }

    function resize() {
      const rect = canvas.getBoundingClientRect();
      w = Math.max(1, rect.width); h = Math.max(1, rect.height);
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.floor(w * dpr); canvas.height = Math.floor(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      build();
    }

    function base() { ctx.fillStyle = "#08090D"; ctx.fillRect(0, 0, w, h); }

    function spawnIg(x, y) {
      if (x == null) {
        // bias to right / upper area, away from headline
        x = w * (0.45 + Math.random() * 0.6);
        y = h * (Math.random() * 0.9);
      }
      igs.push({ x, y, t0: t, speed: 95 + Math.random() * 40, life: 4.4 });
      if (igs.length > 5) igs.shift();
    }

    // activation from ignition wavefronts at a point
    function activation(px, py) {
      let a = 0;
      for (const ig of igs) {
        const age = t - ig.t0;
        if (age < 0 || age > ig.life) continue;
        const d = Math.hypot(px - ig.x, py - ig.y);
        const front = ig.speed * age;
        const fade = Math.max(0, 1 - age / ig.life);
        const ring = Math.exp(-(((d - front) / 46) ** 2));
        a += ring * fade;
      }
      return a;
    }

    function drawLattice(ripple) {
      ctx.fillStyle = "#08090D"; ctx.fillRect(0, 0, w, h);
      ctx.lineWidth = 1;
      for (const cell of cells) {
        const breathe = 0.5 + 0.5 * Math.sin(t * 0.5 + cell.ph + cell.nx * 2);
        const baseA = (0.05 + 0.07 * breathe) * cell.calm;
        // outline
        ctx.strokeStyle = `rgba(150,170,210,${baseA})`;
        hexPath(cell.x, cell.y); ctx.stroke();
        // accent activation
        let act = activation(cell.x, cell.y) * cell.calm;
        if (ripple && pointer.active) {
          const d = Math.hypot(cell.x - pointer.x, cell.y - pointer.y);
          act += Math.max(0, 1 - d / 150) * 0.5 * cell.calm;
        }
        if (act > 0.015) {
          const aa = Math.min(0.85, act);
          ctx.strokeStyle = `rgba(${aB[0]},${aB[1]},${aB[2]},${aa})`;
          ctx.lineWidth = 1.2;
          hexPath(cell.x, cell.y); ctx.stroke();
          ctx.fillStyle = `rgba(${aBase[0]},${aBase[1]},${aBase[2]},${aa * 0.18})`;
          hexPath(cell.x, cell.y, 0.92); ctx.fill();
          ctx.lineWidth = 1;
        }
      }
    }

    function drawSettle() {
      base();
      // “order from motion” — scattered cloud converges into the hex grid, then
      // scatters again. Triangle cycle, smooth-eased.
      const cycle = (t * 0.085) % 1;
      const s = cycle < 0.5 ? cycle / 0.5 : 1 - (cycle - 0.5) / 0.5;
      const ease = s < 0.5 ? 2 * s * s : 1 - (2 - 2 * s) * (2 - 2 * s) / 2;
      ctx.lineWidth = 1;
      for (const p of particles) {
        const calm = p.cell.calm;
        const wob = 7 * (1 - ease);
        const hx = p.cell.x + Math.cos(t * 0.6 + p.seed) * wob;
        const hy = p.cell.y + Math.sin(t * 0.5 + p.seed) * wob;
        const x = p.sx + (hx - p.sx) * ease;
        const y = p.sy + (hy - p.sy) * ease;
        // forming hex cell lights up as the grid assembles
        if (ease > 0.55) {
          const ha = ((ease - 0.55) / 0.45) * 0.5 * calm;
          ctx.strokeStyle = `rgba(${aB[0]},${aB[1]},${aB[2]},${ha})`;
          hexPath(p.cell.x, p.cell.y, 0.9 * ease); ctx.stroke();
        }
        const a = (0.16 + 0.55 * ease) * calm;
        ctx.fillStyle = `rgba(${aB[0]},${aB[1]},${aB[2]},${a})`;
        const r = 1.2 + 1.6 * ease;
        ctx.fillRect(x - r / 2, y - r / 2, r, r);
      }
    }

    function staticFrame() {
      base();
      ctx.lineWidth = 1;
      for (const cell of cells) {
        ctx.strokeStyle = `rgba(150,170,210,${0.05 * cell.calm})`;
        hexPath(cell.x, cell.y); ctx.stroke();
      }
      // a calm cluster of lit cells, upper-right
      const seed = { x: w * 0.78, y: h * 0.32 };
      for (const cell of cells) {
        const d = Math.hypot(cell.x - seed.x, cell.y - seed.y);
        const a = Math.max(0, 1 - d / 220) * 0.5 * cell.calm;
        if (a > 0.02) {
          ctx.strokeStyle = `rgba(${aB[0]},${aB[1]},${aB[2]},${a})`; ctx.lineWidth = 1.2;
          hexPath(cell.x, cell.y); ctx.stroke();
          ctx.fillStyle = `rgba(${aBase[0]},${aBase[1]},${aBase[2]},${a * 0.16})`; hexPath(cell.x, cell.y, 0.92); ctx.fill();
          ctx.lineWidth = 1;
        }
      }
    }

    let prev = performance.now();
    function step(now) {
      const dt = Math.min(0.05, (now - prev) / 1000); prev = now; t += dt;
      if (curMode === "settle") {
        drawSettle();
      } else {
        const ripple = curMode === "ripple";
        if (!ripple && t - lastIg > 1.7) { spawnIg(); lastIg = t; }
        if (ripple && pointer.active && t - lastSpawn > 0.5) { spawnIg(pointer.x, pointer.y); lastSpawn = t; }
        if (ripple && t - lastIg > 3.2) { spawnIg(); lastIg = t; } // ambient even without pointer
        drawLattice(ripple);
      }
      raf = requestAnimationFrame(step);
    }

    function start() { if (raf || reduce || !visible) return; prev = performance.now(); raf = requestAnimationFrame(step); }
    function stop() { if (raf) cancelAnimationFrame(raf); raf = 0; }

    resize();
    if (reduce) staticFrame();
    else if (curMode === "settle") { t = 2.6; drawSettle(); start(); }
    else { spawnIg(w * 0.7, h * 0.3); spawnIg(w * 0.5, h * 0.62); drawLattice(false); start(); }

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      pointer.x = e.clientX - rect.left; pointer.y = e.clientY - rect.top; pointer.active = true;
    };
    const onLeave = () => { pointer.active = false; };
    if (!matchMedia("(pointer: coarse)").matches) {
      window.addEventListener("pointermove", onMove, { passive: true });
      window.addEventListener("pointerout", onLeave, { passive: true });
    }

    const ro = new ResizeObserver(() => { stop(); resize(); reduce ? staticFrame() : start(); });
    ro.observe(canvas);
    const io = new IntersectionObserver((es) => es.forEach((e) => { visible = e.isIntersecting; visible ? start() : stop(); }), { threshold: 0 });
    io.observe(canvas);

    // allow Tweaks to switch mode live
    const onMode = (e) => { curMode = e.detail; igs = []; lastIg = t; if (!reduce) { stop(); start(); } else staticFrame(); };
    window.addEventListener("apis:heromode", onMode);
    const onAccent = (e) => { aB = e.detail.bright; aBase = e.detail.base; if (reduce) staticFrame(); };
    window.addEventListener("apis:accent", onAccent);

    return () => {
      stop(); ro.disconnect(); io.disconnect();
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerout", onLeave);
      window.removeEventListener("apis:heromode", onMode);
      window.removeEventListener("apis:accent", onAccent);
    };
  }, [mode]);

  return <canvas ref={ref} aria-hidden="true" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }} />;
}

window.HeroBackground = HeroBackground;
