//
// Library browsing — async. The UI calls library_refresh_artists /
// library_select_artist / library_select_album, which submit HTTP tasks.
// When each task completes the on_done callback parses JSON and writes the
// result into app.library on the main thread.
//
// Stale-result guard: each "select" stores a generation id. Late callbacks
// compare against the current id and discard if the user has navigated
// elsewhere.
//

Artist :: struct {
    id:    string;
    name:  string;
}

Album :: struct {
    id:        string;
    name:      string;
    artist:    string;
    artist_id: string;
}

Track :: struct {
    id:             string;
    name:           string;
    album:          string;
    album_id:       string;
    artist:         string;
    artist_id:      string;
    duration_ticks: s64;
    index_number:   int;
}

Library_State :: struct {
    artists:            [..] Artist;
    albums:             [..] Album;
    tracks:             [..] Track;

    selected_artist_id: string;
    selected_album_id:  string;

    artists_loaded:     bool;
    albums_loaded:      bool;
    tracks_loaded:      bool;

    artists_loading:    bool;
    albums_loading:     bool;
    tracks_loading:     bool;

    // Generation counters used by the async callbacks to discard stale
    // results when the user has navigated away.
    albums_request_gen: int;
    tracks_request_gen: int;

    artists_scroll:     float;
    albums_scroll:      float;
    tracks_scroll:      float;
}

#scope_module

Item_Summary :: struct {
    Id:             string;
    Name:           string;
    Album:          string;
    AlbumId:        string;
    AlbumArtist:    string;
    AlbumArtistId:  string;
    RunTimeTicks:   s64;
    IndexNumber:    int;
}

Items_Response :: struct {
    Items:            [..] Item_Summary;
    TotalRecordCount: int;
}

#scope_export

//
// Public — kick off async fetches.
//

library_refresh_artists :: () {
    if app.library.artists_loading  return;
    app.library.artists_loading = true;
    path := tprint("/Users/%/Items?IncludeItemTypes=MusicArtist&Recursive=true&SortBy=SortName&Limit=2000", app.jellyfin.user_id);
    log_info("library: fetching artists");
    http_submit("GET", path, on_done=on_artists_loaded);
}

library_select_artist :: (artist_id: string) {
    if app.library.selected_artist_id == artist_id && app.library.albums_loaded  return;

    free(app.library.selected_artist_id);
    app.library.selected_artist_id = copy_string(artist_id);

    free_albums(*app.library.albums);
    app.library.albums_loaded  = false;
    app.library.albums_loading = true;
    app.library.albums_request_gen += 1;
    app.library.albums_scroll = 0;

    free_tracks(*app.library.tracks);
    free(app.library.selected_album_id);
    app.library.selected_album_id = "";
    app.library.tracks_loaded = false;
    app.library.tracks_loading = false;
    app.library.tracks_scroll = 0;

    path := tprint("/Users/%/Items?IncludeItemTypes=MusicAlbum&ArtistIds=%&Recursive=true&SortBy=ProductionYear,SortName&Limit=2000", app.jellyfin.user_id, artist_id);
    log_info("library: fetching albums for %", artist_id);
    http_submit("GET", path, on_done=on_albums_loaded, user_data=cast(*void) app.library.albums_request_gen);
}

library_select_album :: (album_id: string) {
    if app.library.selected_album_id == album_id && app.library.tracks_loaded  return;

    free(app.library.selected_album_id);
    app.library.selected_album_id = copy_string(album_id);

    free_tracks(*app.library.tracks);
    app.library.tracks_loaded  = false;
    app.library.tracks_loading = true;
    app.library.tracks_request_gen += 1;
    app.library.tracks_scroll = 0;

    path := tprint("/Users/%/Items?ParentId=%&IncludeItemTypes=Audio&SortBy=ParentIndexNumber,IndexNumber,SortName&Limit=2000", app.jellyfin.user_id, album_id);
    log_info("library: fetching tracks for %", album_id);
    http_submit("GET", path, on_done=on_tracks_loaded, user_data=cast(*void) app.library.tracks_request_gen);
}

