// Mirrors internal/api/playlists.go Playlist + PlaylistDetail wire shapes.
//
// Server distinguishes user playlists (created by the caller) from
// system playlists (server-generated mixes like for_you / discover).
// system_variant is null for user playlists; "for_you" / "discover"
// otherwise. Read-only operations work the same; PATCH/POST/DELETE
// are gated on user-owned playlists.

class Playlist {
  const Playlist({
    required this.id,
    required this.userId,
    required this.name,
    required this.description,
    required this.isPublic,
    required this.systemVariant,
    required this.trackCount,
    required this.coverUrl,
    required this.ownerUsername,
    required this.createdAt,
    required this.updatedAt,
  });

  final String id;
  final String userId;
  final String name;
  final String description;
  final bool isPublic;
  /// "for_you" / "discover" / null
  final String? systemVariant;
  final int trackCount;
  /// Server-emitted URL to the cover; empty when no tracks yet.
  final String coverUrl;
  /// Display name of the owner — for showing other users' public playlists.
  final String ownerUsername;
  final String createdAt;
  final String updatedAt;

  bool get isSystem => systemVariant != null;

  /// Whether this playlist supports the generic by-kind refresh/
  /// shuffle endpoints (#411 R2) — i.e. a singleton system kind.
  /// The server exposes a `refreshable` flag for JSON-sourced
  /// playlists, but the list tiles are drift-cache-sourced (no
  /// migration just for this), so derive it: every system kind is a
  /// singleton except songs_like_artist (multi-per-user). This rule
  /// holds for For-You/Discover and all planned discovery mixes; if
  /// a future non-singleton kind appears, extend the exclusion.
  bool get refreshable => isSystem && systemVariant != 'songs_like_artist';

  factory Playlist.fromJson(Map<String, dynamic> j) => Playlist(
        id: j['id'] as String,
        userId: j['user_id'] as String? ?? '',
        name: j['name'] as String? ?? '',
        description: j['description'] as String? ?? '',
        isPublic: j['is_public'] as bool? ?? false,
        systemVariant: j['system_variant'] as String?,
        trackCount: (j['track_count'] as num?)?.toInt() ?? 0,
        coverUrl: j['cover_url'] as String? ?? '',
        ownerUsername: j['owner_username'] as String? ?? '',
        createdAt: j['created_at'] as String? ?? '',
        updatedAt: j['updated_at'] as String? ?? '',
      );
}

/// Playlist row as returned in PlaylistDetail.tracks. Each row carries
/// the track's display fields directly so the client doesn't need a
/// separate /api/tracks/{id} fetch per row.
///
/// track_id is nullable: when the upstream track has been removed from
/// the library, the row remains in the playlist with its title/artist
/// preserved but track_id=null and stream_url=null. UI should render
/// these greyed-out and unplayable.
class PlaylistTrack {
  const PlaylistTrack({
    required this.position,
    required this.trackId,
    required this.title,
    required this.albumId,
    required this.albumTitle,
    required this.artistId,
    required this.artistName,
    required this.durationSec,
    required this.streamUrl,
  });

  final int position;
  final String? trackId;
  final String title;
  final String? albumId;
  final String albumTitle;
  final String? artistId;
  final String artistName;
  final int durationSec;
  final String? streamUrl;

  bool get isAvailable => trackId != null;

  factory PlaylistTrack.fromJson(Map<String, dynamic> j) => PlaylistTrack(
        position: (j['position'] as num?)?.toInt() ?? 0,
        trackId: j['track_id'] as String?,
        title: j['title'] as String? ?? '',
        albumId: j['album_id'] as String?,
        albumTitle: j['album_title'] as String? ?? '',
        artistId: j['artist_id'] as String?,
        artistName: j['artist_name'] as String? ?? '',
        durationSec: (j['duration_sec'] as num?)?.toInt() ?? 0,
        streamUrl: j['stream_url'] as String?,
      );
}

class PlaylistDetail {
  const PlaylistDetail({required this.playlist, required this.tracks});

  final Playlist playlist;
  final List<PlaylistTrack> tracks;

  factory PlaylistDetail.fromJson(Map<String, dynamic> j) {
    final tracksRaw = (j['tracks'] as List?) ?? const [];
    return PlaylistDetail(
      playlist: Playlist.fromJson(j),
      tracks: tracksRaw
          .map((e) => PlaylistTrack.fromJson((e as Map).cast<String, dynamic>()))
          .toList(growable: false),
    );
  }
}
