// NoMind — emergent background systems. All canvas-based, slow.
const { useEffect, useRef } = React;

function useCanvas(draw, deps = []) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current;
    if (!c) return;
    const ctx = c.getContext('2d');
    let raf, start = performance.now();
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let w = 0, h = 0;
    const resize = () => {
      const rect = c.getBoundingClientRect();
      w = rect.width; h = rect.height;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(c);
    const loop = (t) => {
      const elapsed = (t - start) / 1000;
      draw(ctx, w, h, elapsed);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, deps);
  return ref;
}

// ——— 1. Field: sparse shimmering dot grid
function FieldBg({ motion = 0.35, accent = '#000' }) {
  const ref = useCanvas((ctx, w, h, t) => {
    ctx.clearRect(0, 0, w, h);
    const spacing = 24;
    const cols = Math.ceil(w / spacing);
    const rows = Math.ceil(h / spacing);
    const amp = 0.4 + 0.6 * motion;
    for (let i = 0; i < cols; i++) {
      for (let j = 0; j < rows; j++) {
        const x = i * spacing + spacing / 2;
        const y = j * spacing + spacing / 2;
        const n = Math.sin(i * 0.35 + t * 0.35 * amp) * Math.cos(j * 0.28 - t * 0.22 * amp);
        // opacity 0.08 - 0.38 — always visible
        const a = 0.18 + 0.20 * n;
        const r = 1.0 + 0.8 * Math.max(0, n);
        ctx.fillStyle = `rgba(0,0,0,${Math.max(0.05, a)})`;
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }, [motion]);
  return <canvas ref={ref} className="bg-canvas" />;
}

// ——— 2. Swarm: agents drift; edges form/dissolve within a proximity band
function SwarmBg({ motion = 0.35, swarmSize = 1.6, swarmCount = 1.0, swarmEdges = true }) {
  const agentsRef = useRef(null);
  const ref = useCanvas((ctx, w, h, t) => {
    const baseN = Math.floor((w * h) / 14000);
    const N = Math.max(20, Math.min(260, Math.floor(baseN * swarmCount)));
    if (!agentsRef.current || agentsRef.current.length !== N) {
      agentsRef.current = Array.from({ length: N }, () => ({
        x: Math.random() * w, y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.3,
        vy: (Math.random() - 0.5) * 0.3,
        phase: Math.random() * Math.PI * 2,
      }));
    }
    ctx.clearRect(0, 0, w, h);
    const speed = 0.5 + motion * 1.5;
    const ags = agentsRef.current;
    const gx = Math.cos(t * 0.08) * 0.2;
    const gy = Math.sin(t * 0.11) * 0.2;
    for (const a of ags) {
      a.vx += (gx - a.vx) * 0.004;
      a.vy += (gy - a.vy) * 0.004;
      a.vx += (Math.random() - 0.5) * 0.03;
      a.vy += (Math.random() - 0.5) * 0.03;
      a.x += a.vx * speed; a.y += a.vy * speed;
      if (a.x < -20) a.x = w + 20; if (a.x > w + 20) a.x = -20;
      if (a.y < -20) a.y = h + 20; if (a.y > h + 20) a.y = -20;
    }
    // Edges: form only within a proximity band — close enough to be neighbors,
    // far enough that they haven't "merged". Edges dissolve smoothly outside the band.
    if (swarmEdges) {
      const bandMin = 18 + swarmSize * 6;        // too close → no edge
      const bandMax = 92 + swarmSize * 10;       // too far → no edge
      const peak = (bandMin + bandMax) / 2;
      const halfWidth = (bandMax - bandMin) / 2;
      ctx.lineWidth = 0.8;
      for (let i = 0; i < ags.length; i++) {
        for (let j = i + 1; j < ags.length; j++) {
          const a = ags[i], b = ags[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx * dx + dy * dy;
          if (d2 > bandMax * bandMax || d2 < bandMin * bandMin) continue;
          const d = Math.sqrt(d2);
          // Triangular falloff peaking at the band center
          const strength = 1 - Math.abs(d - peak) / halfWidth;
          if (strength <= 0) continue;
          const alpha = strength * 0.22;
          ctx.strokeStyle = `rgba(0,0,0,${alpha})`;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
          ctx.stroke();
        }
      }
    }
    // Nodes drawn on top
    for (const a of ags) {
      const alpha = 0.25 + 0.30 * (0.5 + 0.5 * Math.sin(a.phase + t * 0.6));
      ctx.fillStyle = `rgba(0,0,0,${alpha})`;
      ctx.beginPath();
      ctx.arc(a.x, a.y, swarmSize, 0, Math.PI * 2);
      ctx.fill();
    }
  }, [motion, swarmSize, swarmCount, swarmEdges]);
  return <canvas ref={ref} className="bg-canvas" />;
}

// ——— 3. Grid: architectural ticks with subtle breathing
function GridBg({ motion = 0.35 }) {
  const ref = useCanvas((ctx, w, h, t) => {
    ctx.clearRect(0, 0, w, h);
    const major = 120, minor = 24;
    ctx.strokeStyle = 'rgba(0,0,0,0.05)';
    ctx.lineWidth = 1;
    for (let x = 0; x < w; x += minor) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); }
    for (let y = 0; y < h; y += minor) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); }
    ctx.strokeStyle = 'rgba(0,0,0,0.11)';
    for (let x = 0; x < w; x += major) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); }
    for (let y = 0; y < h; y += major) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); }
    // scanning ticks
    const amp = 0.4 + motion * 1.2;
    const scan = ((t * 8 * amp) % (w + 200)) - 100;
    const grad = ctx.createLinearGradient(scan - 120, 0, scan + 120, 0);
    grad.addColorStop(0, 'rgba(0,0,0,0)');
    grad.addColorStop(0.5, 'rgba(0,0,0,0.08)');
    grad.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = grad;
    ctx.fillRect(scan - 120, 0, 240, h);
  }, [motion]);
  return <canvas ref={ref} className="bg-canvas" />;
}

