-- name: CreatePlaylist :one
INSERT INTO playlists (user_id, name, description, is_public)
VALUES ($1, $2, $3, $4)
RETURNING *;

-- name: GetPlaylist :one
SELECT p.*, u.username AS owner_username
FROM playlists p
JOIN users u ON u.id = p.user_id
WHERE p.id = $1;

-- name: ListPlaylistsForUser :many
-- Owner's playlists (any visibility) + other users' public playlists.
-- Ordered by updated_at desc so newly-edited ones float to the top.
SELECT p.*, u.username AS owner_username
FROM playlists p
JOIN users u ON u.id = p.user_id
WHERE p.user_id = $1 OR p.is_public = true
ORDER BY p.updated_at DESC;

-- name: UpdatePlaylist :one
-- Updates only the fields whose corresponding `updateX` flag is true.
-- The flags let the service layer keep PATCH semantics (only-touch-what-the-caller-sent)
-- without writing N variants.
UPDATE playlists
SET
    name        = CASE WHEN sqlc.arg(update_name)::boolean        THEN sqlc.arg(name)::text        ELSE name        END,
    description = CASE WHEN sqlc.arg(update_description)::boolean THEN sqlc.arg(description)::text ELSE description END,
    is_public   = CASE WHEN sqlc.arg(update_is_public)::boolean   THEN sqlc.arg(is_public)::boolean ELSE is_public   END,
    updated_at  = now()
WHERE id = sqlc.arg(id)
RETURNING *;

-- name: UpdatePlaylistRollups :exec
-- Set track_count + duration_sec from a fresh aggregate. Called after
-- every mutation that touches playlist_tracks. Cheap; the table is small.
UPDATE playlists
SET
    track_count  = (SELECT COUNT(*) FROM playlist_tracks pt WHERE pt.playlist_id = $1),
    duration_sec = (SELECT COALESCE(SUM(pt.duration_sec), 0) FROM playlist_tracks pt WHERE pt.playlist_id = $1),
    updated_at   = now()
WHERE id = $1;

-- name: SetPlaylistCover :exec
UPDATE playlists SET cover_path = $2, updated_at = now() WHERE id = $1;

-- name: DeletePlaylist :one
-- Returns cover_path so the caller can clean up the cached collage on disk.
DELETE FROM playlists WHERE id = $1
RETURNING id, cover_path;

-- name: ListPlaylistTracks :many
-- Joined to tracks for the live track id (the service layer derives the
-- stream URL from it); LEFT JOIN preserves the row when track_id is NULL
-- (track was removed from the library). The denormalized snapshot fields
-- on playlist_tracks remain authoritative for title/artist/album text.
--
-- Deliberately NOT filtered on missing_since (#2527), unlike every browse /
-- discover / mix query. A playlist entry is something the user put here on
-- purpose, so a missing file stays in the list and renders as a dead row
-- rather than silently vanishing; missing_since rides along so the service
-- layer can mark it unplayable.
SELECT pt.*,
       t.id            AS live_track_id,
       t.missing_since AS missing_since,
       albums.id       AS album_id,
       artists.id      AS artist_id
FROM playlist_tracks pt
LEFT JOIN tracks  t       ON t.id       = pt.track_id
LEFT JOIN albums           ON albums.id  = t.album_id
LEFT JOIN artists          ON artists.id = t.artist_id
WHERE pt.playlist_id = $1
ORDER BY pt.position;

-- name: AppendPlaylistTrack :one
-- Inserts at the next available position. Snapshot fields are copied
-- from the tracks/albums/artists join at insert time. tracks.duration_ms
-- is converted to seconds for the snapshot. pick_kind is NULL for manual
-- playlists; the For You builder stamps 'taste'/'fresh' (#1249).
INSERT INTO playlist_tracks (playlist_id, position, track_id, title, artist_name, album_title, duration_sec, pick_kind)
SELECT
    sqlc.arg(playlist_id)::uuid,
    COALESCE((SELECT MAX(position) + 1 FROM playlist_tracks WHERE playlist_id = sqlc.arg(playlist_id)::uuid), 0),
    t.id,
    t.title,
    artists.name,
    albums.title,
    (t.duration_ms / 1000)::integer,
    sqlc.narg(pick_kind)::text
FROM tracks t
JOIN albums  ON albums.id  = t.album_id
JOIN artists ON artists.id = t.artist_id
WHERE t.id = sqlc.arg(track_id)::uuid
RETURNING *;

-- name: DeletePlaylistTrack :exec
-- Two-step: delete the row at `position`, then renumber subsequent rows
-- to close the gap. The renumber is a single UPDATE; the service layer
-- runs both in one transaction.
DELETE FROM playlist_tracks
WHERE playlist_id = $1 AND position = $2;

-- name: RenumberPlaylistTracksAfter :exec
-- Used after DeletePlaylistTrack to close the gap.
UPDATE playlist_tracks
SET position = position - 1
WHERE playlist_id = $1 AND position > $2;

-- name: ListAllPlaylistTracksForCollage :many
-- First N tracks for the collage. Uses LEFT JOIN on albums for the
-- cover_path; rows with NULL cover_path get the glyph fallback.
SELECT pt.position,
       albums.cover_art_path AS album_cover_path
FROM playlist_tracks pt
LEFT JOIN tracks t ON t.id = pt.track_id
LEFT JOIN albums   ON albums.id = t.album_id
WHERE pt.playlist_id = $1
ORDER BY pt.position
LIMIT $2;

-- name: GetPlaylistsByIDs :many
-- Batched lookup used by /api/library/sync to hydrate upsert payloads
-- (#357). Mirror of GetArtistsByIDs.
SELECT * FROM playlists WHERE id = ANY($1::uuid[]);
