// background.jsx — animated canvas backgrounds: matrix / circuit / scanlines / off
// Exposes: window.BgCanvas component

function BgCanvas({ mode = 'matrix', palette }) {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const stateRef = React.useRef({});

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);

    function resize() {
      w = canvas.clientWidth;
      h = canvas.clientHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      stateRef.current.dirty = true;
    }
    resize();
    window.addEventListener('resize', resize);

    const accent = palette?.neon || '#39ff14';
    const purple = palette?.purple || '#7c2dc7';
    const bg = palette?.bg || '#0b0612';

    // ---- MATRIX RAIN ----
    function setupMatrix() {
      const fontSize = 14;
      const cols = Math.ceil(w / fontSize);
      const drops = new Array(cols).fill(0).map(() => Math.random() * -50);
      const chars = '01░▒▓│┤┐└┴┬├─┼ABCDEFGHIJKLMNOPQRSTUVWXYZ<>{}[]/\\$#@*+';
      stateRef.current = { drops, cols, fontSize, chars };
    }
    function drawMatrix() {
      const { drops, fontSize, chars } = stateRef.current;
      if (!drops) return;
      ctx.fillStyle = 'rgba(11, 6, 18, 0.08)';
      ctx.fillRect(0, 0, w, h);
      ctx.font = `${fontSize}px 'JetBrains Mono', monospace`;
      for (let i = 0; i < drops.length; i++) {
        const text = chars[Math.floor(Math.random() * chars.length)];
        const x = i * fontSize;
        const y = drops[i] * fontSize;
        // head
        if (Math.random() < 0.02) ctx.fillStyle = '#ffffff';
        else if (Math.random() < 0.15) ctx.fillStyle = accent;
        else ctx.fillStyle = `rgba(57, 255, 20, ${0.25 + Math.random() * 0.35})`;
        ctx.fillText(text, x, y);
        if (y > h && Math.random() > 0.975) drops[i] = 0;
        drops[i] += 1;
      }
    }

    // ---- CIRCUIT NODES ----
    function setupCircuit() {
      const nodes = [];
      const N = Math.floor((w * h) / 22000);
      for (let i = 0; i < N; i++) {
        nodes.push({
          x: Math.random() * w,
          y: Math.random() * h,
          vx: (Math.random() - 0.5) * 0.18,
          vy: (Math.random() - 0.5) * 0.18,
          pulse: Math.random() * Math.PI * 2,
        });
      }
      stateRef.current = { nodes };
    }
    function drawCircuit() {
      const { nodes } = stateRef.current;
      if (!nodes) return;
      ctx.fillStyle = 'rgba(11, 6, 18, 0.5)';
      ctx.fillRect(0, 0, w, h);
      const t = performance.now() * 0.001;
      // grid points lightly
      ctx.strokeStyle = 'rgba(124,45,199,0.08)';
      ctx.lineWidth = 1;
      const gridSize = 80;
      ctx.beginPath();
      for (let x = 0; x < w; x += gridSize) { ctx.moveTo(x, 0); ctx.lineTo(x, h); }
      for (let y = 0; y < h; y += gridSize) { ctx.moveTo(0, y); ctx.lineTo(w, y); }
      ctx.stroke();
      // edges
      for (let i = 0; i < nodes.length; i++) {
        const a = nodes[i];
        a.x += a.vx; a.y += a.vy;
        if (a.x < 0 || a.x > w) a.vx *= -1;
        if (a.y < 0 || a.y > h) a.vy *= -1;
        for (let j = i + 1; j < nodes.length; j++) {
          const b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx * dx + dy * dy;
          if (d2 < 140 * 140) {
            const alpha = (1 - Math.sqrt(d2) / 140) * 0.32;
            ctx.strokeStyle = `rgba(57,255,20,${alpha})`;
            ctx.lineWidth = 1;
            ctx.beginPath();
            // 90-degree path between nodes for circuit feel
            const midX = (Math.random() < 0.5) ? a.x : b.x;
            const midY = (Math.random() < 0.5) ? a.y : b.y;
            ctx.moveTo(a.x, a.y);
            ctx.lineTo(midX, midY);
            ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }
      // nodes
      nodes.forEach((n) => {
        n.pulse += 0.04;
        const r = 1.5 + Math.sin(n.pulse) * 0.7;
        ctx.fillStyle = accent;
        ctx.shadowBlur = 8;
        ctx.shadowColor = accent;
        ctx.beginPath();
        ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
        ctx.fill();
      });
      ctx.shadowBlur = 0;
    }

    // ---- CIRCUIT TRACES (PCB lines) ----
    function setupTraces() {
      const traces = [];
      const N = 26;
      for (let i = 0; i < N; i++) {
        traces.push(spawnTrace());
      }
      stateRef.current = { traces };
    }
    function spawnTrace() {
      const horiz = Math.random() < 0.5;
      const segs = 3 + Math.floor(Math.random() * 4);
      const path = [];
      let x = Math.random() * w;
      let y = Math.random() * h;
      path.push({ x, y });
      let dir = horiz ? 0 : 1; // 0 = horiz, 1 = vert
      for (let i = 0; i < segs; i++) {
        const len = 40 + Math.random() * 160;
        const sign = Math.random() < 0.5 ? -1 : 1;
        if (dir === 0) x += len * sign;
        else y += len * sign;
        path.push({ x, y });
        dir = 1 - dir;
      }
      return {
        path,
        progress: 0,
        speed: 0.004 + Math.random() * 0.006,
        life: 0,
        maxLife: 600 + Math.random() * 400,
      };
    }
    function drawTraces() {
      const { traces } = stateRef.current;
      if (!traces) return;
      ctx.fillStyle = 'rgba(11, 6, 18, 0.18)';
      ctx.fillRect(0, 0, w, h);
      traces.forEach((t, idx) => {
        t.life++;
        t.progress = Math.min(1, t.progress + t.speed);
        // fade in then out
        const fade = t.life < 60 ? t.life / 60 :
                     t.life > t.maxLife - 80 ? Math.max(0, (t.maxLife - t.life) / 80) : 1;
        const total = t.path.length - 1;
        const cur = t.progress * total;
        ctx.lineWidth = 1.4;
        ctx.lineCap = 'round';
        // draw filled portion
        ctx.strokeStyle = `rgba(57,255,20,${0.35 * fade})`;
        ctx.beginPath();
        ctx.moveTo(t.path[0].x, t.path[0].y);
        for (let i = 1; i <= Math.floor(cur); i++) {
          ctx.lineTo(t.path[i].x, t.path[i].y);
        }
        if (Math.floor(cur) < total) {
          const p0 = t.path[Math.floor(cur)];
          const p1 = t.path[Math.floor(cur) + 1];
          const f = cur - Math.floor(cur);
          ctx.lineTo(p0.x + (p1.x - p0.x) * f, p0.y + (p1.y - p0.y) * f);
        }
        ctx.stroke();
        // head dot
        if (t.progress < 1) {
          const p0 = t.path[Math.floor(cur)];
          const p1 = t.path[Math.min(total, Math.floor(cur) + 1)];
          const f = cur - Math.floor(cur);
          const hx = p0.x + (p1.x - p0.x) * f;
          const hy = p0.y + (p1.y - p0.y) * f;
          ctx.fillStyle = `rgba(57,255,20,${0.9 * fade})`;
          ctx.shadowBlur = 10;
          ctx.shadowColor = accent;
          ctx.beginPath();
          ctx.arc(hx, hy, 2.2, 0, Math.PI * 2);
          ctx.fill();
          ctx.shadowBlur = 0;
        }
        // pads (junctions)
        ctx.fillStyle = `rgba(124,45,199,${0.6 * fade})`;
        for (let i = 0; i <= Math.floor(cur); i++) {
          ctx.beginPath();
          ctx.arc(t.path[i].x, t.path[i].y, 2.4, 0, Math.PI * 2);
          ctx.fill();
        }
        if (t.life > t.maxLife) traces[idx] = spawnTrace();
      });
    }

    // ---- HEX GRID (pulse) ----
    function setupHex() {
      stateRef.current = { ripples: [
        { x: w * 0.25, y: h * 0.35, born: -1500 },
        { x: w * 0.7, y: h * 0.7, born: -3000 },
      ] };
    }
    function drawHex() {
      ctx.clearRect(0, 0, w, h);
      const r = 9;
      const dx = r * Math.sqrt(3);
      const dy = r * 1.5;
      const now = performance.now();
      const time = now * 0.001;
      const st = stateRef.current;
      if (!st.ripples) st.ripples = [];
      if (Math.random() < 0.01) {
        st.ripples.push({ x: Math.random() * w, y: Math.random() * h, born: now });
      }
      st.ripples = st.ripples.filter(rp => now - rp.born < 3500);
      let row = 0;
      for (let y = dy / 2; y < h + dy; y += dy) {
        const offX = (row % 2) * (dx / 2);
        for (let x = -dx + offX; x < w + dx; x += dx) {
          const wave = Math.sin(x * 0.01 + y * 0.012 + time * 1.4) * 0.5 + 0.5;
          let a = 0.08 + wave * 0.16;
          let dotR = 1.2 + wave * 0.6;
          for (const rip of st.ripples) {
            const age = (now - rip.born) / 1000;
            const radius = age * 240;
            const dist = Math.hypot(x - rip.x, y - rip.y);
            if (Math.abs(dist - radius) < 50) {
              const k = 1 - Math.abs(dist - radius) / 50;
              const fade = Math.max(0, 1 - age / 3.5);
              a += k * 0.55 * fade;
              dotR += k * 1.4 * fade;
            }
          }
          ctx.fillStyle = `rgba(57,255,20,${Math.min(0.9, a)})`;
          ctx.beginPath();
          ctx.arc(x, y, dotR, 0, Math.PI * 2);
          ctx.fill();
        }
        row++;
      }
    }

    // ---- MESH LINES (grid + traveling pulse) ----
    function setupMesh() {
      const cell = 56;
      const cols = Math.ceil(w / cell) + 1;
      const rows = Math.ceil(h / cell) + 1;
      // a handful of pulses traveling along grid lines
      const pulses = [];
      for (let i = 0; i < 14; i++) pulses.push(spawnMeshPulse(w, h, cell));
      stateRef.current = { cell, cols, rows, pulses };
    }
    function spawnMeshPulse(w, h, cell) {
      const horiz = Math.random() < 0.5;
      if (horiz) {
        const y = Math.floor(Math.random() * Math.ceil(h / cell)) * cell;
        return { horiz, y, x: -cell, x2: 0, speed: 1.2 + Math.random() * 1.5, life: 0 };
      } else {
        const x = Math.floor(Math.random() * Math.ceil(w / cell)) * cell;
        return { horiz, x, y: -cell, y2: 0, speed: 1.2 + Math.random() * 1.5, life: 0 };
      }
    }
    function drawMesh() {
      ctx.clearRect(0, 0, w, h);
      const { cell, pulses } = stateRef.current;
      if (!cell) return;
      // base grid
      ctx.strokeStyle = 'rgba(124,45,199,0.16)';
      ctx.lineWidth = 1;
      ctx.beginPath();
      for (let x = 0; x < w; x += cell) { ctx.moveTo(x, 0); ctx.lineTo(x, h); }
      for (let y = 0; y < h; y += cell) { ctx.moveTo(0, y); ctx.lineTo(w, y); }
      ctx.stroke();
      // grid intersections
      ctx.fillStyle = 'rgba(57,255,20,0.20)';
      for (let x = 0; x < w; x += cell) {
        for (let y = 0; y < h; y += cell) {
          ctx.beginPath();
          ctx.arc(x, y, 1.4, 0, Math.PI * 2);
          ctx.fill();
        }
      }
      // pulses
      for (let i = 0; i < pulses.length; i++) {
        const p = pulses[i];
        p.life++;
        if (p.horiz) p.x += p.speed; else p.y += p.speed;
        const len = 80;
        const grd = p.horiz
          ? ctx.createLinearGradient(p.x - len, 0, p.x, 0)
          : ctx.createLinearGradient(0, p.y - len, 0, p.y);
        grd.addColorStop(0, 'rgba(57,255,20,0)');
        grd.addColorStop(0.85, 'rgba(57,255,20,0.7)');
        grd.addColorStop(1, 'rgba(180,255,160,1)');
        ctx.strokeStyle = grd;
        ctx.lineWidth = 1.6;
        ctx.beginPath();
        if (p.horiz) {
          ctx.moveTo(p.x - len, p.y);
          ctx.lineTo(p.x, p.y);
        } else {
          ctx.moveTo(p.x, p.y - len);
          ctx.lineTo(p.x, p.y);
        }
        ctx.stroke();
        // head glow
        ctx.fillStyle = 'rgba(180,255,160,0.9)';
        ctx.shadowBlur = 10;
        ctx.shadowColor = accent;
        ctx.beginPath();
        ctx.arc(p.horiz ? p.x : p.x, p.horiz ? p.y : p.y, 2.4, 0, Math.PI * 2);
        ctx.fill();
        ctx.shadowBlur = 0;
        if ((p.horiz && p.x > w + 50) || (!p.horiz && p.y > h + 50)) {
          pulses[i] = spawnMeshPulse(w, h, cell);
        }
      }
    }

    // ---- TOPOGRAPHY (flowing contour lines) ----
    function setupTopo() {
      stateRef.current = { t: 0 };
    }
    function drawTopo() {
      ctx.clearRect(0, 0, w, h);
      const time = performance.now() * 0.00018;
      const lineCount = 14;
      const step = 12;
      ctx.lineWidth = 1;
      for (let i = 0; i < lineCount; i++) {
        const baseY = (h / (lineCount - 1)) * i;
        const phase = i * 0.5 + time * 2;
        const amp = 18 + (i % 3) * 8;
        ctx.strokeStyle = `rgba(57,255,20,${0.12 + (i % 2) * 0.10})`;
        ctx.beginPath();
        for (let x = 0; x <= w; x += step) {
          const y = baseY
            + Math.sin(x * 0.005 + phase) * amp
            + Math.sin(x * 0.013 + phase * 1.3) * (amp * 0.4);
          if (x === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        }
        ctx.stroke();
      }
      // sprinkle of glow points along a couple of contours
      for (let i = 0; i < 6; i++) {
        const baseY = (h / (lineCount - 1)) * (i * 2);
        const phase = (i * 2) * 0.5 + time * 2;
        const amp = 18 + ((i * 2) % 3) * 8;
        const px = ((time * 80 + i * 240) % (w + 100));
        const py = baseY + Math.sin(px * 0.005 + phase) * amp
                 + Math.sin(px * 0.013 + phase * 1.3) * (amp * 0.4);
        ctx.fillStyle = 'rgba(180,255,160,0.9)';
        ctx.shadowBlur = 12;
        ctx.shadowColor = accent;
        ctx.beginPath();
        ctx.arc(px, py, 2.2, 0, Math.PI * 2);
        ctx.fill();
        ctx.shadowBlur = 0;
      }
    }

    // ---- CONSTELLATION (slow drifting nodes + lines) ----
    function setupConstellation() {
      const nodes = [];
      const N = Math.floor((w * h) / 14000);
      for (let i = 0; i < N; i++) {
        nodes.push({
          x: Math.random() * w,
          y: Math.random() * h,
          vx: (Math.random() - 0.5) * 0.12,
          vy: (Math.random() - 0.5) * 0.12,
          tw: Math.random() * Math.PI * 2,
        });
      }
      stateRef.current = { nodes };
    }
    function drawConstellation() {
      ctx.clearRect(0, 0, w, h);
      const { nodes } = stateRef.current;
      if (!nodes) return;
      const t = performance.now() * 0.002;
      // lines
      for (let i = 0; i < nodes.length; i++) {
        const a = nodes[i];
        a.x += a.vx; a.y += a.vy;
        if (a.x < 0) a.x += w; else if (a.x > w) a.x -= w;
        if (a.y < 0) a.y += h; else if (a.y > h) a.y -= h;
        for (let j = i + 1; j < nodes.length; j++) {
          const b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx * dx + dy * dy;
          const max = 130;
          if (d2 < max * max) {
            const alpha = (1 - Math.sqrt(d2) / max) * 0.30;
            ctx.strokeStyle = `rgba(57,255,20,${alpha})`;
            ctx.lineWidth = 1;
            ctx.beginPath();
            ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }
      // dots with twinkle
      for (const n of nodes) {
        n.tw += 0.05;
        const tw = 0.7 + Math.sin(n.tw) * 0.3;
        ctx.fillStyle = `rgba(180,255,160,${0.7 * tw})`;
        ctx.beginPath();
        ctx.arc(n.x, n.y, 1.6 * tw, 0, Math.PI * 2);
        ctx.fill();
      }
    }

    // ---- DOTS GRID (pulse grid, full bleed) ----
    function setupDots() {
      stateRef.current = { t: 0, ripples: [] };
      // seed a couple of ripples so motion is visible from frame 1
      stateRef.current.ripples.push({ x: w * 0.3, y: h * 0.4, born: -1200 });
      stateRef.current.ripples.push({ x: w * 0.75, y: h * 0.65, born: -2800 });
    }
    function drawDots() {
      ctx.clearRect(0, 0, w, h);
      const spacing = 28;
      const now = performance.now();
      const time = now * 0.0009;

      // occasionally spawn a new ripple somewhere
      const st = stateRef.current;
      if (!st.ripples) st.ripples = [];
      if (Math.random() < 0.012) {
        st.ripples.push({ x: Math.random() * w, y: Math.random() * h, born: now });
      }
      // cull old
      st.ripples = st.ripples.filter(r => now - r.born < 3200);

      for (let y = spacing / 2; y < h; y += spacing) {
        for (let x = spacing / 2; x < w; x += spacing) {
          // base wave
          const wave = Math.sin((x * 0.012 + y * 0.014) + time * 1.6) * 0.5 + 0.5;
          let a = 0.10 + wave * 0.20;
          let r = 1.3 + wave * 0.9;
          // ripple boosts
          for (const rip of st.ripples) {
            const age = (now - rip.born) / 1000; // seconds
            const radius = age * 220;
            const dist = Math.hypot(x - rip.x, y - rip.y);
            const band = 40;
            if (Math.abs(dist - radius) < band) {
              const k = 1 - Math.abs(dist - radius) / band;
              const fade = Math.max(0, 1 - age / 3.2);
              a += k * 0.55 * fade;
              r += k * 1.6 * fade;
            }
          }
          ctx.fillStyle = `rgba(57,255,20,${Math.min(0.95, a)})`;
          ctx.beginPath();
          ctx.arc(x, y, r, 0, Math.PI * 2);
          ctx.fill();
        }
      }
    }

    function loop() {
      if (mode === 'matrix') drawMatrix();
      else if (mode === 'circuit') drawCircuit();
      else if (mode === 'traces') drawTraces();
      else if (mode === 'dots') drawDots();
      else if (mode === 'hex') drawHex();
      else if (mode === 'mesh') drawMesh();
      else if (mode === 'topo') drawTopo();
      else if (mode === 'constellation') drawConstellation();
      rafRef.current = requestAnimationFrame(loop);
    }

    if (mode === 'matrix') setupMatrix();
    else if (mode === 'circuit') setupCircuit();
    else if (mode === 'traces') setupTraces();
    else if (mode === 'dots') setupDots();
    else if (mode === 'hex') setupHex();
    else if (mode === 'mesh') setupMesh();
    else if (mode === 'topo') setupTopo();
    else if (mode === 'constellation') setupConstellation();

    if (mode !== 'off') {
      // black initial paint
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, w, h);
      loop();
    } else {
      ctx.clearRect(0, 0, w, h);
    }

    return () => {
      cancelAnimationFrame(rafRef.current);
      window.removeEventListener('resize', resize);
    };
  }, [mode, palette]);

  return <canvas id="bg-canvas" ref={canvasRef} />;
}

window.BgCanvas = BgCanvas;
