/* NEXABOT — generative cinematic backdrops (canvas) */

/* cheap smooth pseudo-noise angle field from layered trig */
function flowAngle(x, y, t) {
  return (
    Math.sin(x * 0.0016 + t) * 1.1 +
    Math.cos(y * 0.0019 - t * 0.7) * 1.1 +
    Math.sin((x + y) * 0.0011 + t * 0.4) * 0.8
  ) * Math.PI;
}

function readColors() {
  const css = getComputedStyle(document.documentElement);
  return {
    accent: (css.getPropertyValue("--accent-2").trim() || "#7c93ff"),
    paper: (css.getPropertyValue("--peri").trim() || "#d5e0ff"),
    ink: (css.getPropertyValue("--navy").trim() || "#020a19"),
  };
}

function hexToRgb(hex) {
  const h = hex.replace("#", "");
  return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
}

function Backdrop({ scene = "flow", intensity = 1, className = "" }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    let raf, W, H, DPR, particles = [], t = 0, running = true;

    function resize() {
      DPR = Math.min(window.devicePixelRatio || 1, 2);
      W = canvas.clientWidth; H = canvas.clientHeight;
      if (!W || !H) return;
      canvas.width = W * DPR; canvas.height = H * DPR;
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
      init();
    }

    function init() {
      const base = scene === "panel" ? 26 : 70;
      const count = Math.round(Math.max(18, Math.min(160, (W * H) / 9000)) * intensity * (base / 70));
      particles = Array.from({ length: count }, () => spawn());
      ctx.clearRect(0, 0, W, H);
    }
    function spawn() {
      return { x: Math.random() * W, y: Math.random() * H, life: Math.random() * 120 + 40, age: Math.random() * 160 };
    }

    const { accent, paper, ink } = readColors();
    const [ar, ag, ab] = hexToRgb(accent);
    const [pr, pg, pb] = hexToRgb(paper);

    function frameFlow(dt) {
      // fade trail
      ctx.globalCompositeOperation = "source-over";
      ctx.fillStyle = `rgba(${hexToRgb(ink).join(",")}, ${reduce ? 1 : 0.09})`;
      ctx.fillRect(0, 0, W, H);
      ctx.globalCompositeOperation = "lighter";
      for (const p of particles) {
        const a = flowAngle(p.x, p.y, t);
        const vx = Math.cos(a), vy = Math.sin(a);
        const nx = p.x + vx * 1.5 * dt, ny = p.y + vy * 1.5 * dt;
        // color blend along field
        const mix = (Math.sin(p.x * 0.004 + p.y * 0.004 + t) + 1) / 2;
        const r = Math.round(ar * mix + pr * (1 - mix));
        const g = Math.round(ag * mix + pg * (1 - mix));
        const b = Math.round(ab * mix + pb * (1 - mix));
        ctx.strokeStyle = `rgba(${r},${g},${b},0.32)`;
        ctx.lineWidth = 0.9;
        ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(nx, ny); ctx.stroke();
        p.x = nx; p.y = ny; p.age += dt;
        if (p.x < -10 || p.x > W + 10 || p.y < -10 || p.y > H + 10 || p.age > p.life) {
          Object.assign(p, spawn());
        }
      }
      ctx.globalCompositeOperation = "source-over";
    }

    function frameRings() {
      ctx.clearRect(0, 0, W, H);
      const cx = W * 0.5, cy = H * 0.52, maxR = Math.hypot(W, H) * 0.42;
      ctx.lineWidth = 1;
      for (let i = 0; i < 7; i++) {
        const r = maxR * (i + 1) / 7;
        const wob = Math.sin(t * 0.8 + i) * 6;
        ctx.strokeStyle = `rgba(${ar},${ag},${ab},${0.05 + 0.12 * (1 - i / 7)})`;
        ctx.beginPath(); ctx.arc(cx, cy, r + wob, 0, Math.PI * 2); ctx.stroke();
      }
      const N = 9;
      for (let i = 0; i < N; i++) {
        const ang = (i / N) * Math.PI * 2 + t * 0.4;
        const rr = maxR * (0.35 + 0.5 * ((i * 7 % N) / N));
        const x = cx + Math.cos(ang) * rr, y = cy + Math.sin(ang) * rr;
        ctx.fillStyle = `rgba(${pr},${pg},${pb},0.7)`;
        ctx.beginPath(); ctx.arc(x, y, 2, 0, Math.PI * 2); ctx.fill();
        ctx.strokeStyle = `rgba(${ar},${ag},${ab},0.25)`;
        ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(x, y); ctx.stroke();
      }
      ctx.fillStyle = `rgba(${ar},${ag},${ab},0.9)`;
      ctx.beginPath(); ctx.arc(cx, cy, 3.2, 0, Math.PI * 2); ctx.fill();
    }

    function frameGrid() {
      ctx.clearRect(0, 0, W, H);
      const cols = 16, rows = 10, gx = W / cols, gy = H / rows;
      for (let i = 0; i <= cols; i++) {
        for (let j = 0; j <= rows; j++) {
          const wob = Math.sin(i * 0.6 + j * 0.5 + t * 1.2);
          const alpha = 0.12 + 0.4 * ((wob + 1) / 2);
          const sz = 0.7 + ((wob + 1) / 2) * 1.4;
          ctx.fillStyle = wob > 0.7 ? `rgba(${ar},${ag},${ab},${alpha})` : `rgba(${pr},${pg},${pb},${alpha * 0.5})`;
          ctx.beginPath(); ctx.arc(i * gx, j * gy, sz, 0, Math.PI * 2); ctx.fill();
        }
      }
    }

    function frameWave() {
      ctx.clearRect(0, 0, W, H);
      for (let l = 0; l < 5; l++) {
        ctx.beginPath();
        const amp = 18 + l * 9, yo = H * (0.32 + l * 0.1);
        for (let x = 0; x <= W; x += 8) {
          const y = yo + Math.sin(x * 0.012 + t * 1.1 + l * 0.8) * amp + Math.sin(x * 0.03 - t) * 5;
          x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
        }
        ctx.strokeStyle = l % 2 ? `rgba(${ar},${ag},${ab},${0.3 - l * 0.04})` : `rgba(${pr},${pg},${pb},${0.22 - l * 0.03})`;
        ctx.lineWidth = 1; ctx.stroke();
      }
    }

    const renderers = { flow: frameFlow, rings: frameRings, grid: frameGrid, wave: frameWave };
    const fn = renderers[scene] || frameFlow;

    let last = performance.now();
    function loop(now) {
      if (!running) return;
      const dt = Math.min(2.4, (now - last) / 16.67); last = now;
      t += 0.0042 * (scene === "flow" ? 1 : 1);
      if (scene === "flow") fn(dt); else fn();
      raf = requestAnimationFrame(loop);
    }

    resize();
    if (reduce) {
      // a few static passes
      if (scene === "flow") { for (let i = 0; i < 60; i++) frameFlow(1.5); } else fn();
    } else {
      raf = requestAnimationFrame(loop);
    }

    const onResize = () => resize();
    window.addEventListener("resize", onResize);
    // pause when offscreen
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => {
        if (e.isIntersecting && !reduce) { if (!running) { running = true; last = performance.now(); raf = requestAnimationFrame(loop); } }
        else { running = false; cancelAnimationFrame(raf); }
      });
    }, { threshold: 0 });
    io.observe(canvas);

    return () => {
      running = false; cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize); io.disconnect();
    };
  }, [scene, intensity]);

  return <canvas ref={ref} className={className} />;
}

window.Backdrop = Backdrop;