// ——— 4. Nodes: sparse graph that forms and dissolves
function NodesBg({ motion = 0.35 }) {
  const nodesRef = useRef(null);
  const ref = useCanvas((ctx, w, h, t) => {
    if (!nodesRef.current) {
      const N = Math.max(18, Math.min(42, Math.floor((w * h) / 30000)));
      nodesRef.current = Array.from({ length: N }, () => ({
        x: Math.random() * w, y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.12,
        vy: (Math.random() - 0.5) * 0.12,
      }));
    }
    ctx.clearRect(0, 0, w, h);
    const speed = 0.3 + motion * 1.0;
    const nodes = nodesRef.current;
    for (const n of nodes) {
      n.x += n.vx * speed; n.y += n.vy * speed;
      if (n.x < 0 || n.x > w) n.vx *= -1;
      if (n.y < 0 || n.y > h) n.vy *= -1;
    }
    // edges
    const maxD = Math.min(w, h) * 0.22;
    ctx.lineWidth = 1;
    for (let i = 0; i < nodes.length; i++) {
      for (let j = i + 1; j < nodes.length; j++) {
        const a = nodes[i], b = nodes[j];
        const dx = a.x - b.x, dy = a.y - b.y;
        const d = Math.hypot(dx, dy);
        if (d < maxD) {
          const alpha = (1 - d / maxD) * 0.15;
          ctx.strokeStyle = `rgba(0,0,0,${alpha})`;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
          ctx.stroke();
        }
      }
    }
    for (const n of nodes) {
      ctx.fillStyle = 'rgba(0,0,0,0.55)';
      ctx.beginPath(); ctx.arc(n.x, n.y, 1.6, 0, Math.PI * 2); ctx.fill();
    }
  }, [motion]);
  useEffect(() => { nodesRef.current = null; }, [motion]);
  return <canvas ref={ref} className="bg-canvas" />;
}

