//
// Cover-art palette extraction.
//
// Called from image loading paths whenever a LARGE (album art) image finishes
// loading. Decodes the raw bytes with stb_image, samples ~400 pixels evenly
// across the image, buckets them by hue, and picks the most saturated-and-
// bright representative from each bucket. Grays and near-blacks are discarded
// so the result is always a set of distinct, vivid colours.
//
// app.palette layout:
//   [0] bg        — dominant hue, darkened  (for background tint)
//   [1] highlight — dominant hue, vivid     (track title, visualizer)
//   [2] mid       — second colour, vivid    (artist name, mid bars)
//   [3] accent    — third colour, vivid     (album, bar tips)
//

palette_extract :: (album_id: string, bytes: string) {
    if album_id == app.palette_album_id  return;
    if !bytes.data || bytes.count == 0   return;

    w, h, ch: s32;
    pixels := Stb_Image.stbi_load_from_memory(
        bytes.data, cast(s32) bytes.count, *w, *h, *ch, 3);
    if !pixels  return;
    defer Stb_Image.stbi_image_free(pixels);

    HUE_BUCKETS :: 12;   // 30-degree slices — enough to separate reds, oranges, etc.

    bucket_r:     [HUE_BUCKETS] float;
    bucket_g:     [HUE_BUCKETS] float;
    bucket_b:     [HUE_BUCKETS] float;
    bucket_score: [HUE_BUCKETS] float;

    total := w * h;
    step  := max(1, total / 400);

    i := 0;
    while i < total {
        px := pixels + i * 3;
        r := cast(float) px[0] / 255.0;
        g := cast(float) px[1] / 255.0;
        b := cast(float) px[2] / 255.0;

        hue, sat, val := rgb_to_hsv(r, g, b);

        // Discard near-grays (low saturation) and near-blacks.
        if sat < 0.25 || val < 0.15 { i += step; continue; }

        // Score peaks for medium-bright, highly saturated colours.
        score := sat * (0.35 + 0.65 * val);

        bucket := cast(int)(hue * HUE_BUCKETS / 360.0);
        if bucket >= HUE_BUCKETS  bucket = HUE_BUCKETS - 1;

        if score > bucket_score[bucket] {
            bucket_score[bucket] = score;
            bucket_r[bucket] = r;
            bucket_g[bucket] = g;
            bucket_b[bucket] = b;
        }
        i += step;
    }

    // Collect non-empty buckets and sort by score descending.
    order: [HUE_BUCKETS] int;
    count := 0;
    for 0..HUE_BUCKETS-1 {
        if bucket_score[it] > 0 {
            order[count] = it;
            count += 1;
        }
    }
    if count > 1 {
        for outer: 1..count-1 {
            j := outer;
            while j > 0 && bucket_score[order[j]] > bucket_score[order[j-1]] {
                tmp := order[j];  order[j] = order[j-1];  order[j-1] = tmp;
                j -= 1;
            }
        }
    }

    // Defaults (keep existing purple aesthetic when image has no strong colour).
    app.palette[0] = .{0.04, 0.02, 0.08, 1};
    app.palette[1] = .{1.0,  0.4,  0.8,  1};
    app.palette[2] = .{0.4,  0.8,  1.0,  1};
    app.palette[3] = .{0.5,  1.0,  0.6,  1};

    if count >= 1 {
        r0 := bucket_r[order[0]];
        g0 := bucket_g[order[0]];
        b0 := bucket_b[order[0]];
        h0, s0, v0 := rgb_to_hsv(r0, g0, b0);
        // bg: dominant hue, very dark so text stays readable
        app.palette[0] = hsv_to_rgb4(h0, min(s0 * 0.8, 0.85), v0 * 0.22);
        // highlight: dominant hue pushed vivid and bright
        app.palette[1] = hsv_to_rgb4(h0, max(s0, 0.80), max(v0, 0.85));
    }
    if count >= 2 {
        r1 := bucket_r[order[1]];
        g1 := bucket_g[order[1]];
        b1 := bucket_b[order[1]];
        h1, s1, v1 := rgb_to_hsv(r1, g1, b1);
        app.palette[2] = hsv_to_rgb4(h1, max(s1, 0.70), max(v1, 0.75));
    }
    if count >= 3 {
        r2 := bucket_r[order[2]];
        g2 := bucket_g[order[2]];
        b2 := bucket_b[order[2]];
        h2, s2, v2 := rgb_to_hsv(r2, g2, b2);
        app.palette[3] = hsv_to_rgb4(h2, max(s2, 0.60), max(v2, 0.70));
    }

    free(app.palette_album_id);
    app.palette_album_id = copy_string(album_id);
    app.palette_ready    = true;

    log_info("palette: album=% hi=(%.2,%.2,%.2) bg=(%.2,%.2,%.2) found % hue buckets",
        album_id,
        app.palette[1].x, app.palette[1].y, app.palette[1].z,
        app.palette[0].x, app.palette[0].y, app.palette[0].z,
        count);
}

#scope_file

rgb_to_hsv :: (r: float, g: float, b: float) -> (hue: float, sat: float, val: float) {
    cmax  := max(r, max(g, b));
    cmin  := min(r, min(g, b));
    delta := cmax - cmin;

    val := cmax;
    sat := ifx cmax > 0.001 then delta / cmax else 0.0;

    hue: float;
    if delta > 0.001 {
        if cmax == r {
            hue = 60.0 * ((g - b) / delta);
            if hue < 0  hue += 360.0;
        } else if cmax == g {
            hue = 60.0 * ((b - r) / delta + 2.0);
        } else {
            hue = 60.0 * ((r - g) / delta + 4.0);
        }
    }
    return hue, sat, val;
}

hsv_to_rgb4 :: (h: float, s: float, v: float) -> Vector4 {
    if s < 0.001  return .{v, v, v, 1};
    hh := h / 60.0;
    i  := cast(int) hh;
    f  := hh - cast(float) i;
    p  := v * (1.0 - s);
    q  := v * (1.0 - s * f);
    t  := v * (1.0 - s * (1.0 - f));
    r, g, b: float;
    if      i == 0 { r=v; g=t; b=p; }
    else if i == 1 { r=q; g=v; b=p; }
    else if i == 2 { r=p; g=v; b=t; }
    else if i == 3 { r=p; g=q; b=v; }
    else if i == 4 { r=t; g=p; b=v; }
    else           { r=v; g=p; b=q; }
    return .{r, g, b, 1};
}
