package com.fabledsword.minstrel.models

/**
 * Lightweight reference to one playlist (user or system-generated).
 * Mirrors `flutter_client/lib/models/playlist.dart`'s `Playlist`.
 *
 * `systemVariant` discriminates user vs. system playlists — null for
 * user-owned, one of "for_you" / "discover" / "songs_like_artist" / etc.
 * for server-generated mixes. The UI gates "edit / delete / append
 * tracks" affordances on `isSystem` so user playlists are the only
 * mutable ones.
 */
data class PlaylistRef(
    val id: String,
    val userId: String,
    val name: String,
    val description: String = "",
    val isPublic: Boolean = false,
    val systemVariant: String? = null,
    val trackCount: Int = 0,
    val coverUrl: String = "",
    val ownerUsername: String = "",
    /** All member tracks resident in the audio cache — playable fully offline. */
    val fullyCached: Boolean = false,
) {
    val isSystem: Boolean get() = systemVariant != null

    /**
     * Whether the "Regenerate" affordance applies. Mirrors Flutter's
     * `Playlist.refreshable`: every system playlist EXCEPT
     * songs_like_artist (server's *songs-like-X* family doesn't
     * expose a refresh trigger; rebuilds are scheduler-driven).
     */
    val refreshable: Boolean get() = isSystem && systemVariant != "songs_like_artist"

    /**
     * Can't be relied on while offline — either it needs the live server to
     * (re)generate (a refreshable system mix) or not all its tracks are cached.
     * Callers combine this with the current offline state to grey the tile.
     */
    val unavailableOffline: Boolean get() = refreshable || !fullyCached
}

/**
 * One row of a playlist's tracks. Mirrors Flutter's `PlaylistTrack`.
 *
 * `trackId` and `streamUrl` are nullable because the upstream track can
 * be removed from the library while the row stays in the playlist —
 * those tiles render grey + unplayable per Flutter's `isAvailable`
 * convention.
 */
data class PlaylistTrackRef(
    val position: Int,
    val trackId: String?,
    val title: String,
    val albumId: String?,
    val albumTitle: String = "",
    val artistId: String?,
    val artistName: String = "",
    val durationSec: Int = 0,
    val streamUrl: String? = null,
) {
    val isAvailable: Boolean get() = trackId != null

    /**
     * Cover URL derived from the parent album's `/api/albums/{id}/cover`
     * endpoint, using the placeholder host that `BaseUrlInterceptor`
     * rewrites to the live server URL. Same mechanism as
     * [TrackRef.coverUrl] — Coil shares the OkHttp client. Empty when
     * the row has no album binding (rare; usually only for tracks
     * whose upstream album was deleted).
     */
    val coverUrl: String
        get() = albumCoverPath(albumId.orEmpty())
}