// ——— 5. Life: strict Conway B3/S23 under the hood, organic render on top.
// - cells are binary for the rule (so gliders/blinkers/still-lifes actually emerge)
// - each cell holds a continuous "energy" that eases toward its binary target,
//   so transitions fade rather than pop
// - wraparound edges, seeded density low enough that structure can form
function LifeBg({ motion = 0.35, lifeCell = 14, lifeOpacity = 0.55, lifeDensity = 0.22 }) {
  const stateRef = useRef(null);
  const ref = useCanvas((ctx, w, h, t) => {
    const cell = Math.max(6, lifeCell);
    const cols = Math.max(2, Math.ceil(w / cell) + 2);
    const rows = Math.max(2, Math.ceil(h / cell) + 2);

    if (!stateRef.current || stateRef.current.cols !== cols || stateRef.current.rows !== rows) {
      const bin = new Uint8Array(cols * rows);
      const energy = new Float32Array(cols * rows);
      for (let i = 0; i < bin.length; i++) {
        bin[i] = Math.random() < lifeDensity ? 1 : 0;
        energy[i] = bin[i];
      }
      const prev = Uint8Array.from(bin);
      stateRef.current = { bin, prev, energy, cols, rows, lastStep: t, gen: 0, sinceReseed: 0 };
    }
    const st = stateRef.current;
    // motion 0..1 → step every 1.4s..0.25s  (slower overall so smooth eases breathe)
    const stepEvery = Math.max(0.25, 1.4 - motion * 1.15);

    if (t - st.lastStep >= stepEvery) {
      st.lastStep = t;
      st.gen += 1;
      const { bin, cols: C, rows: R } = st;
      // snapshot previous state as the "from" for smooth interpolation
      st.prev = Uint8Array.from(bin);
      const next = new Uint8Array(C * R);
      // Strict B3/S23 (toroidal)
      for (let y = 0; y < R; y++) {
        const ym = (y - 1 + R) % R;
        const yp = (y + 1) % R;
        for (let x = 0; x < C; x++) {
          const xm = (x - 1 + C) % C;
          const xp = (x + 1) % C;
          const n =
            bin[ym * C + xm] + bin[ym * C + x] + bin[ym * C + xp] +
            bin[y  * C + xm] +                     bin[y  * C + xp] +
            bin[yp * C + xm] + bin[yp * C + x] + bin[yp * C + xp];
          const alive = bin[y * C + x];
          next[y * C + x] = (alive ? (n === 2 || n === 3) : (n === 3)) ? 1 : 0;
        }
      }
      // Anti-stagnation: if the population has barely changed for many gens,
      // inject a small patch of randomness elsewhere so emergence continues.
      let pop = 0;
      for (let i = 0; i < next.length; i++) pop += next[i];
      st.sinceReseed = (st._lastPop !== undefined && Math.abs(pop - st._lastPop) < 2)
        ? st.sinceReseed + 1 : 0;
      st._lastPop = pop;
      if (st.sinceReseed > 30 || pop < cols * rows * 0.02) {
        // seed a small random patch
        const cx = (Math.random() * C) | 0;
        const cy = (Math.random() * R) | 0;
        const rad = 6;
        for (let dy = -rad; dy <= rad; dy++) {
          for (let dx = -rad; dx <= rad; dx++) {
            const nx = (cx + dx + C) % C;
            const ny = (cy + dy + R) % R;
            if (Math.random() < 0.45) next[ny * C + nx] = 1;
          }
        }
        st.sinceReseed = 0;
      }
      st.bin = next;
    }

    // Smooth interpolation across the full step interval:
    // phase 0 → prev state, phase 1 → current state. Linear for a steady,
    // continuous grow/shrink with no acceleration curve.
    const phase = Math.min(1, (t - st.lastStep) / stepEvery);
    const ease = phase;
    const { bin, prev, energy, cols: C, rows: R } = st;
    if (prev) {
      for (let i = 0; i < bin.length; i++) {
        energy[i] = prev[i] * (1 - ease) + bin[i] * ease;
      }
    } else {
      for (let i = 0; i < bin.length; i++) energy[i] = bin[i];
    }

    ctx.clearRect(0, 0, w, h);
    for (let y = 0; y < R; y++) {
      for (let x = 0; x < C; x++) {
        const v = energy[y * C + x];
        if (v < 0.02) continue;
        const px = x * cell;
        const py = y * cell;
        // linear: radius and alpha track v directly
        const alpha = Math.min(1, v * lifeOpacity);
        const r = cell * 0.44 * v;
        ctx.fillStyle = `rgba(0,0,0,${alpha})`;
        ctx.beginPath();
        ctx.arc(px + cell / 2, py + cell / 2, r, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }, [motion, lifeCell, lifeOpacity, lifeDensity]);
  useEffect(() => { if (stateRef.current) stateRef.current = null; }, [lifeCell, lifeDensity]);
  return <canvas ref={ref} className="bg-canvas" />;
}

function Background({ system = 'field', motion = 0.35, dark = false, swarmSize = 1.6, swarmCount = 1.0, swarmEdges = true, lifeCell = 14, lifeOpacity = 0.55, lifeDensity = 0.22 }) {
  const style = dark ? { filter: 'invert(1) hue-rotate(180deg)', opacity: 0.75 } : { opacity: 1 };
  let el;
  if (system === 'swarm') el = <SwarmBg motion={motion} swarmSize={swarmSize} swarmCount={swarmCount} swarmEdges={swarmEdges} />;
  else if (system === 'grid')  el = <GridBg motion={motion} />;
  else if (system === 'nodes') el = <NodesBg motion={motion} />;
  else if (system === 'life')  el = <LifeBg motion={motion} lifeCell={lifeCell} lifeOpacity={lifeOpacity} lifeDensity={lifeDensity} />;
  else el = <FieldBg motion={motion} />;
  return <div className="bg-layer" style={style}>{el}</div>;
}

Object.assign(window, { Background });
