// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.31.1
// source: playlists.sql

package dbq

import (
	"context"

	"github.com/jackc/pgx/v5/pgtype"
)

const appendPlaylistTrack = `-- name: AppendPlaylistTrack :one
INSERT INTO playlist_tracks (playlist_id, position, track_id, title, artist_name, album_title, duration_sec, pick_kind)
SELECT
    $1::uuid,
    COALESCE((SELECT MAX(position) + 1 FROM playlist_tracks WHERE playlist_id = $1::uuid), 0),
    t.id,
    t.title,
    artists.name,
    albums.title,
    (t.duration_ms / 1000)::integer,
    $2::text
FROM tracks t
JOIN albums  ON albums.id  = t.album_id
JOIN artists ON artists.id = t.artist_id
WHERE t.id = $3::uuid
RETURNING playlist_id, position, track_id, title, artist_name, album_title, duration_sec, added_at, pick_kind
`

type AppendPlaylistTrackParams struct {
	PlaylistID pgtype.UUID
	PickKind   *string
	TrackID    pgtype.UUID
}

// 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).
func (q *Queries) AppendPlaylistTrack(ctx context.Context, arg AppendPlaylistTrackParams) (PlaylistTrack, error) {
	row := q.db.QueryRow(ctx, appendPlaylistTrack, arg.PlaylistID, arg.PickKind, arg.TrackID)
	var i PlaylistTrack
	err := row.Scan(
		&i.PlaylistID,
		&i.Position,
		&i.TrackID,
		&i.Title,
		&i.ArtistName,
		&i.AlbumTitle,
		&i.DurationSec,
		&i.AddedAt,
		&i.PickKind,
	)
	return i, err
}

const createPlaylist = `-- name: CreatePlaylist :one
INSERT INTO playlists (user_id, name, description, is_public)
VALUES ($1, $2, $3, $4)
RETURNING id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id
`

type CreatePlaylistParams struct {
	UserID      pgtype.UUID
	Name        string
	Description string
	IsPublic    bool
}

func (q *Queries) CreatePlaylist(ctx context.Context, arg CreatePlaylistParams) (Playlist, error) {
	row := q.db.QueryRow(ctx, createPlaylist,
		arg.UserID,
		arg.Name,
		arg.Description,
		arg.IsPublic,
	)
	var i Playlist
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.Name,
		&i.Description,
		&i.IsPublic,
		&i.CoverPath,
		&i.TrackCount,
		&i.DurationSec,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.Kind,
		&i.SystemVariant,
		&i.SeedArtistID,
	)
	return i, err
}

const deletePlaylist = `-- name: DeletePlaylist :one
DELETE FROM playlists WHERE id = $1
RETURNING id, cover_path
`

type DeletePlaylistRow struct {
	ID        pgtype.UUID
	CoverPath *string
}

// Returns cover_path so the caller can clean up the cached collage on disk.
func (q *Queries) DeletePlaylist(ctx context.Context, id pgtype.UUID) (DeletePlaylistRow, error) {
	row := q.db.QueryRow(ctx, deletePlaylist, id)
	var i DeletePlaylistRow
	err := row.Scan(&i.ID, &i.CoverPath)
	return i, err
}

const deletePlaylistTrack = `-- name: DeletePlaylistTrack :exec
DELETE FROM playlist_tracks
WHERE playlist_id = $1 AND position = $2
`

type DeletePlaylistTrackParams struct {
	PlaylistID pgtype.UUID
	Position   int32
}

// 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.
func (q *Queries) DeletePlaylistTrack(ctx context.Context, arg DeletePlaylistTrackParams) error {
	_, err := q.db.Exec(ctx, deletePlaylistTrack, arg.PlaylistID, arg.Position)
	return err
}

const getPlaylist = `-- name: GetPlaylist :one
SELECT p.id, p.user_id, p.name, p.description, p.is_public, p.cover_path, p.track_count, p.duration_sec, p.created_at, p.updated_at, p.kind, p.system_variant, p.seed_artist_id, u.username AS owner_username
FROM playlists p
JOIN users u ON u.id = p.user_id
WHERE p.id = $1
`

