//
// Visualizer backdrop. Reads `app.spectrum` (filled by audio/analysis.jai with
// a real FFT each frame) and draws a mirrored bar visualizer with a
// bass-driven background pulse, plus a particle system that shoots sparks
// from bar tips and arcs them under gravity.
//
// All immediate-mode quads via Simp for now. A real GLSL fragment shader is
// on the roadmap (ai/todo.md) — once that lands this becomes a fullscreen
// quad reading the spectrum + waveform as 1D textures.
//

gfx_init :: () {
    // Nothing yet.
}

gfx_draw_visualizer_background :: (w: float, h: float, bg_alpha := 1.0) {
    update_audio_analysis();

    t := cast(float) app.current_time;

    bass: float = 0;
    bass_bins := SPECTRUM_BINS / 6;
    if bass_bins < 1  bass_bins = 1;
    for 0..bass_bins-1  bass += app.spectrum[it];
    bass /= cast(float) bass_bins;

    {
        base: Vector4;
        if app.palette_ready {
            p := app.palette[0];
            base = .{p.x + 0.08 * bass, p.y + 0.04 * bass, p.z + 0.12 * bass, bg_alpha};
        } else {
            base = .{0.04 + 0.10 * bass, 0.02 + 0.04 * bass, 0.10 + 0.18 * bass, bg_alpha};
        }
        Simp.set_shader_for_color(enable_blend = bg_alpha < 1.0);
        Simp.immediate_quad(0, 0, w, h, base);
    }

    // Mirrored bars from the horizontal mid-line — bass on the left, treble
    // on the right, both spreading top and bottom.
    cy := h * 0.5;
    bar_w := w / cast(float) SPECTRUM_BINS;
    max_bar := h * 0.45;

    for i: 0..SPECTRUM_BINS-1 {
        v := app.spectrum[i];
        bar_h := v * max_bar;
        x0 := cast(float) i * bar_w;
        x1 := x0 + bar_w * 0.85;

        hue := cast(float) i / cast(float) SPECTRUM_BINS;
        body: Vector4;
        if app.palette_ready {
            // 4-stop gradient across the spectrum using all palette colours.
            // hue goes 0..1 left-to-right; map onto 3 segments:
            //   [0, 0.33]  palette[1] → palette[2]
            //   [0.33,0.66] palette[2] → palette[3]
            //   [0.66, 1]  palette[3] → palette[1]  (wraps back for continuity)
            c: Vector4;
            if hue < 0.333 {
                t2 := hue / 0.333;
                c1 := app.palette[1];  c2 := app.palette[2];
                c = .{c1.x+(c2.x-c1.x)*t2, c1.y+(c2.y-c1.y)*t2, c1.z+(c2.z-c1.z)*t2, 1};
            } else if hue < 0.666 {
                t2 := (hue - 0.333) / 0.333;
                c1 := app.palette[2];  c2 := app.palette[3];
                c = .{c1.x+(c2.x-c1.x)*t2, c1.y+(c2.y-c1.y)*t2, c1.z+(c2.z-c1.z)*t2, 1};
            } else {
                t2 := (hue - 0.666) / 0.334;
                c1 := app.palette[3];  c2 := app.palette[1];
                c = .{c1.x+(c2.x-c1.x)*t2, c1.y+(c2.y-c1.y)*t2, c1.z+(c2.z-c1.z)*t2, 1};
            }
            // Modulate brightness by amplitude; dim quiet bars a little.
            bright := 0.45 + 0.55 * v;
            body = .{c.x * bright, c.y * bright, c.z * bright, 0.85 + 0.15 * v};
        } else {
            body = neon_color(hue, v, t);
        }
        tip  := body;
        tip.x = min(1.0, tip.x + 0.35);
        tip.y = min(1.0, tip.y + 0.35);
        tip.z = min(1.0, tip.z + 0.35);

        // Top half (going up).
        Simp.immediate_quad(x0, cy - bar_h, x1, cy, body);
        // Bottom half (going down) — slightly shorter so the eye reads the
        // top half as primary.
        Simp.immediate_quad(x0, cy, x1, cy + bar_h * 0.85, body);

        // Bright tip cap on the top bar (the classic Winamp peak nub).
        cap_h := max_bar * 0.012;
        if bar_h > cap_h * 2 {
            Simp.immediate_quad(x0, cy - bar_h - cap_h, x1, cy - bar_h, tip);
        }

        // Spawn sparks from the top bar tip when the bar is energetic.
        // Spawn x is random across the full bar width.
        if v > 0.18 {
            spawn_chance := (v - 0.18) / 0.82 * 0.28;
            if random_get_zero_to_one() < spawn_chance {
                spark_col := tip;
                spark_col.w = 1.0;
                spawn_x := x0 + random_get_zero_to_one() * bar_w;
                particle_spawn(spawn_x, cy - bar_h, spark_col, min(w, h));
            }
        }
    }

    // Center mirror line — a thin neon glow across the middle.
    {
        line_h   := 1.5;
        line_col := ifx app.palette_ready then app.palette[1] else Vector4.{1.0, 0.4, 0.8, 1};
        line_col.w = 0.5 + 0.3 * bass;
        Simp.immediate_quad(0, cy - line_h * 0.5, w, cy + line_h * 0.5, line_col);
    }

    particles_update_draw(w, h);
}

