import 'package:dio/dio.dart';

import '../../models/album.dart';
import '../../models/artist.dart';
import '../../models/home_data.dart';
import '../../models/home_index.dart';
import '../../models/track.dart';

/// LibraryApi wraps the server's native /api/* library surface.
///
/// Response shapes (verified against internal/api/library.go,
/// internal/api/library_albums.go, internal/api/home.go):
///
///   GET /api/home              → HomePayload (flat sections)
///   GET /api/artists/{id}      → ArtistDetail (ArtistRef fields + "albums")
///   GET /api/artists/{id}/tracks → flat []TrackRef array (NOT enveloped)
///   GET /api/albums/{id}       → AlbumDetail (AlbumRef fields + "tracks")
///
/// The artist-tracks endpoint deviates from the plan-text starter: the
/// server emits a bare JSON array, not `{"tracks": [...]}`. We parse the
/// top-level response as `List` accordingly.
class LibraryApi {
  LibraryApi(this._dio);
  final Dio _dio;

  Future<HomeData> getHome() async {
    final r = await _dio.get<Map<String, dynamic>>('/api/home');
    return HomeData.fromJson(r.data ?? const {});
  }

  /// GET /api/home/index — per-item rendering variant. Returns just IDs
  /// per section; client hydrates each tile via the per-entity
  /// endpoints. ~10× smaller than /api/home on populated libraries so
  /// the cold-visit round-trip is correspondingly short.
  Future<HomeIndex> getHomeIndex() async {
    final r = await _dio.get<Map<String, dynamic>>('/api/home/index');
    return HomeIndex.fromJson(r.data ?? const {});
  }

  /// GET /api/tracks/{id}. Returns the canonical TrackRef. Used by
  /// the HydrationQueue to populate cached_tracks on a per-tile miss
  /// — the existing endpoint already joins album + artist so the
  /// response carries everything TrackRef needs.
  Future<TrackRef> getTrack(String id) async {
    final r = await _dio.get<Map<String, dynamic>>('/api/tracks/$id');
    return TrackRef.fromJson(r.data ?? const {});
  }

  /// GET /api/artists/{id}. Server returns ArtistDetail which embeds
  /// ArtistRef inline; ArtistRef.fromJson already reads only the fields
  /// it cares about, so passing the whole body is correct.
  Future<ArtistRef> getArtist(String id) async {
    final r = await _dio.get<Map<String, dynamic>>('/api/artists/$id');
    return ArtistRef.fromJson(r.data ?? const {});
  }

  /// GET /api/artists/{id} — full response with the ArtistRef AND the
  /// embedded album list, both parsed. Single round-trip variant used
  /// by CacheFiller and other callers that want to populate both
  /// cached_artists and cached_albums in one shot. The existing
  /// getArtist / getArtistAlbums keep working for callers that only
  /// need one half — they hit the same URL but the per-id Riverpod
  /// caching layer dedupes.
  Future<({ArtistRef artist, List<AlbumRef> albums})> getArtistDetail(
      String id) async {
    final r = await _dio.get<Map<String, dynamic>>('/api/artists/$id');
    final body = r.data ?? const <String, dynamic>{};
    final artist = ArtistRef.fromJson(body);
    final albums = ((body['albums'] as List?) ?? const [])
        .map((e) => AlbumRef.fromJson((e as Map).cast<String, dynamic>()))
        .toList(growable: false);
    return (artist: artist, albums: albums);
  }

  /// Pulls the "albums" array out of the same ArtistDetail body. Callers
  /// that need both the artist and its albums should issue two provider
  /// reads (artistProvider + artistAlbumsProvider) — both hit the same
  /// underlying URL and dio's response is not memoized here, but the
  /// Riverpod layer caches per-id so cost stays at one round-trip.
  Future<List<AlbumRef>> getArtistAlbums(String id) async {
    final r = await _dio.get<Map<String, dynamic>>('/api/artists/$id');
    final raw = (r.data?['albums'] as List?) ?? const [];
    return raw
        .map((e) => AlbumRef.fromJson((e as Map).cast<String, dynamic>()))
        .toList(growable: false);
  }

  /// GET /api/library/shuffle?limit=N (#427 S4). N random library
  /// tracks — the online source for "Shuffle all". Bare JSON array.
  Future<List<TrackRef>> shuffle({int limit = 100}) async {
    final r = await _dio.get<List<dynamic>>(
      '/api/library/shuffle',
      queryParameters: {'limit': limit},
    );
    final raw = r.data ?? const [];
    return raw
        .map((e) => TrackRef.fromJson((e as Map).cast<String, dynamic>()))
        .toList(growable: false);
  }

  /// GET /api/artists/{id}/tracks. Server emits a bare JSON array, so we
  /// type the response as `List<dynamic>` rather than a Map envelope.
  Future<List<TrackRef>> getArtistTracks(String id) async {
    final r = await _dio.get<List<dynamic>>('/api/artists/$id/tracks');
    final raw = r.data ?? const [];
    return raw
        .map((e) => TrackRef.fromJson((e as Map).cast<String, dynamic>()))
        .toList(growable: false);
  }

  /// GET /api/albums/{id}. Returns the album ref alongside its track list
  /// in a single record so screens can render both without a second fetch.
  Future<({AlbumRef album, List<TrackRef> tracks})> getAlbum(String id) async {
    final r = await _dio.get<Map<String, dynamic>>('/api/albums/$id');
    final body = r.data ?? const <String, dynamic>{};
    final tracks = ((body['tracks'] as List?) ?? const [])
        .map((e) => TrackRef.fromJson((e as Map).cast<String, dynamic>()))
        .toList(growable: false);
    return (album: AlbumRef.fromJson(body), tracks: tracks);
  }
}