type GetPlaylistRow struct {
	ID            pgtype.UUID
	UserID        pgtype.UUID
	Name          string
	Description   string
	IsPublic      bool
	CoverPath     *string
	TrackCount    int32
	DurationSec   int32
	CreatedAt     pgtype.Timestamptz
	UpdatedAt     pgtype.Timestamptz
	Kind          string
	SystemVariant *string
	SeedArtistID  pgtype.UUID
	OwnerUsername string
}

func (q *Queries) GetPlaylist(ctx context.Context, id pgtype.UUID) (GetPlaylistRow, error) {
	row := q.db.QueryRow(ctx, getPlaylist, id)
	var i GetPlaylistRow
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.Name,
		&i.Description,
		&i.IsPublic,
		&i.CoverPath,
		&i.TrackCount,
		&i.DurationSec,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.Kind,
		&i.SystemVariant,
		&i.SeedArtistID,
		&i.OwnerUsername,
	)
	return i, err
}

const getPlaylistsByIDs = `-- name: GetPlaylistsByIDs :many
SELECT id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id FROM playlists WHERE id = ANY($1::uuid[])
`

// Batched lookup used by /api/library/sync to hydrate upsert payloads
// (#357). Mirror of GetArtistsByIDs.
func (q *Queries) GetPlaylistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Playlist, error) {
	rows, err := q.db.Query(ctx, getPlaylistsByIDs, dollar_1)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []Playlist
	for rows.Next() {
		var i Playlist
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.Name,
			&i.Description,
			&i.IsPublic,
			&i.CoverPath,
			&i.TrackCount,
			&i.DurationSec,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.Kind,
			&i.SystemVariant,
			&i.SeedArtistID,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listAllPlaylistTracksForCollage = `-- name: ListAllPlaylistTracksForCollage :many
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
`

type ListAllPlaylistTracksForCollageParams struct {
	PlaylistID pgtype.UUID
	Limit      int32
}

type ListAllPlaylistTracksForCollageRow struct {
	Position       int32
	AlbumCoverPath *string
}

// First N tracks for the collage. Uses LEFT JOIN on albums for the
// cover_path; rows with NULL cover_path get the glyph fallback.
func (q *Queries) ListAllPlaylistTracksForCollage(ctx context.Context, arg ListAllPlaylistTracksForCollageParams) ([]ListAllPlaylistTracksForCollageRow, error) {
	rows, err := q.db.Query(ctx, listAllPlaylistTracksForCollage, arg.PlaylistID, arg.Limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListAllPlaylistTracksForCollageRow
	for rows.Next() {
		var i ListAllPlaylistTracksForCollageRow
		if err := rows.Scan(&i.Position, &i.AlbumCoverPath); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listPlaylistTracks = `-- name: ListPlaylistTracks :many
SELECT pt.playlist_id, pt.position, pt.track_id, pt.title, pt.artist_name, pt.album_title, pt.duration_sec, pt.added_at, pt.pick_kind,
       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
`

type ListPlaylistTracksRow struct {
	PlaylistID   pgtype.UUID
	Position     int32
	TrackID      pgtype.UUID
	Title        string
	ArtistName   string
	AlbumTitle   string
	DurationSec  int32
	AddedAt      pgtype.Timestamptz
	PickKind     *string
	LiveTrackID  pgtype.UUID
	MissingSince pgtype.Timestamptz
	AlbumID      pgtype.UUID
	ArtistID     pgtype.UUID
}

// 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.
func (q *Queries) ListPlaylistTracks(ctx context.Context, playlistID pgtype.UUID) ([]ListPlaylistTracksRow, error) {
	rows, err := q.db.Query(ctx, listPlaylistTracks, playlistID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListPlaylistTracksRow
	for rows.Next() {
		var i ListPlaylistTracksRow
		if err := rows.Scan(
			&i.PlaylistID,
			&i.Position,
			&i.TrackID,
			&i.Title,
			&i.ArtistName,
			&i.AlbumTitle,
			&i.DurationSec,
			&i.AddedAt,
			&i.PickKind,
			&i.LiveTrackID,
			&i.MissingSince,
			&i.AlbumID,
			&i.ArtistID,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listPlaylistsForUser = `-- name: ListPlaylistsForUser :many
SELECT p.id, p.user_id, p.name, p.description, p.is_public, p.cover_path, p.track_count, p.duration_sec, p.created_at, p.updated_at, p.kind, p.system_variant, p.seed_artist_id, 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
`

type ListPlaylistsForUserRow struct {
	ID            pgtype.UUID
	UserID        pgtype.UUID
	Name          string
	Description   string
	IsPublic      bool
	CoverPath     *string
	TrackCount    int32
	DurationSec   int32
	CreatedAt     pgtype.Timestamptz
	UpdatedAt     pgtype.Timestamptz
	Kind          string
	SystemVariant *string
	SeedArtistID  pgtype.UUID
	OwnerUsername string
}

// Owner's playlists (any visibility) + other users' public playlists.
// Ordered by updated_at desc so newly-edited ones float to the top.
func (q *Queries) ListPlaylistsForUser(ctx context.Context, userID pgtype.UUID) ([]ListPlaylistsForUserRow, error) {
	rows, err := q.db.Query(ctx, listPlaylistsForUser, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListPlaylistsForUserRow
	for rows.Next() {
		var i ListPlaylistsForUserRow
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.Name,
			&i.Description,
			&i.IsPublic,
			&i.CoverPath,
			&i.TrackCount,
			&i.DurationSec,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.Kind,
			&i.SystemVariant,
			&i.SeedArtistID,
			&i.OwnerUsername,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const renumberPlaylistTracksAfter = `-- name: RenumberPlaylistTracksAfter :exec
UPDATE playlist_tracks
SET position = position - 1
WHERE playlist_id = $1 AND position > $2
`

type RenumberPlaylistTracksAfterParams struct {
	PlaylistID pgtype.UUID
	Position   int32
}

// Used after DeletePlaylistTrack to close the gap.
func (q *Queries) RenumberPlaylistTracksAfter(ctx context.Context, arg RenumberPlaylistTracksAfterParams) error {
	_, err := q.db.Exec(ctx, renumberPlaylistTracksAfter, arg.PlaylistID, arg.Position)
	return err
}

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

type SetPlaylistCoverParams struct {
	ID        pgtype.UUID
	CoverPath *string
}

func (q *Queries) SetPlaylistCover(ctx context.Context, arg SetPlaylistCoverParams) error {
	_, err := q.db.Exec(ctx, setPlaylistCover, arg.ID, arg.CoverPath)
	return err
}

const updatePlaylist = `-- name: UpdatePlaylist :one
UPDATE playlists
SET
    name        = CASE WHEN $1::boolean        THEN $2::text        ELSE name        END,
    description = CASE WHEN $3::boolean THEN $4::text ELSE description END,
    is_public   = CASE WHEN $5::boolean   THEN $6::boolean ELSE is_public   END,
    updated_at  = now()
WHERE id = $7
RETURNING id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id
`

type UpdatePlaylistParams struct {
	UpdateName        bool
	Name              string
	UpdateDescription bool
	Description       string
	UpdateIsPublic    bool
	IsPublic          bool
	ID                pgtype.UUID
}

// 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.
func (q *Queries) UpdatePlaylist(ctx context.Context, arg UpdatePlaylistParams) (Playlist, error) {
	row := q.db.QueryRow(ctx, updatePlaylist,
		arg.UpdateName,
		arg.Name,
		arg.UpdateDescription,
		arg.Description,
		arg.UpdateIsPublic,
		arg.IsPublic,
		arg.ID,
	)
	var i Playlist
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.Name,
		&i.Description,
		&i.IsPublic,
		&i.CoverPath,
		&i.TrackCount,
		&i.DurationSec,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.Kind,
		&i.SystemVariant,
		&i.SeedArtistID,
	)
	return i, err
}

const updatePlaylistRollups = `-- name: UpdatePlaylistRollups :exec
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
`

// Set track_count + duration_sec from a fresh aggregate. Called after
// every mutation that touches playlist_tracks. Cheap; the table is small.
func (q *Queries) UpdatePlaylistRollups(ctx context.Context, playlistID pgtype.UUID) error {
	_, err := q.db.Exec(ctx, updatePlaylistRollups, playlistID)
	return err
}
