/* ========================================================================
   LUMINA — Visualizers batch: ARENA SOURCES
   Resolume-style built-in generators & reactive sources.
   Appends to window.VISUALIZERS.
   ======================================================================== */
(function () {
  const TAU = Math.PI * 2;
  const hsla = (h, s = 80, l = 60, a = 1) => `hsla(${((h % 360) + 360) % 360}, ${s}%, ${l}%, ${a})`;
  const fade = (ctx, w, h, a) => { ctx.fillStyle = `rgba(8,8,12,${a})`; ctx.fillRect(0, 0, w, h); };
  const ic = (d) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">{d}</svg>;

  /* ============ 1. FIRE — rising flame field (Resolume "Fire") ============ */
  const Fire = {
    id: "fire", name: "Fire", tag: "GENERATOR",
    icon: ic(<path d="M12 3c2 3 4 4 4 8a4 4 0 11-8 0c0-2 1-3 2-4 0 1 1 2 2 2 0-2 0-4-2-6z"/>),
    draw(ctx, w, h, A, P, t) {
      const cols = 90, rows = 56;
      if (!Fire._g || Fire._g.length !== cols * rows) {
        Fire._g = new Float32Array(cols * rows);
      }
      const g = Fire._g;
      // seed bottom row from spectrum
      for (let x = 0; x < cols; x++) {
        const fi = Math.floor((x / cols) * A.freq.length * 0.5);
        const base = 0.3 + (A.freq[fi] / 255) * 1.6 * (P.intensity || 1) + A.bass * 0.6;
        g[(rows - 1) * cols + x] = Math.min(1.4, base * (0.6 + Math.random() * 0.7));
      }
      // propagate upward with cooling
      const cool = 0.012 + (1 - (P.density || 0.6)) * 0.02;
      for (let y = 0; y < rows - 1; y++) {
        for (let x = 0; x < cols; x++) {
          const below = g[(y + 1) * cols + x];
          const bl = g[(y + 1) * cols + ((x - 1 + cols) % cols)];
          const br = g[(y + 1) * cols + ((x + 1) % cols)];
          g[y * cols + x] = Math.max(0, (below * 0.5 + bl * 0.25 + br * 0.25) - cool);
        }
      }
      const cw = w / cols, ch = h / rows;
      fade(ctx, w, h, 1);
      for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
          const v = g[y * cols + x];
          if (v < 0.04) continue;
          // fire palette: red->orange->yellow->white, hue param shifts whole palette
          const hue = (P.hue - 20) + v * 60;
          const lig = Math.min(95, 20 + v * 70);
          ctx.fillStyle = hsla(hue, 100, lig, Math.min(1, v));
          ctx.fillRect(x * cw, y * ch, cw + 1, ch + 1);
        }
      }
    },
  };

  /* ============ 2. CELLS — Resolume "Cells" voronoi shards ============ */
  const Cells = {
    id: "cells", name: "Cells", tag: "GENERATOR",
    icon: ic(<><path d="M3 9l5-4 6 2 5 5-2 6-6 3-6-3-2-6z"/><path d="M8 5l2 7-3 5M14 7l-4 5 5 4M19 12l-9 0"/></>),
    draw(ctx, w, h, A, P, t) {
      const N = Math.max(8, Math.floor(8 + (P.density || 0.6) * 26));
      if (!Cells._p || Cells._p.length !== N) {
        Cells._p = Array.from({ length: N }, (_, i) => ({
          x: Math.random(), y: Math.random(),
          a: Math.random() * TAU, sp: 0.1 + Math.random() * 0.3, band: i,
        }));
      }
      const ps = Cells._p;
      const mo = (P.motion || 1);
      for (const p of ps) {
        p.x += Math.cos(p.a) * 0.0008 * p.sp * mo * (1 + A.energy * 2);
        p.y += Math.sin(p.a) * 0.0008 * p.sp * mo * (1 + A.energy * 2);
        if (p.x < 0 || p.x > 1) p.a = Math.PI - p.a;
        if (p.y < 0 || p.y > 1) p.a = -p.a;
        p.x = Math.max(0, Math.min(1, p.x)); p.y = Math.max(0, Math.min(1, p.y));
      }
      const step = 7;
      for (let y = 0; y < h; y += step) {
        for (let x = 0; x < w; x += step) {
          let best = 1e9, second = 1e9, bi = 0;
          for (let i = 0; i < ps.length; i++) {
            const dx = ps[i].x * w - x, dy = ps[i].y * h - y;
            const d = dx * dx + dy * dy;
            if (d < best) { second = best; best = d; bi = i; }
            else if (d < second) second = d;
          }
          const edge = Math.sqrt(second) - Math.sqrt(best); // distance to cell border
          const fi = Math.floor((bi / ps.length) * A.freq.length * 0.6);
          const v = A.freq[fi] / 255;
          if (edge < 2.2) {
            ctx.fillStyle = hsla(P.hue + 40 + t * 20, 100, 70, 0.9); // bright border
          } else {
            ctx.fillStyle = hsla(P.hue + bi * 12 + t * 15, 85, 12 + v * 55 * (P.intensity || 1), 1);
          }
          ctx.fillRect(x, y, step, step);
        }
      }
    },
  };

  /* ============ 3. VU BARS — broadcast stereo VU meters ============ */
  const VuBars = {
    id: "vubars", name: "VU Meters", tag: "BROADCAST",
    icon: ic(<><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 17V9M12 17V7M17 17v-6"/></>),
    draw(ctx, w, h, A, P, t) {
      fade(ctx, w, h, 1);
      const bands = 24;
      const gap = 6;
      const bw = (w - gap * (bands + 1)) / bands;
      if (!VuBars._peak) VuBars._peak = new Float32Array(bands);
      const peak = VuBars._peak;
      const N = A.freq.length;
      for (let i = 0; i < bands; i++) {
        const fi = Math.floor(Math.pow(i / bands, 1.5) * N * 0.7);
        let v = A.freq[fi] / 255;
        v = Math.pow(v, 0.8) * (P.intensity || 1);
        peak[i] = Math.max(peak[i] - 0.012, v);
        const x = gap + i * (bw + gap);
        const bh = v * h * 0.92;
        // segmented LED meter
        const segH = 9, segGap = 3;
        const segs = Math.floor(h * 0.92 / (segH + segGap));
        const lit = Math.floor((bh / (h * 0.92)) * segs);
        for (let s = 0; s < segs; s++) {
          const sy = h - gap - s * (segH + segGap) - segH;
          const frac = s / segs;
          const on = s <= lit;
          // green -> amber -> red zones
          const hue = frac < 0.6 ? 130 : frac < 0.82 ? 45 : 0;
          ctx.fillStyle = on
            ? hsla(hue + (P.hue - 90), 100, 55, 1)
            : `rgba(255,255,255,0.04)`;
          if (on) { ctx.shadowBlur = (P.glow || 0.5) * 6; ctx.shadowColor = hsla(hue + (P.hue - 90), 100, 55); }
          ctx.fillRect(x, sy, bw, segH);
          ctx.shadowBlur = 0;
        }
        // peak hold cap
        const py = h - gap - peak[i] * h * 0.92;
        ctx.fillStyle = hsla(P.hue + 10, 100, 80, 0.9);
        ctx.fillRect(x, py - 3, bw, 3);
      }
    },
  };

  /* ============ 4. TEST PATTERN — SMPTE-ish calibration bars ============ */
  const TestPattern = {
    id: "testpattern", name: "Test Pattern", tag: "CALIBRATE",
    icon: ic(<><rect x="3" y="3" width="18" height="18" rx="1"/><path d="M7 3v18M11 3v18M15 3v18M19 3v18"/></>),
    draw(ctx, w, h, A, P, t) {
      // top color bars
      const cols = ["#c0c0c0", "#c0c000", "#00c0c0", "#00c000", "#c000c0", "#c00000", "#0000c0"];
      const bw = w / cols.length;
      const topH = h * 0.66;
      for (let i = 0; i < cols.length; i++) {
        ctx.fillStyle = cols[i];
        ctx.fillRect(i * bw, 0, bw + 1, topH);
      }
      // reactive middle strip (reverse bars, pulse on beat)
      const midH = h * 0.1;
      const flash = A.beat > 0.4;
      for (let i = 0; i < cols.length; i++) {
        ctx.fillStyle = flash ? hsla(P.hue + i * 40 + t * 60, 100, 60) : cols[cols.length - 1 - i];
        ctx.fillRect(i * bw, topH, bw + 1, midH);
      }
      // bottom: gradient + moving sync pip + spectrum trace
      const by = topH + midH, bh = h - by;
      const grad = ctx.createLinearGradient(0, 0, w, 0);
      grad.addColorStop(0, "#000"); grad.addColorStop(1, "#fff");
      ctx.fillStyle = grad; ctx.fillRect(0, by, w, bh);
      // moving pip synced to time / bass
      const pipX = ((t * 0.2 + A.bass) % 1) * w;
      ctx.fillStyle = hsla(P.hue, 100, 60); ctx.fillRect(pipX - 3, by, 6, bh);
      // spectrum trace overlay
      ctx.strokeStyle = hsla(P.hue + 120, 100, 70, 0.9);
      ctx.lineWidth = 2; ctx.beginPath();
      for (let x = 0; x < w; x++) {
        const fi = Math.floor((x / w) * A.freq.length * 0.7);
        const v = A.freq[fi] / 255;
        const y = by + bh - v * bh * (P.intensity || 1);
        x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
      }
      ctx.stroke();
    },
  };

  window.VISUALIZERS.push(Fire, Cells, VuBars, TestPattern);
})();
