/* ========================================================================
   LUMINA — Visualizers batch: SIGNAL LAB
   Six premium audio-reactive effects. Appends to window.VISUALIZERS.
   draw(ctx, w, h, A, P, t):
     A = { bass, mid, high, energy, beat, freq[Uint8], wave[Uint8] }
     P = { hue, intensity, motion, glow, density, bgAlpha, macro2, ... }
   ======================================================================== */
(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. CYMATICS — Chladni standing-wave plate ============ */
  const Cymatics = {
    id: "cymatics", name: "Cymatics", tag: "CHLADNI",
    icon: ic(<><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 12h18M12 3v18M6 6l12 12M18 6L6 18"/></>),
    draw(ctx, w, h, A, P, t) {
      fade(ctx, w, h, 0.5);
      const res = 5;
      // mode numbers driven by spectrum bands
      const n = 2 + Math.floor(A.bass * 6 * (P.intensity || 1)) + Math.sin(t * 0.2) * 1.5;
      const m = 2 + Math.floor(A.high * 6 * (P.intensity || 1)) + Math.cos(t * 0.17) * 1.5;
      const thr = 0.04 + (1 - (P.density || 0.6)) * 0.06;
      const hue = P.hue + t * 20;
      ctx.shadowBlur = (P.glow || 0.5) * 10;
      ctx.shadowColor = hsla(hue + 40, 100, 60);
      for (let y = 0; y < h; y += res) {
        const ny = y / h;
        for (let x = 0; x < w; x += res) {
          const nx = x / w;
          const v = Math.sin(n * Math.PI * nx) * Math.sin(m * Math.PI * ny) +
                    Math.sin(m * Math.PI * nx) * Math.sin(n * Math.PI * ny);
          if (Math.abs(v) < thr) {
            const sh = (hue + Math.abs(v) * 600) % 360;
            ctx.fillStyle = hsla(sh, 100, 55 + A.energy * 30, 0.9);
            ctx.fillRect(x, y, res, res);
          }
        }
      }
      ctx.shadowBlur = 0;
    },
  };

  /* ============ 2. SPECTROGRAM — scrolling time/frequency waterfall ============ */
  const Spectrogram = {
    id: "spectrogram", name: "Spectrogram", tag: "WATERFALL",
    icon: ic(<><path d="M3 20V8M7 20V4M11 20v-9M15 20V6M19 20v-7"/><path d="M3 20h18"/></>),
    draw(ctx, w, h, A, P, t) {
      // scroll the existing image up by 2px, draw new row at bottom
      const sp = 2;
      ctx.drawImage(ctx.canvas, 0, sp, w, h - sp, 0, 0, w, h - sp);
      const freq = A.freq, N = freq.length;
      const rowY = h - sp;
      for (let x = 0; x < w; x++) {
        // log-ish mapping so bass gets more room
        const fi = Math.floor(Math.pow(x / w, 1.7) * (N * 0.7));
        const v = freq[fi] / 255;
        const hue = (P.hue + 250 - v * 260) % 360;
        ctx.fillStyle = v < 0.02 ? "rgba(8,8,12,1)" : hsla(hue, 100, 12 + v * 55, 1);
        ctx.fillRect(x, rowY, 1, sp);
      }
      // playhead grid line glow on beat
      if (A.beat > 0.4) { ctx.fillStyle = `rgba(255,255,255,${A.beat * 0.15})`; ctx.fillRect(0, rowY, w, sp); }
    },
  };

  /* ============ 3. PLASMA — demoscene sinusoidal field ============ */
  const Plasma = {
    id: "plasma2", name: "Plasma Field", tag: "DEMOSCENE",
    icon: ic(<><path d="M3 8c4-4 6 4 9 0s5 4 9 0M3 16c4-4 6 4 9 0s5 4 9 0"/></>),
    draw(ctx, w, h, A, P, t) {
      const step = 6;
      const sc = 0.012 / (0.6 + (P.density || 0.6));
      const k = (P.motion || 1);
      const amp = 1 + A.bass * 2 * (P.intensity || 1);
      const huebase = P.hue + t * 30 + A.energy * 80;
      for (let y = 0; y < h; y += step) {
        for (let x = 0; x < w; x += step) {
          const v = Math.sin(x * sc + t * k) +
                    Math.sin(y * sc * 1.3 - t * k * 0.7) +
                    Math.sin((x + y) * sc * 0.7 + t * k * 0.5) +
                    Math.sin(Math.hypot(x - w/2, y - h/2) * sc * amp - t * k);
          const hue = huebase + v * 60;
          ctx.fillStyle = hsla(hue, 95, 52, 1);
          ctx.fillRect(x, y, step, step);
        }
      }
    },
  };

  /* ============ 4. WARP STARS — hyperspace jump ============ */
  const WarpStars = {
    id: "warpstars", name: "Warp Speed", tag: "HYPERJUMP",
    icon: ic(<><path d="M12 12L4 6M12 12l8-6M12 12l-7 7M12 12l8 6M12 12v-9"/></>),
    draw(ctx, w, h, A, P, t) {
      fade(ctx, w, h, 0.35);
      const cx = w / 2, cy = h / 2;
      if (!WarpStars._s) {
        WarpStars._s = Array.from({ length: 700 }, () => ({
          a: Math.random() * TAU, r: Math.random() * Math.hypot(w, h) * 0.5, sp: 0.5 + Math.random() * 2,
        }));
      }
      const boost = 1 + A.bass * 8 * (P.intensity || 1) + (A.beat > 0.5 ? 6 : 0);
      const stars = WarpStars._s;
      ctx.lineCap = "round";
      for (const s of stars) {
        const r0 = s.r;
        s.r += s.sp * boost * (P.motion || 1);
        const maxR = Math.hypot(w, h) * 0.55;
        if (s.r > maxR) { s.r = 0; s.a = Math.random() * TAU; }
        const x0 = cx + Math.cos(s.a) * r0, y0 = cy + Math.sin(s.a) * r0;
        const x1 = cx + Math.cos(s.a) * s.r, y1 = cy + Math.sin(s.a) * s.r;
        const depth = s.r / maxR;
        const hue = (P.hue + depth * 120 + t * 30) % 360;
        ctx.strokeStyle = hsla(hue, 100, 60 + depth * 30, 0.3 + depth * 0.7);
        ctx.lineWidth = 0.5 + depth * 2.5 * boost * 0.2;
        ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke();
      }
    },
  };

  /* ============ 5. INK FLUID — advected dye swirl ============ */
  const InkFluid = {
    id: "inkfluid", name: "Ink Fluid", tag: "ADVECT",
    icon: ic(<><path d="M12 3c4 4 6 7 6 11a6 6 0 11-12 0c0-4 2-7 6-11z"/></>),
    draw(ctx, w, h, A, P, t) {
      fade(ctx, w, h, 0.06);
      if (!InkFluid._p) InkFluid._p = [];
      const ps = InkFluid._p;
      // emit
      const emit = Math.floor(2 + A.energy * 14 * (P.density || 0.6));
      for (let i = 0; i < emit; i++) {
        const a = Math.random() * TAU;
        ps.push({
          x: w/2 + Math.cos(a) * 20, y: h/2 + Math.sin(a) * 20,
          vx: Math.cos(a) * (1 + A.bass * 6), vy: Math.sin(a) * (1 + A.bass * 6),
          life: 1, hue: (P.hue + Math.random() * 80 + t * 20),
          sz: 6 + Math.random() * 18 * (P.intensity || 1),
        });
      }
      if (ps.length > 1400) ps.splice(0, ps.length - 1400);
      ctx.globalCompositeOperation = "lighter";
      for (let i = ps.length - 1; i >= 0; i--) {
        const p = ps[i];
        // curl-noise-ish swirl
        const ang = Math.sin(p.x * 0.006 + t * 0.5) * Math.cos(p.y * 0.006 - t * 0.4) * 3;
        p.vx += Math.cos(ang) * 0.4 * (P.motion || 1);
        p.vy += Math.sin(ang) * 0.4 * (P.motion || 1);
        p.vx *= 0.94; p.vy *= 0.94;
        p.x += p.vx; p.y += p.vy; p.life -= 0.012;
        if (p.life <= 0) { ps.splice(i, 1); continue; }
        const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.sz);
        g.addColorStop(0, hsla(p.hue, 100, 60, p.life * 0.5));
        g.addColorStop(1, hsla(p.hue, 100, 50, 0));
        ctx.fillStyle = g;
        ctx.beginPath(); ctx.arc(p.x, p.y, p.sz, 0, TAU); ctx.fill();
      }
      ctx.globalCompositeOperation = "source-over";
    },
  };

  /* ============ 6. AUDIO ORB — rotating reactive point sphere ============ */
  const AudioOrb = {
    id: "audioorb", name: "Audio Orb", tag: "3D · POINTS",
    icon: ic(<><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="9" ry="3.5"/><ellipse cx="12" cy="12" rx="3.5" ry="9"/></>),
    draw(ctx, w, h, A, P, t) {
      fade(ctx, w, h, 0.3);
      const cx = w/2, cy = h/2;
      const R = Math.min(w, h) * 0.32;
      const N = 1100;
      const rot = t * 0.4 * (P.motion || 1);
      const bassPush = 1 + A.bass * 0.5 * (P.intensity || 1);
      ctx.shadowBlur = (P.glow || 0.5) * 8;
      for (let i = 0; i < N; i++) {
        // fibonacci sphere
        const y = 1 - (i / (N - 1)) * 2;
        const rr = Math.sqrt(1 - y * y);
        const phi = i * 2.399963;
        let px = Math.cos(phi) * rr, py = y, pz = Math.sin(phi) * rr;
        // displace by spectrum
        const fi = Math.floor((i / N) * A.freq.length * 0.6);
        const disp = 1 + (A.freq[fi] / 255) * 0.6 * (P.intensity || 1);
        const rad = R * bassPush * disp;
        // rotate around Y then X
        let X = px * Math.cos(rot) - pz * Math.sin(rot);
        let Z = px * Math.sin(rot) + pz * Math.cos(rot);
        let Y = py * Math.cos(rot * 0.5) - Z * Math.sin(rot * 0.5);
        Z = py * Math.sin(rot * 0.5) + Z * Math.cos(rot * 0.5);
        const persp = 320 / (320 + Z * rad);
        const sx = cx + X * rad * persp;
        const sy = cy + Y * rad * persp;
        const depth = (Z + 1) / 2;
        const hue = (P.hue + (A.freq[fi] / 255) * 200 + t * 30) % 360;
        ctx.fillStyle = hsla(hue, 100, 50 + depth * 30, 0.4 + depth * 0.6);
        ctx.shadowColor = hsla(hue, 100, 60);
        const sz = (0.6 + depth * 2.2) * persp;
        ctx.fillRect(sx, sy, sz, sz);
      }
      ctx.shadowBlur = 0;
    },
  };

  window.VISUALIZERS.push(Cymatics, Spectrogram, Plasma, WarpStars, InkFluid, AudioOrb);
})();
