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

package dbq

import (
	"context"

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

const listCrossUserLikedTracksForDiscover = `-- name: ListCrossUserLikedTracksForDiscover :many
SELECT t.id, t.album_id, t.artist_id
  FROM general_likes gl
  JOIN tracks t ON t.id = gl.track_id
 WHERE t.missing_since IS NULL   -- #2523: never offer a file that is gone
   AND gl.user_id != $1
   AND NOT EXISTS (
       SELECT 1 FROM play_events pe
        WHERE pe.user_id = $1
          AND pe.track_id = t.id
          AND pe.was_skipped = false
   )
   AND NOT EXISTS (
       SELECT 1 FROM general_likes ml
        WHERE ml.user_id = $1 AND ml.track_id = t.id
   )
   AND NOT EXISTS (
       SELECT 1 FROM lidarr_quarantine q
        WHERE q.user_id = $1 AND q.track_id = t.id
   )
 GROUP BY t.id, t.album_id, t.artist_id
 ORDER BY md5(t.id::text || $2::text)
 LIMIT 60
`

type ListCrossUserLikedTracksForDiscoverParams struct {
	UserID  pgtype.UUID
	Column2 string
}

type ListCrossUserLikedTracksForDiscoverRow struct {
	ID       pgtype.UUID
	AlbumID  pgtype.UUID
	ArtistID pgtype.UUID
}

// Tracks any OTHER user has liked, that this user hasn't played,
// liked, or had quarantined. On single-user servers this returns 0
// rows (the caller's slot redistribution rolls the deficit into the
// other two buckets).
// $1 = user_id, $2 = date string for md5 ordering.
//
// GROUP BY (not SELECT DISTINCT) so the md5 ORDER BY expression need
// not appear in the select list — Postgres rejects DISTINCT + ORDER BY
// by-expression at plan time (SQLSTATE 42P10), which previously caused
// the entire Discover build to fail silently.
func (q *Queries) ListCrossUserLikedTracksForDiscover(ctx context.Context, arg ListCrossUserLikedTracksForDiscoverParams) ([]ListCrossUserLikedTracksForDiscoverRow, error) {
	rows, err := q.db.Query(ctx, listCrossUserLikedTracksForDiscover, arg.UserID, arg.Column2)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListCrossUserLikedTracksForDiscoverRow
	for rows.Next() {
		var i ListCrossUserLikedTracksForDiscoverRow
		if err := rows.Scan(&i.ID, &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 listDormantArtistTracksForDiscover = `-- name: ListDormantArtistTracksForDiscover :many

WITH user_artist_counts AS (
    SELECT t.artist_id, COUNT(*) AS play_count
      FROM play_events pe
      JOIN tracks t ON t.id = pe.track_id
     WHERE pe.user_id = $1
       AND pe.was_skipped = false
     GROUP BY t.artist_id
),
dormant_artists AS (
    SELECT a.id
      FROM artists a
      LEFT JOIN user_artist_counts uac ON uac.artist_id = a.id
     WHERE COALESCE(uac.play_count, 0) < 10
)
SELECT t.id, t.album_id, t.artist_id
  FROM tracks t
  JOIN dormant_artists da ON da.id = t.artist_id
 WHERE t.missing_since IS NULL   -- #2523: never offer a file that is gone
   AND NOT EXISTS (
       SELECT 1 FROM play_events pe
        WHERE pe.user_id = $1
          AND pe.track_id = t.id
          AND pe.was_skipped = false
   )
   AND NOT EXISTS (
       SELECT 1 FROM general_likes gl
        WHERE gl.user_id = $1 AND gl.track_id = t.id
   )
   AND NOT EXISTS (
       SELECT 1 FROM lidarr_quarantine q
        WHERE q.user_id = $1 AND q.track_id = t.id
   )
 ORDER BY md5(t.id::text || $2::text)
 LIMIT 80
`

type ListDormantArtistTracksForDiscoverParams struct {
	UserID  pgtype.UUID
	Column2 string
}

type ListDormantArtistTracksForDiscoverRow struct {
	ID       pgtype.UUID
	AlbumID  pgtype.UUID
	ArtistID pgtype.UUID
}

// Discover playlist bucket queries. Each returns up to its LIMIT
// count of (track_id, album_id, artist_id) triples ordered
// daily-deterministically via md5(track_id || dateStr). The Go-side
// bucket allocator (internal/playlists/discover.go) applies
// per-album (<=2) and per-artist (<=3) caps then redistributes any
// bucket deficit equally across the others. LIMIT values are
// generous so the caps + redistribution have headroom.
// Tracks whose artist this user has played fewer than 10 times total.
// The artist threshold defines "dormant" — we want to surface the
// long tail of artists in the user's library that they rarely listen
// to. Excludes tracks the user has played, liked, or has quarantined.
// $1 = user_id, $2 = date string for md5 ordering.
func (q *Queries) ListDormantArtistTracksForDiscover(ctx context.Context, arg ListDormantArtistTracksForDiscoverParams) ([]ListDormantArtistTracksForDiscoverRow, error) {
	rows, err := q.db.Query(ctx, listDormantArtistTracksForDiscover, arg.UserID, arg.Column2)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListDormantArtistTracksForDiscoverRow
	for rows.Next() {
		var i ListDormantArtistTracksForDiscoverRow
		if err := rows.Scan(&i.ID, &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 listRandomUnheardTracksForDiscover = `-- name: ListRandomUnheardTracksForDiscover :many
SELECT t.id, t.album_id, t.artist_id
  FROM tracks t
 WHERE t.missing_since IS NULL   -- #2523: never offer a file that is gone
   AND NOT EXISTS (
       SELECT 1 FROM play_events pe
        WHERE pe.user_id = $1
          AND pe.track_id = t.id
          AND pe.was_skipped = false
   )
   AND NOT EXISTS (
       SELECT 1 FROM general_likes gl
        WHERE gl.user_id = $1 AND gl.track_id = t.id
   )
   AND NOT EXISTS (
       SELECT 1 FROM lidarr_quarantine q
        WHERE q.user_id = $1 AND q.track_id = t.id
   )
 ORDER BY md5(t.id::text || $2::text)
 LIMIT 200
`

type ListRandomUnheardTracksForDiscoverParams struct {
	UserID  pgtype.UUID
	Column2 string
}

type ListRandomUnheardTracksForDiscoverRow struct {
	ID       pgtype.UUID
	AlbumID  pgtype.UUID
	ArtistID pgtype.UUID
}

// Random sample from the entire library. Same exclusion filters as
// the other two buckets. The generous LIMIT (200) gives the caller
// headroom for the per-album/artist caps and the slot redistribution.
// $1 = user_id, $2 = date string for md5 ordering.
func (q *Queries) ListRandomUnheardTracksForDiscover(ctx context.Context, arg ListRandomUnheardTracksForDiscoverParams) ([]ListRandomUnheardTracksForDiscoverRow, error) {
	rows, err := q.db.Query(ctx, listRandomUnheardTracksForDiscover, arg.UserID, arg.Column2)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListRandomUnheardTracksForDiscoverRow
	for rows.Next() {
		var i ListRandomUnheardTracksForDiscoverRow
		if err := rows.Scan(&i.ID, &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 listTasteUnheardTracksForDiscover = `-- name: ListTasteUnheardTracksForDiscover :many
SELECT t.id, t.album_id, t.artist_id
  FROM tracks t
  JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true
  JOIN taste_profile_tags nt ON nt.user_id = $1 AND trim(g_split.g) = nt.tag
 WHERE t.missing_since IS NULL   -- #2523: never offer a file that is gone
   AND nt.weight > 0
   AND trim(g_split.g) <> ''
   AND NOT EXISTS (
       SELECT 1 FROM play_events pe
        WHERE pe.user_id = $1
          AND pe.track_id = t.id
          AND pe.was_skipped = false
   )
   AND NOT EXISTS (
       SELECT 1 FROM general_likes gl
        WHERE gl.user_id = $1 AND gl.track_id = t.id
   )
   AND NOT EXISTS (
       SELECT 1 FROM lidarr_quarantine q
        WHERE q.user_id = $1 AND q.track_id = t.id
   )
 GROUP BY t.id, t.album_id, t.artist_id
 ORDER BY SUM(nt.weight) DESC, md5(t.id::text || $2::text)
 LIMIT 120
`

type ListTasteUnheardTracksForDiscoverParams struct {
	UserID  pgtype.UUID
	Column2 string
}

type ListTasteUnheardTracksForDiscoverRow struct {
	ID       pgtype.UUID
	AlbumID  pgtype.UUID
	ArtistID pgtype.UUID
}

// Taste-targeted novelty: unheard tracks whose genres overlap the user's
// taste-profile tags (taste_profile_tags, #796), ranked by summed tag
// weight — "new to you, but your vibe" rather than the crude random arm.
// Genres live inline on tracks.genre as a delimited string, split the
// same way the radio tag_overlap arm does (regexp_split_to_table on
// [;,]). Same exclusion filters as the other buckets. Returns nothing
// when the user has no taste tags yet (cold start), so the caller
// redistributes its slots to the other buckets. Stamped 'taste_unheard'.
// $1 = user_id, $2 = date string for md5 tiebreak ordering.
func (q *Queries) ListTasteUnheardTracksForDiscover(ctx context.Context, arg ListTasteUnheardTracksForDiscoverParams) ([]ListTasteUnheardTracksForDiscoverRow, error) {
	rows, err := q.db.Query(ctx, listTasteUnheardTracksForDiscover, arg.UserID, arg.Column2)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListTasteUnheardTracksForDiscoverRow
	for rows.Next() {
		var i ListTasteUnheardTracksForDiscoverRow
		if err := rows.Scan(&i.ID, &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
}
