/* ========================================================================
   LUMINA — Media visualizers (Image + Video as audio-reactive layers)
   Appends to window.VISUALIZERS.
   ======================================================================== */
(function () {
  const TAU = Math.PI * 2;
  const hsla = (h, s = 80, l = 60, a = 1) => `hsla(${h}, ${s}%, ${l}%, ${a})`;

  function bg(ctx, w, h, P) {
    ctx.fillStyle = `rgba(8,8,12,${P.bgAlpha ?? 0.18})`;
    ctx.fillRect(0, 0, w, h);
  }

  /* Compute draw rect that contains src (W,H) in (w,h) respecting "contain". */
  function fit(src, w, h, mode = "contain") {
    const sr = src.w / src.h;
    const tr = w / h;
    let dw, dh;
    if (mode === "cover" ? sr > tr : sr < tr) {
      dh = h; dw = h * sr;
    } else {
      dw = w; dh = w / sr;
    }
    return { x: (w - dw) / 2, y: (h - dh) / 2, w: dw, h: dh };
  }

  /* ============ IMAGE FRAME (animates ornaments around an image) ============ */
  const ImageFrame = {
    id: "imageFrame",
    name: "Image · Frame FX",
    tag: "PNG/JPG",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <rect x="3" y="3" width="18" height="18" rx="2"/>
      <circle cx="9" cy="9" r="2"/>
      <path d="M21 15l-5-5L5 21"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const layer = window.MediaStore.layers.find(l => l.id === window.MediaStore.active && l.kind === "image");
      if (!layer || !layer.el) {
        // placeholder
        ctx.fillStyle = "rgba(255,174,58,0.05)";
        ctx.fillRect(w*0.2, h*0.2, w*0.6, h*0.6);
        ctx.strokeStyle = "rgba(255,174,58,0.4)"; ctx.lineWidth = 2; ctx.setLineDash([4,4]);
        ctx.strokeRect(w*0.2, h*0.2, w*0.6, h*0.6);
        ctx.setLineDash([]);
        ctx.fillStyle = "rgba(255,174,58,0.7)";
        ctx.font = '600 14px "Space Grotesk", sans-serif';
        ctx.textAlign = "center"; ctx.textBaseline = "middle";
        ctx.fillText("DROP PNG / JPG HERE OR USE LAYERS PANEL", w/2, h/2);
        return;
      }
      // pulsing scale
      const pulse = 1 + A.bass * 0.12 * P.intensity;
      const inner = fit(layer, w * 0.78, h * 0.78);
      const cx = w/2, cy = h/2;
      // shadow / glow under image
      ctx.save();
      ctx.translate(cx, cy);
      ctx.scale(pulse, pulse);
      ctx.translate(-w/2, -h/2);
      // halo behind
      ctx.save();
      ctx.shadowColor = hsla(P.hue, 90, 60, 0.7);
      ctx.shadowBlur = 30 + 80 * P.glow * (0.4 + A.energy);
      ctx.fillStyle = "#000";
      ctx.fillRect(inner.x, inner.y, inner.w, inner.h);
      ctx.restore();

      // the image itself
      ctx.save();
      // bass-driven hue rotate filter
      if ((P.macro1 ?? 0) > 0.01)
        ctx.filter = `hue-rotate(${P.hue + A.bass * 120 * P.macro1}deg) saturate(${1 + A.high * P.intensity})`;
      ctx.drawImage(layer.el, inner.x, inner.y, inner.w, inner.h);
      ctx.filter = "none";
      ctx.restore();
      ctx.restore();

      // animated frame ornaments
      const dpts = Math.max(40, Math.floor(P.density * 160));
      const ring = [];
      // build path around image rect (clockwise starting top-left)
      const r0 = { x: cx - (inner.w*pulse)/2, y: cy - (inner.h*pulse)/2, w: inner.w*pulse, h: inner.h*pulse };
      const peri = 2 * (r0.w + r0.h);
      for (let i = 0; i < dpts; i++) {
        const u = (i / dpts + t * 0.05 * P.motion) % 1;
        const d = u * peri;
        let px, py;
        if (d < r0.w) { px = r0.x + d; py = r0.y; }
        else if (d < r0.w + r0.h) { px = r0.x + r0.w; py = r0.y + (d - r0.w); }
        else if (d < 2*r0.w + r0.h) { px = r0.x + r0.w - (d - r0.w - r0.h); py = r0.y + r0.h; }
        else { px = r0.x; py = r0.y + r0.h - (d - 2*r0.w - r0.h); }
        ring.push({ x: px, y: py, u });
      }
      // spokes
      ctx.shadowBlur = P.glow * 18;
      for (let i = 0; i < ring.length; i++) {
        const fi = Math.floor((i / ring.length) * (A.freq.length * 0.55));
        const v = A.freq[fi] / 255;
        const len = 6 + v * 60 * P.intensity;
        // normal direction = outward from center
        const nx = ring[i].x - cx, ny = ring[i].y - cy;
        const nl = Math.hypot(nx, ny) || 1;
        const ox = nx / nl, oy = ny / nl;
        const hue = (P.hue + i * 6 + t * 30) % 360;
        ctx.strokeStyle = hsla(hue, 90, 60, 0.95);
        ctx.shadowColor = hsla(hue, 90, 60);
        ctx.lineWidth = P.lineWidth;
        ctx.beginPath();
        ctx.moveTo(ring[i].x, ring[i].y);
        ctx.lineTo(ring[i].x + ox * len, ring[i].y + oy * len);
        ctx.stroke();
      }
      // corner brackets
      ctx.shadowBlur = 0;
      ctx.strokeStyle = hsla(P.hue, 90, 70, 1);
      ctx.lineWidth = 2;
      const cl = 26;
      const cs = (x, y, sx, sy) => {
        ctx.beginPath();
        ctx.moveTo(x + sx * cl, y); ctx.lineTo(x, y); ctx.lineTo(x, y + sy * cl);
        ctx.stroke();
      };
      cs(r0.x - 8, r0.y - 8, 1, 1);
      cs(r0.x + r0.w + 8, r0.y - 8, -1, 1);
      cs(r0.x - 8, r0.y + r0.h + 8, 1, -1);
      cs(r0.x + r0.w + 8, r0.y + r0.h + 8, -1, -1);

      // tick marks across top/bottom
      ctx.shadowColor = hsla(P.hue, 90, 60);
      ctx.shadowBlur = P.glow * 10;
      for (let i = 0; i < 12; i++) {
        const x = r0.x + r0.w * (i / 11);
        const v = A.freq[Math.floor((i / 11) * A.freq.length * 0.4)] / 255;
        ctx.strokeStyle = hsla((P.hue + i * 12) % 360, 90, 60, 0.6 + v * 0.4);
        ctx.lineWidth = 1.5;
        ctx.beginPath();
        ctx.moveTo(x, r0.y - 16); ctx.lineTo(x, r0.y - 16 - v * 28 * P.intensity);
        ctx.moveTo(x, r0.y + r0.h + 16); ctx.lineTo(x, r0.y + r0.h + 16 + v * 28 * P.intensity);
        ctx.stroke();
      }
    },
  };

  /* ============ IMAGE BURST (image + radial particles from edges) ============ */
  const imgParticles = [];
  const ImageBurst = {
    id: "imageBurst",
    name: "Image · Particle Burst",
    tag: "BEAT EMIT",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <rect x="6" y="6" width="12" height="12" rx="1.5"/>
      <circle cx="3" cy="3" r="1"/><circle cx="21" cy="3" r="1"/>
      <circle cx="3" cy="21" r="1"/><circle cx="21" cy="21" r="1"/>
      <path d="M6 6 L3 3 M18 6 L21 3 M6 18 L3 21 M18 18 L21 21"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const layer = window.MediaStore.layers.find(l => l.id === window.MediaStore.active && l.kind === "image");
      const cx = w/2, cy = h/2;
      const baseRect = layer ? fit(layer, w * 0.7, h * 0.7) : { x: w*0.25, y: h*0.25, w: w*0.5, h: h*0.5 };
      // emit on beat
      if (A.beat > 0.6 && imgParticles.length < 600) {
        const N = Math.floor(20 + P.density * 60);
        for (let i = 0; i < N; i++) {
          // pick a point on rect perimeter
          const u = Math.random();
          let px, py, nx, ny;
          const peri = 2 * (baseRect.w + baseRect.h);
          const d = u * peri;
          if (d < baseRect.w) { px = baseRect.x + d; py = baseRect.y; nx = 0; ny = -1; }
          else if (d < baseRect.w + baseRect.h) { px = baseRect.x + baseRect.w; py = baseRect.y + (d - baseRect.w); nx = 1; ny = 0; }
          else if (d < 2*baseRect.w + baseRect.h) { px = baseRect.x + baseRect.w - (d - baseRect.w - baseRect.h); py = baseRect.y + baseRect.h; nx = 0; ny = 1; }
          else { px = baseRect.x; py = baseRect.y + baseRect.h - (d - 2*baseRect.w - baseRect.h); nx = -1; ny = 0; }
          imgParticles.push({
            x: px, y: py,
            vx: nx * (2 + Math.random()*4) * P.intensity + (Math.random()-0.5),
            vy: ny * (2 + Math.random()*4) * P.intensity + (Math.random()-0.5),
            life: 1, size: 1 + Math.random()*3,
            hue: (P.hue + Math.random()*40) % 360,
          });
        }
      }
      // draw image
      if (layer) {
        ctx.save();
        ctx.shadowColor = hsla(P.hue, 90, 60, 0.6);
        ctx.shadowBlur = P.glow * 30;
        ctx.drawImage(layer.el, baseRect.x, baseRect.y, baseRect.w, baseRect.h);
        ctx.restore();
      } else {
        ctx.strokeStyle = hsla(P.hue, 90, 60, 0.6);
        ctx.setLineDash([4, 4]); ctx.strokeRect(baseRect.x, baseRect.y, baseRect.w, baseRect.h);
        ctx.setLineDash([]);
      }
      // update + draw particles
      ctx.save(); ctx.globalCompositeOperation = "lighter";
      for (let i = imgParticles.length - 1; i >= 0; i--) {
        const p = imgParticles[i];
        p.x += p.vx; p.y += p.vy;
        p.vx *= 0.985; p.vy *= 0.985;
        p.vy += 0.05;
        p.life -= 0.012;
        if (p.life <= 0) { imgParticles.splice(i, 1); continue; }
        const a = p.life;
        ctx.fillStyle = hsla(p.hue, 90, 60, a);
        ctx.shadowBlur = P.glow * 10;
        ctx.shadowColor = hsla(p.hue, 90, 60);
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size * (0.5 + A.high * P.intensity), 0, TAU);
        ctx.fill();
      }
      ctx.restore();
    },
  };

  /* ============ VIDEO · TouchDesigner-style channel ============ */
  const VideoTD = {
    id: "videoTD",
    name: "Video · TD Channel",
    tag: "MP4/WEBM",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <rect x="3" y="6" width="14" height="12" rx="1.5"/>
      <path d="M17 10l4-2v8l-4-2z"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const layer = window.MediaStore.layers.find(l => l.id === window.MediaStore.active && l.kind === "video");
      if (!layer || !layer.el || layer.el.readyState < 2) {
        ctx.fillStyle = "rgba(75,230,255,0.05)";
        ctx.fillRect(w*0.2, h*0.2, w*0.6, h*0.6);
        ctx.strokeStyle = "rgba(75,230,255,0.4)"; ctx.lineWidth = 2; ctx.setLineDash([4,4]);
        ctx.strokeRect(w*0.2, h*0.2, w*0.6, h*0.6); ctx.setLineDash([]);
        ctx.fillStyle = "rgba(75,230,255,0.7)";
        ctx.font = '600 14px "Space Grotesk", sans-serif';
        ctx.textAlign = "center"; ctx.textBaseline = "middle";
        ctx.fillText("DROP MP4 / WEBM OR USE LAYERS PANEL", w/2, h/2);
        return;
      }
      const v = layer.el;
      const dst = fit(layer, w, h, "cover");
      // bass scale (TD-style pulse)
      const sc = 1 + A.bass * 0.08 * P.intensity;
      const dx = (w - dst.w * sc) / 2, dy = (h - dst.h * sc) / 2;
      ctx.save();
      // hue rotate driven by motion macro
      const hueAmt = (P.hue % 360) + (A.energy * 60 * P.motion);
      ctx.filter = `hue-rotate(${hueAmt}deg) saturate(${1 + A.high * P.intensity}) contrast(${1 + A.bass * 0.6 * P.intensity})`;
      ctx.drawImage(v, dx, dy, dst.w * sc, dst.h * sc);
      ctx.filter = "none";
      ctx.restore();
      // slit-scan overlay strips driven by mid band
      if ((P.macro2 ?? 0) > 0.05) {
        const strips = 12 + Math.floor(P.density * 24);
        for (let i = 0; i < strips; i++) {
          const hh = h / strips;
          const off = Math.sin(t * 2 + i * 0.4) * 30 * P.macro2 * (0.4 + A.mid);
          ctx.save();
          ctx.globalAlpha = 0.6;
          ctx.drawImage(v,
            0, (i / strips) * v.videoHeight, v.videoWidth, v.videoHeight / strips,
            off, i * hh, w, hh);
          ctx.restore();
        }
      }
      // RGB split overlay on beat
      if (A.beat > 0.4) {
        const off = 10 * P.intensity;
        ctx.save();
        ctx.globalCompositeOperation = "lighter";
        ctx.globalAlpha = 0.35;
        ctx.fillStyle = "rgba(255,40,40,0.6)";
        ctx.fillRect(dx - off, dy, dst.w * sc, dst.h * sc);
        ctx.fillStyle = "rgba(40,200,255,0.5)";
        ctx.fillRect(dx + off, dy, dst.w * sc, dst.h * sc);
        ctx.restore();
      }
      // bottom band: live frequency meter colored by current video tint
      const N = 64;
      const bh = h * 0.12;
      ctx.save();
      ctx.globalCompositeOperation = "lighter";
      for (let i = 0; i < N; i++) {
        const fi = Math.floor((i / N) * (A.freq.length * 0.5));
        const vv = A.freq[fi] / 255;
        ctx.fillStyle = hsla((P.hue + i * 4) % 360, 90, 60, 0.6);
        ctx.fillRect(i * (w / N), h - vv * bh * P.intensity, w/N - 2, vv * bh * P.intensity);
      }
      ctx.restore();
    },
  };

  /* ============ VIDEO · Datamosh / Pixel Sort ============ */
  const VideoMosh = {
    id: "videoMosh",
    name: "Video · Pixel Sort",
    tag: "DATAMOSH",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <rect x="3" y="3" width="18" height="18" rx="1.5"/>
      <path d="M3 9h18M3 13h18M3 17h18M7 3v18M13 3v18M17 3v18"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const layer = window.MediaStore.layers.find(l => l.id === window.MediaStore.active && l.kind === "video");
      if (!layer || !layer.el || layer.el.readyState < 2) {
        VideoTD.draw(ctx, w, h, A, P, t);
        return;
      }
      const v = layer.el;
      // draw video first
      const dst = fit(layer, w, h, "cover");
      ctx.drawImage(v, (w-dst.w)/2, (h-dst.h)/2, dst.w, dst.h);
      // pick random slice on beat, smear it horizontally
      const slices = Math.max(2, Math.floor(P.density * 40));
      ctx.save();
      ctx.globalCompositeOperation = "screen";
      for (let i = 0; i < slices; i++) {
        const sy = Math.floor((i / slices) * h);
        const sh = Math.ceil(h / slices);
        const off = Math.sin(t * 3 + i * 0.3) * 40 * P.intensity * (0.3 + A.energy);
        const skew = (A.beat > 0.4 ? (Math.random() - 0.5) * 40 * P.intensity : 0);
        ctx.drawImage(ctx.canvas,
          0, sy, w, sh,
          off + skew, sy, w, sh);
      }
      ctx.restore();
      // color blocks where frequency is high
      const blocks = 24;
      for (let i = 0; i < blocks; i++) {
        const fi = Math.floor((i / blocks) * A.freq.length * 0.5);
        const vv = A.freq[fi] / 255;
        if (vv > 0.6 && Math.random() > 0.7) {
          ctx.save();
          ctx.globalCompositeOperation = "difference";
          ctx.fillStyle = hsla((P.hue + i * 8) % 360, 90, 60, 0.7);
          const bw = w / blocks;
          ctx.fillRect(i * bw, Math.random() * h, bw, 4 + Math.random() * 30);
          ctx.restore();
        }
      }
    },
  };

  /* ============ PHOTO STRIP (multi-image carousel) ============ */
  const PhotoStrip = {
    id: "photoStrip",
    name: "Photo Strip · Beat Cut",
    tag: "BEAT CUT",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <rect x="2" y="6" width="6" height="12" rx="1"/>
      <rect x="9" y="6" width="6" height="12" rx="1"/>
      <rect x="16" y="6" width="6" height="12" rx="1"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const imgs = window.MediaStore.layers.filter(l => l.kind === "image" && l.visible);
      if (!imgs.length) {
        ctx.fillStyle = "rgba(255,174,58,0.5)";
        ctx.font = '600 14px "Space Grotesk", sans-serif';
        ctx.textAlign = "center";
        ctx.fillText("ADD IMAGES IN LAYERS PANEL", w/2, h/2);
        return;
      }
      // cycle index advances on each beat
      if (!PhotoStrip._i) PhotoStrip._i = { v: 0, last: 0 };
      const now = performance.now();
      if (A.beat > 0.5 && now - PhotoStrip._i.last > 200) {
        PhotoStrip._i.v = (PhotoStrip._i.v + 1) % imgs.length;
        PhotoStrip._i.last = now;
      }
      const cur = imgs[PhotoStrip._i.v];
      const next = imgs[(PhotoStrip._i.v + 1) % imgs.length];
      const dst = fit(cur, w * 0.85, h * 0.85);
      const sc = 1 + A.bass * 0.15 * P.intensity;
      ctx.save();
      ctx.translate(w/2, h/2);
      ctx.scale(sc, sc);
      ctx.rotate(Math.sin(t * 0.4 * P.motion) * 0.04);
      ctx.translate(-w/2, -h/2);
      ctx.drawImage(cur.el, (w-dst.w)/2, (h-dst.h)/2, dst.w, dst.h);
      ctx.restore();
      // ghost of next image
      if (next !== cur) {
        ctx.save();
        ctx.globalAlpha = 0.25 + A.energy * 0.3;
        ctx.globalCompositeOperation = "lighter";
        ctx.filter = `hue-rotate(${P.hue}deg)`;
        const dn = fit(next, w * 0.85, h * 0.85);
        ctx.drawImage(next.el, (w-dn.w)/2, (h-dn.h)/2, dn.w, dn.h);
        ctx.filter = "none";
        ctx.restore();
      }
      // border ticks
      const N = 32;
      for (let i = 0; i < N; i++) {
        const fi = Math.floor((i / N) * A.freq.length * 0.4);
        const vv = A.freq[fi] / 255;
        ctx.fillStyle = hsla((P.hue + i * 8) % 360, 90, 60, 0.7);
        ctx.fillRect((i / N) * w, h - 10, w/N - 2, -vv * 50 * P.intensity);
      }
    },
  };

  window.VISUALIZERS.push(ImageFrame, ImageBurst, PhotoStrip, VideoTD, VideoMosh);
})();