#scope_file

//
// Callbacks — run on the main thread once the worker thread finishes.
//

on_artists_loaded :: (task: *Http_Task) {
    app.library.artists_loading = false;
    if !task.response.ok {
        log_error("artists: status=% body=%", task.response.status_code, slice(task.response.body, 0, min(300, task.response.body.count)));
        return;
    }
    ok, parsed := Jaison.json_parse_string(task.response.body, Items_Response);
    if !ok {
        log_error("artists: json parse failed");
        return;
    }
    free_artists(*app.library.artists);
    for parsed.Items {
        a: Artist;
        a.id   = copy_string(it.Id);
        a.name = copy_string(it.Name);
        array_add(*app.library.artists, a);
    }
    app.library.artists_loaded = true;
    log_info("library: % artists loaded", app.library.artists.count);

    // Prefetch every artist thumb. The image cache caps concurrency at 4
    // so this just queues — it doesn't blow up the network.
    for app.library.artists  image_request(it.id, .THUMB);
}

on_albums_loaded :: (task: *Http_Task) {
    gen := cast(int) task.user_data;
    if gen != app.library.albums_request_gen  return;  // user moved on; discard

    app.library.albums_loading = false;
    if !task.response.ok {
        log_error("albums: status=% body=%", task.response.status_code, slice(task.response.body, 0, min(300, task.response.body.count)));
        return;
    }
    ok, parsed := Jaison.json_parse_string(task.response.body, Items_Response);
    if !ok {
        log_error("albums: json parse failed");
        return;
    }
    free_albums(*app.library.albums);
    for parsed.Items {
        a: Album;
        a.id        = copy_string(it.Id);
        a.name      = copy_string(it.Name);
        a.artist    = copy_string(it.AlbumArtist);
        a.artist_id = copy_string(it.AlbumArtistId);
        array_add(*app.library.albums, a);
    }
    app.library.albums_loaded = true;
    log_info("library: % albums loaded", app.library.albums.count);

    // Prefetch every album thumb up front so scrolling is instant.
    for app.library.albums  image_request(it.id, .THUMB);
}

on_tracks_loaded :: (task: *Http_Task) {
    gen := cast(int) task.user_data;
    if gen != app.library.tracks_request_gen  return;

    app.library.tracks_loading = false;
    if !task.response.ok {
        log_error("tracks: status=% body=%", task.response.status_code, slice(task.response.body, 0, min(300, task.response.body.count)));
        return;
    }
    ok, parsed := Jaison.json_parse_string(task.response.body, Items_Response);
    if !ok {
        log_error("tracks: json parse failed");
        return;
    }
    free_tracks(*app.library.tracks);
    for parsed.Items {
        t: Track;
        t.id             = copy_string(it.Id);
        t.name           = copy_string(it.Name);
        t.album          = copy_string(it.Album);
        t.album_id       = copy_string(it.AlbumId);
        t.artist         = copy_string(it.AlbumArtist);
        t.artist_id      = copy_string(it.AlbumArtistId);
        t.duration_ticks = it.RunTimeTicks;
        t.index_number   = it.IndexNumber;
        array_add(*app.library.tracks, t);
    }
    app.library.tracks_loaded = true;
    log_info("library: % tracks loaded", app.library.tracks.count);
}

free_artists :: (xs: *[..] Artist) {
    for xs.* { free(it.id); free(it.name); }
    array_reset(xs);
}

free_albums :: (xs: *[..] Album) {
    for xs.* { free(it.id); free(it.name); free(it.artist); free(it.artist_id); }
    array_reset(xs);
}

free_tracks :: (xs: *[..] Track) {
    for xs.* { free(it.id); free(it.name); free(it.album); free(it.album_id); free(it.artist); free(it.artist_id); }
    array_reset(xs);
}
