//
// OGG Vorbis decoder. Jellyfin always serves us /universal as OGG, so this
// is the single decode path. Returns Sound_Data with type LINEAR_SAMPLE_ARRAY
// — same buffer the visualizer reads via sound_data.samples (no parallel
// decoder, no cursor desync).
//
// We do NOT use stb_vorbis_decode_memory: that malloc()s the output buffer
// with libc's allocator, and Sound_Player's release_asset path frees it
// with Jai's allocator → SEGV at song end. Instead we open the stream,
// allocate the PCM buffer ourselves, and decode into it. Same allocator on
// both ends.
//

decode_ogg :: (bytes: string, name: string) -> Sound.Sound_Data, bool {
    err: s32;
    v := Stb_Vorbis.stb_vorbis_open_memory(bytes.data, cast(s32) bytes.count, *err, null);
    if !v {
        log_error("decode_ogg: open failed for '%' (err=%)", name, err);
        return .{}, false;
    }
    defer Stb_Vorbis.stb_vorbis_close(v);

    info := Stb_Vorbis.stb_vorbis_get_info(v);
    nch  := cast(s64) info.channels;
    if nch <= 0 || info.sample_rate == 0 {
        log_error("decode_ogg: bad info for '%' (ch=%, rate=%)", name, info.channels, info.sample_rate);
        return .{}, false;
    }

    total_frames := cast(s64) Stb_Vorbis.stb_vorbis_stream_length_in_samples(v);
    if total_frames <= 0 {
        log_error("decode_ogg: zero-length stream for '%'", name);
        return .{}, false;
    }

    total_samples := total_frames * nch;
    samples := cast(*s16) alloc(total_samples * size_of(s16));
    if !samples {
        log_error("decode_ogg: alloc failed for '%'", name);
        return .{}, false;
    }

    // stb_vorbis sometimes hands back fewer frames than the header advertised
    // (chained / corrupt streams), so loop until it returns 0 and use what
    // we got.
    decoded_frames: s64 = 0;
    while decoded_frames < total_frames {
        remaining_shorts := cast(s32) ((total_frames - decoded_frames) * nch);
        n := Stb_Vorbis.stb_vorbis_get_samples_short_interleaved(
            v, cast(s32) nch,
            samples + decoded_frames * nch,
            remaining_shorts);
        if n <= 0  break;
        decoded_frames += n;
    }

    if decoded_frames == 0 {
        log_error("decode_ogg: no frames decoded for '%'", name);
        free(samples);
        return .{}, false;
    }

    actual_samples := decoded_frames * nch;

    sd: Sound.Sound_Data;
    sd.name                     = copy_string(name);
    sd.loaded                   = true;
    sd.type                     = .LINEAR_SAMPLE_ARRAY;
    sd.nchannels                = cast(u16) nch;
    sd.sampling_rate            = cast(u32) info.sample_rate;
    sd.nsamples_times_nchannels = actual_samples;
    sd.samples                  = samples;
    sd.buffer.count             = actual_samples * size_of(s16);
    sd.buffer.data              = cast(*u8) samples;

    log_info("decode_ogg: '%' ch=%, rate=%, frames=%", name, nch, info.sample_rate, decoded_frames);
    return sd, true;
}
