/* ========================================================================
   LUMINA — Creative coding visualizers (p5.js inspired)
   ======================================================================== */
(function () {
  const TAU = Math.PI * 2;
  const hsla = (h, s = 80, l = 60, a = 1) => `hsla(${h}, ${s}%, ${l}%, ${a})`;
  const bg = (ctx, w, h, P) => {
    ctx.fillStyle = `rgba(8,8,12,${P.bgAlpha ?? 0.18})`;
    ctx.fillRect(0, 0, w, h);
  };

  /* === Perlin-ish noise (cheap) === */
  function noise(x, y, z = 0) {
    return (Math.sin(x * 12.9898 + y * 78.233 + z * 37.719) * 43758.5453) % 1;
  }
  function smoothNoise(x, y, z = 0) {
    const xi = Math.floor(x), yi = Math.floor(y);
    const xf = x - xi, yf = y - yi;
    const u = xf * xf * (3 - 2 * xf);
    const v = yf * yf * (3 - 2 * yf);
    const a = noise(xi, yi, z), b = noise(xi + 1, yi, z);
    const c = noise(xi, yi + 1, z), d = noise(xi + 1, yi + 1, z);
    return Math.abs((a * (1 - u) + b * u) * (1 - v) + (c * (1 - u) + d * u) * v);
  }

  /* ============ FLOW FIELD (Perlin-noise particles) ============ */
  const flowParts = [];
  const FlowField = {
    id: "flowField", name: "Flow Field", tag: "PERLIN",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M2 8 Q8 4 12 8 T22 8 M2 16 Q8 20 12 16 T22 16M2 12 Q8 8 12 12 T22 12"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      ctx.fillStyle = `rgba(8,8,12,${0.05 + (1 - P.bgAlpha) * 0.03})`;
      ctx.fillRect(0, 0, w, h);
      const N = Math.max(200, Math.floor(P.density * 1200));
      while (flowParts.length < N) {
        flowParts.push({
          x: Math.random() * w, y: Math.random() * h,
          life: Math.random() * 200,
          hue: Math.random() * 60,
        });
      }
      while (flowParts.length > N) flowParts.pop();
      ctx.globalCompositeOperation = "lighter";
      for (const p of flowParts) {
        const angle = smoothNoise(p.x * 0.005, p.y * 0.005, t * 0.2) * TAU * 4;
        const speed = 1 + A.energy * 5 * P.intensity;
        p.x += Math.cos(angle) * speed;
        p.y += Math.sin(angle) * speed;
        p.life--;
        if (p.life <= 0 || p.x < 0 || p.x > w || p.y < 0 || p.y > h) {
          p.x = Math.random() * w; p.y = Math.random() * h;
          p.life = 100 + Math.random() * 200;
          p.hue = Math.random() * 60;
        }
        const hue = (P.hue + p.hue + t * 30) % 360;
        ctx.fillStyle = hsla(hue, 100, 60, 0.5);
        ctx.fillRect(p.x, p.y, 1.5, 1.5);
      }
      ctx.globalCompositeOperation = "source-over";
    },
  };

  /* ============ ATTRACTOR (Lorenz-ish) ============ */
  const attrPts = [];
  const Attractor = {
    id: "attractor", name: "Strange Attractor", tag: "CHAOS",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M3 12 Q8 4 12 12 T21 12 Q16 20 12 12 T3 12"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      ctx.fillStyle = `rgba(8,8,12,${0.04})`;
      ctx.fillRect(0, 0, w, h);
      const cx = w/2, cy = h/2;
      const a = 1.4 + A.bass * 0.5;
      const b = 0.3 + A.high * 0.2;
      const N = 1500;
      let x = attrPts._x || 0.1, y = attrPts._y || 0.1;
      const scale = Math.min(w, h) * 0.25;
      ctx.globalCompositeOperation = "lighter";
      for (let i = 0; i < N; i++) {
        const xn = 1 - a * x * x + y;
        const yn = b * x;
        x = xn; y = yn;
        const px = cx + x * scale, py = cy + y * scale;
        const hue = (P.hue + i * 0.2 + t * 30) % 360;
        ctx.fillStyle = hsla(hue, 100, 60, 0.4);
        ctx.fillRect(px, py, 1, 1);
      }
      attrPts._x = x; attrPts._y = y;
      ctx.globalCompositeOperation = "source-over";
    },
  };

  /* ============ ROSE CURVE (mathematical petals) ============ */
  const RoseCurve = {
    id: "roseCurve", name: "Rose Curve", tag: "MATH",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M12 2 Q22 12 12 22 Q2 12 12 2 M2 12 Q12 22 22 12 Q12 2 2 12"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const R = Math.min(w, h) * 0.42;
      const k = 3 + Math.floor(A.bass * 6);
      const d = 5 + Math.floor(A.high * 6);
      ctx.shadowBlur = P.glow * 22;
      for (let pass = 0; pass < 3; pass++) {
        ctx.beginPath();
        for (let i = 0; i <= 720; i++) {
          const theta = (i / 360) * Math.PI + t * 0.4 * P.motion;
          const r = R * Math.cos((k / d) * theta);
          const x = cx + r * Math.cos(theta);
          const y = cy + r * Math.sin(theta);
          if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        const hue = (P.hue + pass * 60 + t * 40) % 360;
        ctx.strokeStyle = hsla(hue, 100, 60, pass === 2 ? 1 : 0.25);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = pass === 2 ? P.lineWidth : P.lineWidth * (4 - pass);
        ctx.stroke();
      }
      ctx.shadowBlur = 0;
    },
  };

  /* ============ MANDALA (radial repetition) ============ */
  const Mandala = {
    id: "mandala", name: "Mandala", tag: "SACRED",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/>
      <path d="M12 3v18M3 12h18M5 5l14 14M19 5L5 19"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const layers = 8;
      const petals = 12;
      ctx.shadowBlur = P.glow * 12;
      for (let l = 0; l < layers; l++) {
        const r = (l + 1) * Math.min(w, h) * 0.05 * (1 + A.bass * 0.2);
        for (let i = 0; i < petals; i++) {
          const ang = (i / petals) * TAU + t * (l % 2 ? 0.2 : -0.2) * P.motion;
          const px = cx + Math.cos(ang) * r;
          const py = cy + Math.sin(ang) * r;
          const sz = (10 + l * 4) * (1 + A.energy * 0.4);
          const hue = (P.hue + l * 40 + i * 20 + t * 30) % 360;
          ctx.fillStyle = hsla(hue, 100, 60, 0.5);
          ctx.shadowColor = hsla(hue, 100, 60);
          ctx.beginPath();
          ctx.arc(px, py, sz * 0.4, 0, TAU);
          ctx.fill();
        }
      }
      ctx.shadowBlur = 0;
    },
  };

  /* ============ ASCII RAIN ============ */
  const AsciiRain = {
    id: "asciiRain", name: "ASCII Rain", tag: "TEXT",
    icon: <svg viewBox="0 0 24 24" fill="currentColor"><text x="3" y="10" fontSize="8" fontFamily="monospace">01</text><text x="3" y="20" fontSize="8" fontFamily="monospace">10</text></svg>,
    draw(ctx, w, h, A, P, t) {
      ctx.fillStyle = `rgba(0,0,0,${0.12 + (1 - P.bgAlpha) * 0.05})`;
      ctx.fillRect(0, 0, w, h);
      const chars = "01ABC#@$%&*+=-<>?!|/\\";
      const sz = 16;
      const cols = Math.floor(w / sz);
      if (!AsciiRain._drops || AsciiRain._drops.length !== cols)
        AsciiRain._drops = Array.from({ length: cols }, () => Math.random() * h);
      ctx.font = `bold ${sz}px monospace`;
      ctx.textAlign = "center";
      for (let i = 0; i < cols; i++) {
        const ch = chars[Math.floor(Math.random() * chars.length)];
        AsciiRain._drops[i] += sz * (0.3 + A.bass * 3 * P.intensity);
        if (AsciiRain._drops[i] > h + sz && Math.random() > 0.95) AsciiRain._drops[i] = -Math.random() * 200;
        const y = AsciiRain._drops[i];
        const hue = (P.hue + i * 5) % 360;
        ctx.fillStyle = hsla(hue, 100, 70, 1);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.shadowBlur = P.glow * 10;
        ctx.fillText(ch, i * sz + sz / 2, y);
        ctx.shadowBlur = 0;
      }
    },
  };

  /* ============ HEXAGON GRID PULSE ============ */
  const HexGrid = {
    id: "hexGrid", name: "Hex Grid Pulse", tag: "TILE",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <polygon points="12,2 21,7 21,17 12,22 3,17 3,7"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const size = 30;
      const hex = Math.sqrt(3) * size;
      ctx.shadowBlur = P.glow * 8;
      for (let row = 0, ry = 0; ry < h + hex; row++, ry += hex * 0.75) {
        const offX = row % 2 ? hex / 2 : 0;
        for (let col = 0, cx = -offX; cx < w + hex; col++, cx += hex) {
          const dx = cx - w/2, dy = ry - h/2;
          const d = Math.hypot(dx, dy) / Math.hypot(w, h);
          const wave = Math.sin(d * 10 - t * 4 * P.motion) * 0.5 + 0.5;
          const v = wave * A.energy * P.intensity + A.bass * 0.5;
          const hue = (P.hue + d * 300 + t * 30) % 360;
          ctx.beginPath();
          for (let i = 0; i < 6; i++) {
            const a = (i / 6) * TAU;
            const px = cx + Math.cos(a) * size * (0.7 + v * 0.4);
            const py = ry + Math.sin(a) * size * (0.7 + v * 0.4);
            if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
          }
          ctx.closePath();
          ctx.fillStyle = hsla(hue, 90, 30 + v * 40, 0.5);
          ctx.strokeStyle = hsla(hue, 100, 60 + v * 30, 0.9);
          ctx.shadowColor = hsla(hue, 100, 60);
          ctx.lineWidth = 1 + v * 2;
          ctx.fill();
          ctx.stroke();
        }
      }
      ctx.shadowBlur = 0;
    },
  };

  /* ============ TURBULENCE (smoke / fluid sim feel) ============ */
  const Turbulence = {
    id: "turbulence", name: "Turbulence", tag: "FLUID",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M2 14 Q8 6 14 14 T22 12 M2 18 Q8 10 14 18 T22 16"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const N = Math.max(30, Math.floor(P.density * 60));
      ctx.globalCompositeOperation = "lighter";
      for (let i = 0; i < N; i++) {
        const phase = (i / N) * TAU;
        const hue = (P.hue + i * 8 + t * 30) % 360;
        ctx.beginPath();
        const amp = h * 0.2 * P.intensity * (0.5 + A.energy);
        for (let x = 0; x <= w; x += 3) {
          const u = x / w;
          const n = smoothNoise(u * 6, t * 0.5 + i * 0.3, t * 0.2);
          const y = h/2 + Math.sin(u * TAU * 2 + t + phase) * amp * n * 2 +
            (n - 0.5) * amp * 2;
          if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        ctx.strokeStyle = hsla(hue, 100, 60, 0.3);
        ctx.shadowBlur = P.glow * 10;
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = P.lineWidth;
        ctx.stroke();
      }
      ctx.globalCompositeOperation = "source-over";
      ctx.shadowBlur = 0;
    },
  };

  /* ============ FRACTAL TREE ============ */
  function branch(ctx, len, depth, A, P, t, hue) {
    if (depth <= 0 || len < 2) return;
    ctx.strokeStyle = hsla(hue, 100, 60, depth / 8);
    ctx.lineWidth = depth * 0.6;
    ctx.beginPath();
    ctx.moveTo(0, 0); ctx.lineTo(0, -len); ctx.stroke();
    ctx.translate(0, -len);
    const angle = 0.3 + A.bass * 0.5 + Math.sin(t * 0.5) * 0.1;
    ctx.save();
    ctx.rotate(angle);
    branch(ctx, len * 0.7, depth - 1, A, P, t, hue + 20);
    ctx.restore();
    ctx.save();
    ctx.rotate(-angle);
    branch(ctx, len * 0.7, depth - 1, A, P, t, hue - 20);
    ctx.restore();
  }
  const FractalTree = {
    id: "fractalTree", name: "Fractal Tree", tag: "GROW",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M12 22V12M12 12L6 6M12 12L18 6M6 6L3 3M6 6L9 3M18 6L15 3M18 6L21 3"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      ctx.save();
      ctx.translate(w/2, h);
      ctx.shadowBlur = P.glow * 14;
      ctx.shadowColor = hsla(P.hue, 100, 60);
      branch(ctx, h * 0.22 * (1 + A.bass * 0.3), 9, A, P, t, P.hue);
      ctx.restore();
      ctx.shadowBlur = 0;
    },
  };

  /* ============ VORONOI-style cells ============ */
  const Voronoi = {
    id: "voronoi", name: "Voronoi Cells", tag: "CELLS",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M2 8L8 4L14 8M14 8L20 4M14 8L18 14L14 20M14 20L8 22M14 20L20 18M8 4L4 14L8 22M4 14L18 14"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const N = Math.max(8, Math.floor(P.density * 30));
      // Generate points moving
      if (!Voronoi._pts || Voronoi._pts.length !== N) {
        Voronoi._pts = Array.from({ length: N }, () => ({
          x: Math.random(), y: Math.random(),
          vx: (Math.random() - 0.5) * 0.001,
          vy: (Math.random() - 0.5) * 0.001,
          hue: Math.random() * 360,
        }));
      }
      const pts = Voronoi._pts;
      for (const p of pts) {
        p.x += p.vx * (1 + A.energy * 3);
        p.y += p.vy * (1 + A.energy * 3);
        if (p.x < 0 || p.x > 1) p.vx *= -1;
        if (p.y < 0 || p.y > 1) p.vy *= -1;
      }
      // Sample pixels (low-res)
      const step = 6;
      for (let y = 0; y < h; y += step) {
        for (let x = 0; x < w; x += step) {
          let best = Infinity, bi = 0;
          for (let i = 0; i < pts.length; i++) {
            const dx = pts[i].x * w - x, dy = pts[i].y * h - y;
            const d = dx * dx + dy * dy;
            if (d < best) { best = d; bi = i; }
          }
          const hue = (P.hue + pts[bi].hue + t * 20) % 360;
          const dist = Math.sqrt(best) / Math.max(w, h);
          ctx.fillStyle = hsla(hue, 90, 30 + (1 - dist) * 40, 0.95);
          ctx.fillRect(x, y, step + 1, step + 1);
        }
      }
      // Sites
      for (const p of pts) {
        ctx.fillStyle = "#fff";
        ctx.beginPath(); ctx.arc(p.x * w, p.y * h, 3, 0, TAU); ctx.fill();
      }
    },
  };

  /* ============ SPRING DOTS (connected mesh) ============ */
  const SpringDots = {
    id: "springDots", name: "Spring Mesh", tag: "WEB",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M4 4L20 20M4 20L20 4M12 4v16M4 12h16"/>
      <circle cx="4" cy="4" r="2" fill="currentColor"/>
      <circle cx="20" cy="20" r="2" fill="currentColor"/>
      <circle cx="20" cy="4" r="2" fill="currentColor"/>
      <circle cx="4" cy="20" r="2" fill="currentColor"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const N = Math.max(20, Math.floor(P.density * 70));
      if (!SpringDots._pts || SpringDots._pts.length !== N) {
        SpringDots._pts = Array.from({ length: N }, () => ({
          x: Math.random() * w, y: Math.random() * h,
          vx: (Math.random() - 0.5) * 2,
          vy: (Math.random() - 0.5) * 2,
        }));
      }
      const pts = SpringDots._pts;
      // Update
      for (const p of pts) {
        p.x += p.vx * (1 + A.energy * 2);
        p.y += p.vy * (1 + A.energy * 2);
        if (p.x < 0 || p.x > w) p.vx *= -1;
        if (p.y < 0 || p.y > h) p.vy *= -1;
      }
      // Connections
      const maxD = 150 + A.bass * 200 * P.intensity;
      ctx.globalCompositeOperation = "lighter";
      for (let i = 0; i < pts.length; i++) {
        for (let j = i + 1; j < pts.length; j++) {
          const dx = pts[i].x - pts[j].x, dy = pts[i].y - pts[j].y;
          const d = Math.hypot(dx, dy);
          if (d < maxD) {
            const a = 1 - d / maxD;
            const hue = (P.hue + i * 5 + t * 30) % 360;
            ctx.strokeStyle = hsla(hue, 100, 60, a * 0.6);
            ctx.lineWidth = a * P.lineWidth;
            ctx.beginPath();
            ctx.moveTo(pts[i].x, pts[i].y);
            ctx.lineTo(pts[j].x, pts[j].y);
            ctx.stroke();
          }
        }
      }
      ctx.globalCompositeOperation = "source-over";
      // Dots
      for (const p of pts) {
        ctx.fillStyle = hsla(P.hue, 100, 80, 1);
        ctx.shadowColor = hsla(P.hue, 100, 60);
        ctx.shadowBlur = P.glow * 10;
        ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, TAU); ctx.fill();
      }
      ctx.shadowBlur = 0;
    },
  };

  /* ============ CIRCLE PACKING ============ */
  const CirclePack = {
    id: "circlePack", name: "Circle Pack", tag: "GENERATIVE",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <circle cx="6" cy="8" r="4"/><circle cx="16" cy="6" r="3"/>
      <circle cx="18" cy="15" r="4"/><circle cx="7" cy="17" r="3"/>
      <circle cx="12" cy="11" r="2"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const N = Math.max(40, Math.floor(P.density * 200));
      if (!CirclePack._circles || CirclePack._circles.length !== N) {
        CirclePack._circles = [];
        let tries = 0;
        while (CirclePack._circles.length < N && tries++ < 4000) {
          const cand = {
            x: Math.random() * w, y: Math.random() * h,
            r: 5 + Math.random() * 30,
            hue: Math.random() * 360,
          };
          let ok = true;
          for (const c of CirclePack._circles) {
            if (Math.hypot(c.x - cand.x, c.y - cand.y) < c.r + cand.r + 2) { ok = false; break; }
          }
          if (ok) CirclePack._circles.push(cand);
        }
      }
      ctx.shadowBlur = P.glow * 14;
      for (const c of CirclePack._circles) {
        const pulse = 1 + Math.sin(t * 2 + c.hue * 0.1) * 0.15 + A.bass * 0.3 * P.intensity;
        const hue = (P.hue + c.hue + t * 30) % 360;
        ctx.fillStyle = hsla(hue, 100, 50, 0.7);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.beginPath(); ctx.arc(c.x, c.y, c.r * pulse, 0, TAU); ctx.fill();
        ctx.strokeStyle = hsla(hue, 100, 80, 0.9);
        ctx.lineWidth = 1.5;
        ctx.stroke();
      }
      ctx.shadowBlur = 0;
    },
  };

  window.VISUALIZERS.push(FlowField, Attractor, RoseCurve, Mandala, AsciiRain,
                          HexGrid, Turbulence, FractalTree, Voronoi,
                          SpringDots, CirclePack);
})();