#scope_file

// ── Spark particle system ─────────────────────────────────────────────────────

MAX_PARTICLES :: 800;

Particle :: struct {
    x, y:     float;
    vx, vy:   float;
    life:     float;   // remaining seconds
    max_life: float;
    color:    Vector4;
    size:     float;
}

particles:  [MAX_PARTICLES] Particle;
live_count: int;

particle_spawn :: (x: float, y: float, color: Vector4, ref_size: float) {
    if live_count >= MAX_PARTICLES  return;
    p := *particles[live_count];
    p.x        = x;
    p.y        = y;
    // Mostly upward: angle biased toward -PI/2 (up in screen coords), ±50° spread.
    base_angle := -1.5708;  // -PI/2
    angle      := base_angle + (random_get_zero_to_one() - 0.5) * 1.396;  // ±40°
    speed      := 150.0 + random_get_zero_to_one() * 200.0;
    p.vx       = cos(angle) * speed;
    p.vy       = sin(angle) * speed;
    p.max_life = 2.0 + random_get_zero_to_one() * 2.0;
    p.life     = p.max_life;
    p.color    = color;
    // 0.3 – 0.8% of the smaller screen dimension.
    p.size     = ref_size * (0.003 + random_get_zero_to_one() * 0.005);
    live_count += 1;
}

particles_update_draw :: (w: float, h: float) {
    dt := app.dt;
    i  := 0;
    while i < live_count {
        p := *particles[i];
        p.life -= dt;
        if p.life <= 0 {
            particles[i] = particles[live_count - 1];
            live_count -= 1;
            continue;
        }

        p.x += p.vx * dt;
        p.y += p.vy * dt;

        // Elastic bounce off all four walls.
        if p.x < 0  { p.x = -p.x;      p.vx = -p.vx; }
        if p.x > w  { p.x = 2*w - p.x; p.vx = -p.vx; }
        if p.y < 0  { p.y = -p.y;      p.vy = -p.vy; }
        if p.y > h  { p.y = 2*h - p.y; p.vy = -p.vy; }

        // Linear fade so they stay vivid most of their life.
        frac := p.life / p.max_life;
        col  := p.color;
        col.w = frac * 0.5;

        half := p.size * 0.5;
        Simp.immediate_quad(p.x - half, p.y - half, p.x + half, p.y + half, col);
        i += 1;
    }
}

// ── Colour helpers ────────────────────────────────────────────────────────────

neon_color :: (hue: float, intensity: float, t: float) -> Vector4 {
    // Slow hue drift over time so the color palette evolves even on
    // sustained tones.
    h := hue + t * 0.03;
    r := 0.5 + 0.5 * sin(h * 6.28318 + 0.0);
    g := 0.5 + 0.5 * sin(h * 6.28318 + 2.094);
    b := 0.5 + 0.5 * sin(h * 6.28318 + 4.188);
    a := 0.55 + 0.45 * intensity;
    boost := 0.45 + 0.55 * intensity;
    return Vector4.{r * boost, g * boost, b * boost, a};
}
