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

package db

import (
	"context"
	"database/sql"
	"time"
)

const countPendingAnimeFetchRetries = `-- name: CountPendingAnimeFetchRetries :one
SELECT COUNT(*)
FROM anime_fetch_retry
WHERE next_retry_at <= CURRENT_TIMESTAMP
`

func (q *Queries) CountPendingAnimeFetchRetries(ctx context.Context) (int64, error) {
	row := q.db.QueryRowContext(ctx, countPendingAnimeFetchRetries)
	var count int64
	err := row.Scan(&count)
	return count, err
}

const createAPIToken = `-- name: CreateAPIToken :one
INSERT INTO api_token (id, user_id, token_hash, name)
VALUES (?, ?, ?, ?)
RETURNING id, user_id, token_hash, name, created_at, last_used_at, revoked_at
`

type CreateAPITokenParams struct {
	ID        string `json:"id"`
	UserID    string `json:"user_id"`
	TokenHash string `json:"token_hash"`
	Name      string `json:"name"`
}

func (q *Queries) CreateAPIToken(ctx context.Context, arg CreateAPITokenParams) (ApiToken, error) {
	row := q.db.QueryRowContext(ctx, createAPIToken,
		arg.ID,
		arg.UserID,
		arg.TokenHash,
		arg.Name,
	)
	var i ApiToken
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.TokenHash,
		&i.Name,
		&i.CreatedAt,
		&i.LastUsedAt,
		&i.RevokedAt,
	)
	return i, err
}

const createAuditLog = `-- name: CreateAuditLog :one
INSERT INTO audit_log (id, user_id, action, resource_type, resource_id, ip, user_agent, metadata_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, occurred_at, user_id, "action", resource_type, resource_id, ip, user_agent, metadata_json
`

type CreateAuditLogParams struct {
	ID           string         `json:"id"`
	UserID       sql.NullString `json:"user_id"`
	Action       string         `json:"action"`
	ResourceType sql.NullString `json:"resource_type"`
	ResourceID   sql.NullString `json:"resource_id"`
	Ip           sql.NullString `json:"ip"`
	UserAgent    sql.NullString `json:"user_agent"`
	MetadataJson sql.NullString `json:"metadata_json"`
}

func (q *Queries) CreateAuditLog(ctx context.Context, arg CreateAuditLogParams) (AuditLog, error) {
	row := q.db.QueryRowContext(ctx, createAuditLog,
		arg.ID,
		arg.UserID,
		arg.Action,
		arg.ResourceType,
		arg.ResourceID,
		arg.Ip,
		arg.UserAgent,
		arg.MetadataJson,
	)
	var i AuditLog
	err := row.Scan(
		&i.ID,
		&i.OccurredAt,
		&i.UserID,
		&i.Action,
		&i.ResourceType,
		&i.ResourceID,
		&i.Ip,
		&i.UserAgent,
		&i.MetadataJson,
	)
	return i, err
}

const createSession = `-- name: CreateSession :one
INSERT INTO session (id, user_id, expires_at)
VALUES (?, ?, ?)
RETURNING id, user_id, expires_at, created_at
`

type CreateSessionParams struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	ExpiresAt time.Time `json:"expires_at"`
}

func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
	row := q.db.QueryRowContext(ctx, createSession, arg.ID, arg.UserID, arg.ExpiresAt)
	var i Session
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.ExpiresAt,
		&i.CreatedAt,
	)
	return i, err
}

const deleteAnimeFetchRetry = `-- name: DeleteAnimeFetchRetry :exec
DELETE FROM anime_fetch_retry
WHERE anime_id = ?
`

func (q *Queries) DeleteAnimeFetchRetry(ctx context.Context, animeID int64) error {
	_, err := q.db.ExecContext(ctx, deleteAnimeFetchRetry, animeID)
	return err
}

const deleteContinueWatchingEntry = `-- name: DeleteContinueWatchingEntry :exec
DELETE FROM continue_watching_entry
WHERE user_id = ? AND anime_id = ?
`

type DeleteContinueWatchingEntryParams struct {
	UserID  string `json:"user_id"`
	AnimeID int64  `json:"anime_id"`
}

func (q *Queries) DeleteContinueWatchingEntry(ctx context.Context, arg DeleteContinueWatchingEntryParams) error {
	_, err := q.db.ExecContext(ctx, deleteContinueWatchingEntry, arg.UserID, arg.AnimeID)
	return err
}

const deleteExpiredJikanCache = `-- name: DeleteExpiredJikanCache :exec
DELETE FROM jikan_cache WHERE datetime(expires_at) <= CURRENT_TIMESTAMP
`

func (q *Queries) DeleteExpiredJikanCache(ctx context.Context) error {
	_, err := q.db.ExecContext(ctx, deleteExpiredJikanCache)
	return err
}

const deleteSession = `-- name: DeleteSession :exec
DELETE FROM session WHERE id = ?
`

func (q *Queries) DeleteSession(ctx context.Context, id string) error {
	_, err := q.db.ExecContext(ctx, deleteSession, id)
	return err
}

const deleteWatchListEntry = `-- name: DeleteWatchListEntry :exec
DELETE FROM watch_list_entry
WHERE user_id = ? AND anime_id = ?
`

type DeleteWatchListEntryParams struct {
	UserID  string `json:"user_id"`
	AnimeID int64  `json:"anime_id"`
}

func (q *Queries) DeleteWatchListEntry(ctx context.Context, arg DeleteWatchListEntryParams) error {
	_, err := q.db.ExecContext(ctx, deleteWatchListEntry, arg.UserID, arg.AnimeID)
	return err
}

const enqueueAnimeFetchRetry = `-- name: EnqueueAnimeFetchRetry :exec
INSERT INTO anime_fetch_retry (anime_id, attempts, next_retry_at, last_error, updated_at)
VALUES (?, 0, CURRENT_TIMESTAMP, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
    next_retry_at = CASE
        WHEN anime_fetch_retry.next_retry_at > CURRENT_TIMESTAMP THEN anime_fetch_retry.next_retry_at
        ELSE CURRENT_TIMESTAMP
    END,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP
`

type EnqueueAnimeFetchRetryParams struct {
	AnimeID   int64  `json:"anime_id"`
	LastError string `json:"last_error"`
}

func (q *Queries) EnqueueAnimeFetchRetry(ctx context.Context, arg EnqueueAnimeFetchRetryParams) error {
	_, err := q.db.ExecContext(ctx, enqueueAnimeFetchRetry, arg.AnimeID, arg.LastError)
	return err
}

const getAPITokenByHash = `-- name: GetAPITokenByHash :one
SELECT id, user_id, token_hash, name, created_at, last_used_at, revoked_at FROM api_token
WHERE token_hash = ? AND revoked_at IS NULL
LIMIT 1
`

func (q *Queries) GetAPITokenByHash(ctx context.Context, tokenHash string) (ApiToken, error) {
	row := q.db.QueryRowContext(ctx, getAPITokenByHash, tokenHash)
	var i ApiToken
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.TokenHash,
		&i.Name,
		&i.CreatedAt,
		&i.LastUsedAt,
		&i.RevokedAt,
	)
	return i, err
}

const getAllCachedAnime = `-- name: GetAllCachedAnime :many
SELECT data FROM jikan_cache
WHERE key LIKE 'anime:%' LIMIT 1000
`

func (q *Queries) GetAllCachedAnime(ctx context.Context) ([]string, error) {
	rows, err := q.db.QueryContext(ctx, getAllCachedAnime)
	if err != nil {
		return nil, err
	}
	var items []string
	for rows.Next() {
		var data string
		if err := rows.Scan(&data); err != nil {
			return nil, err
		}
		items = append(items, data)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getAnime = `-- name: GetAnime :one
SELECT id, title_original, image_url, created_at, title_english, title_japanese, airing, status, relations_synced_at, duration_seconds FROM anime WHERE id = ? LIMIT 1
`

func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
	row := q.db.QueryRowContext(ctx, getAnime, id)
	var i Anime
	err := row.Scan(
		&i.ID,
		&i.TitleOriginal,
		&i.ImageUrl,
		&i.CreatedAt,
		&i.TitleEnglish,
		&i.TitleJapanese,
		&i.Airing,
		&i.Status,
		&i.RelationsSyncedAt,
		&i.DurationSeconds,
	)
	return i, err
}

const getAnimeNeedingRelationSync = `-- name: GetAnimeNeedingRelationSync :many
WITH RECURSIVE sequel_chain AS (
    SELECT a.id, a.title_original, a.relations_synced_at, w.updated_at as base_updated_at, 0 as depth
    FROM watch_list_entry w
    JOIN anime a ON w.anime_id = a.id
    WHERE w.status IN ('completed', 'watching')
    
    UNION
    
    SELECT a.id, a.title_original, a.relations_synced_at, sc.base_updated_at, sc.depth + 1
    FROM sequel_chain sc
    JOIN anime_relation r ON sc.id = r.anime_id AND r.relation_type = 'Sequel'
    JOIN anime a ON r.related_anime_id = a.id
    WHERE sc.depth < 10
)
SELECT id, title_original
FROM sequel_chain
WHERE relations_synced_at IS NULL OR relations_synced_at < datetime('now', '-7 days')
GROUP BY id, title_original
ORDER BY MAX(base_updated_at) DESC, MIN(depth) ASC
LIMIT 50
`

type GetAnimeNeedingRelationSyncRow struct {
	ID            int64  `json:"id"`
	TitleOriginal string `json:"title_original"`
}

func (q *Queries) GetAnimeNeedingRelationSync(ctx context.Context) ([]GetAnimeNeedingRelationSyncRow, error) {
	rows, err := q.db.QueryContext(ctx, getAnimeNeedingRelationSync)
	if err != nil {
		return nil, err
	}
	var items []GetAnimeNeedingRelationSyncRow
	for rows.Next() {
		var i GetAnimeNeedingRelationSyncRow
		if err := rows.Scan(&i.ID, &i.TitleOriginal); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getAuditLogsForUser = `-- name: GetAuditLogsForUser :many
SELECT id, occurred_at, user_id, "action", resource_type, resource_id, ip, user_agent, metadata_json
FROM audit_log
WHERE user_id = ?
ORDER BY occurred_at DESC
LIMIT ?
`

type GetAuditLogsForUserParams struct {
	UserID sql.NullString `json:"user_id"`
	Limit  int64          `json:"limit"`
}

func (q *Queries) GetAuditLogsForUser(ctx context.Context, arg GetAuditLogsForUserParams) ([]AuditLog, error) {
	rows, err := q.db.QueryContext(ctx, getAuditLogsForUser, arg.UserID, arg.Limit)
	if err != nil {
		return nil, err
	}
	var items []AuditLog
	for rows.Next() {
		var i AuditLog
		if err := rows.Scan(
			&i.ID,
			&i.OccurredAt,
			&i.UserID,
			&i.Action,
			&i.ResourceType,
			&i.ResourceID,
			&i.Ip,
			&i.UserAgent,
			&i.MetadataJson,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getContinueWatchingEntries = `-- name: GetContinueWatchingEntries :many
SELECT
    c.id,
    c.user_id,
    c.anime_id,
    c.current_episode,
    c.current_time_seconds,
    c.duration_seconds,
    c.created_at,
    c.updated_at,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.duration_seconds as anime_duration_seconds
FROM continue_watching_entry c
JOIN anime a ON c.anime_id = a.id
WHERE c.user_id = ?
ORDER BY c.updated_at DESC
`

type GetContinueWatchingEntriesRow struct {
	ID                   string          `json:"id"`
	UserID               string          `json:"user_id"`
	AnimeID              int64           `json:"anime_id"`
	CurrentEpisode       sql.NullInt64   `json:"current_episode"`
	CurrentTimeSeconds   float64         `json:"current_time_seconds"`
	DurationSeconds      sql.NullFloat64 `json:"duration_seconds"`
	CreatedAt            time.Time       `json:"created_at"`
	UpdatedAt            time.Time       `json:"updated_at"`
	TitleOriginal        string          `json:"title_original"`
	TitleEnglish         sql.NullString  `json:"title_english"`
	TitleJapanese        sql.NullString  `json:"title_japanese"`
	ImageUrl             string          `json:"image_url"`
	AnimeDurationSeconds sql.NullFloat64 `json:"anime_duration_seconds"`
}

func (q *Queries) GetContinueWatchingEntries(ctx context.Context, userID string) ([]GetContinueWatchingEntriesRow, error) {
	rows, err := q.db.QueryContext(ctx, getContinueWatchingEntries, userID)
	if err != nil {
		return nil, err
	}
	var items []GetContinueWatchingEntriesRow
	for rows.Next() {
		var i GetContinueWatchingEntriesRow
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.AnimeID,
			&i.CurrentEpisode,
			&i.CurrentTimeSeconds,
			&i.DurationSeconds,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.TitleOriginal,
			&i.TitleEnglish,
			&i.TitleJapanese,
			&i.ImageUrl,
			&i.AnimeDurationSeconds,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getContinueWatchingEntry = `-- name: GetContinueWatchingEntry :one
SELECT id, user_id, anime_id, current_episode, current_time_seconds, created_at, updated_at, duration_seconds FROM continue_watching_entry
WHERE user_id = ? AND anime_id = ? LIMIT 1
`

type GetContinueWatchingEntryParams struct {
	UserID  string `json:"user_id"`
	AnimeID int64  `json:"anime_id"`
}

func (q *Queries) GetContinueWatchingEntry(ctx context.Context, arg GetContinueWatchingEntryParams) (ContinueWatchingEntry, error) {
	row := q.db.QueryRowContext(ctx, getContinueWatchingEntry, arg.UserID, arg.AnimeID)
	var i ContinueWatchingEntry
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.AnimeID,
		&i.CurrentEpisode,
		&i.CurrentTimeSeconds,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.DurationSeconds,
	)
	return i, err
}

const getDueAnimeFetchRetries = `-- name: GetDueAnimeFetchRetries :many
SELECT anime_id, attempts, next_retry_at, last_error, created_at, updated_at
FROM anime_fetch_retry
WHERE next_retry_at <= CURRENT_TIMESTAMP
ORDER BY next_retry_at ASC
LIMIT ?
`

func (q *Queries) GetDueAnimeFetchRetries(ctx context.Context, limit int64) ([]AnimeFetchRetry, error) {
	rows, err := q.db.QueryContext(ctx, getDueAnimeFetchRetries, limit)
	if err != nil {
		return nil, err
	}
	var items []AnimeFetchRetry
	for rows.Next() {
		var i AnimeFetchRetry
		if err := rows.Scan(
			&i.AnimeID,
			&i.Attempts,
			&i.NextRetryAt,
			&i.LastError,
			&i.CreatedAt,
			&i.UpdatedAt,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getEpisodeAvailabilityCache = `-- name: GetEpisodeAvailabilityCache :one
SELECT anime_id, data, next_refresh_at, retry_until_at, last_attempt_at, last_success_at, failure_count, last_error, updated_at
FROM episode_availability_cache
WHERE anime_id = ? LIMIT 1
`

func (q *Queries) GetEpisodeAvailabilityCache(ctx context.Context, animeID int64) (EpisodeAvailabilityCache, error) {
	row := q.db.QueryRowContext(ctx, getEpisodeAvailabilityCache, animeID)
	var i EpisodeAvailabilityCache
	err := row.Scan(
		&i.AnimeID,
		&i.Data,
		&i.NextRefreshAt,
		&i.RetryUntilAt,
		&i.LastAttemptAt,
		&i.LastSuccessAt,
		&i.FailureCount,
		&i.LastError,
		&i.UpdatedAt,
	)
	return i, err
}

const getEpisodeProviderMapping = `-- name: GetEpisodeProviderMapping :one
SELECT anime_id, provider, provider_show_id, failed_until, last_error, updated_at
FROM episode_provider_mapping
WHERE anime_id = ? AND provider = ? LIMIT 1
`

type GetEpisodeProviderMappingParams struct {
	AnimeID  int64  `json:"anime_id"`
	Provider string `json:"provider"`
}

func (q *Queries) GetEpisodeProviderMapping(ctx context.Context, arg GetEpisodeProviderMappingParams) (EpisodeProviderMapping, error) {
	row := q.db.QueryRowContext(ctx, getEpisodeProviderMapping, arg.AnimeID, arg.Provider)
	var i EpisodeProviderMapping
	err := row.Scan(
		&i.AnimeID,
		&i.Provider,
		&i.ProviderShowID,
		&i.FailedUntil,
		&i.LastError,
		&i.UpdatedAt,
	)
	return i, err
}

const getJikanCache = `-- name: GetJikanCache :one
SELECT data FROM jikan_cache
WHERE key = ? AND datetime(expires_at) > CURRENT_TIMESTAMP LIMIT 1
`

func (q *Queries) GetJikanCache(ctx context.Context, key string) (string, error) {
	row := q.db.QueryRowContext(ctx, getJikanCache, key)
	var data string
	err := row.Scan(&data)
	return data, err
}

const getJikanCacheStale = `-- name: GetJikanCacheStale :one
SELECT data FROM jikan_cache
WHERE key = ? LIMIT 1
`

func (q *Queries) GetJikanCacheStale(ctx context.Context, key string) (string, error) {
	row := q.db.QueryRowContext(ctx, getJikanCacheStale, key)
	var data string
	err := row.Scan(&data)
	return data, err
}

const getSession = `-- name: GetSession :one
SELECT id, user_id, expires_at, created_at FROM session WHERE id = ? LIMIT 1
`

func (q *Queries) GetSession(ctx context.Context, id string) (Session, error) {
	row := q.db.QueryRowContext(ctx, getSession, id)
	var i Session
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.ExpiresAt,
		&i.CreatedAt,
	)
	return i, err
}

const getTrackedAiringAnimeIDsDueForEpisodeRefresh = `-- name: GetTrackedAiringAnimeIDsDueForEpisodeRefresh :many
WITH tracked AS (
    SELECT DISTINCT w.anime_id
    FROM watch_list_entry w
    JOIN anime a ON a.id = w.anime_id
    WHERE a.airing = 1
      AND w.status IN ('watching', 'plan_to_watch')

    UNION

    SELECT DISTINCT c.anime_id
    FROM continue_watching_entry c
    JOIN anime a ON a.id = c.anime_id
    WHERE a.airing = 1
)
SELECT tracked.anime_id
FROM tracked
LEFT JOIN episode_availability_cache e ON e.anime_id = tracked.anime_id
WHERE e.anime_id IS NULL OR e.next_refresh_at IS NULL OR e.next_refresh_at <= CURRENT_TIMESTAMP
ORDER BY tracked.anime_id
LIMIT ?
`

func (q *Queries) GetTrackedAiringAnimeIDsDueForEpisodeRefresh(ctx context.Context, limit int64) ([]int64, error) {
	rows, err := q.db.QueryContext(ctx, getTrackedAiringAnimeIDsDueForEpisodeRefresh, limit)
	if err != nil {
		return nil, err
	}
	var items []int64
	for rows.Next() {
		var anime_id int64
		if err := rows.Scan(&anime_id); err != nil {
			return nil, err
		}
		items = append(items, anime_id)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getUpcomingSeasons = `-- name: GetUpcomingSeasons :many
WITH RECURSIVE sequel_chain AS (
    SELECT 
        w.anime_id as root_id, 
        a.title_original as root_title, 
        r.related_anime_id as current_id, 
        1 as depth,
        w.user_id
    FROM watch_list_entry w
    JOIN anime a ON w.anime_id = a.id
    JOIN anime_relation r ON w.anime_id = r.anime_id
    WHERE w.user_id = ? 
      AND w.status IN ('completed', 'watching') 
      AND r.relation_type = 'Sequel'

    UNION

    SELECT 
        sc.root_id, 
        sc.root_title, 
        r.related_anime_id, 
        sc.depth + 1,
        sc.user_id
    FROM sequel_chain sc
    JOIN anime_relation r ON sc.current_id = r.anime_id
    WHERE r.relation_type = 'Sequel' AND sc.depth < 10
)
SELECT DISTINCT
    related.id, related.title_original, related.image_url, related.created_at, related.title_english, related.title_japanese, related.airing, related.status, related.relations_synced_at, related.duration_seconds,
    sc.root_title AS prequel_title
FROM sequel_chain sc
JOIN anime related ON sc.current_id = related.id
WHERE related.status IN ('Not yet aired', 'Currently Airing')
  AND NOT EXISTS (
      SELECT 1 FROM watch_list_entry we 
      WHERE we.user_id = sc.user_id AND we.anime_id = related.id
  )
ORDER BY related.id DESC
`

type GetUpcomingSeasonsRow struct {
	ID                int64           `json:"id"`
	TitleOriginal     string          `json:"title_original"`
	ImageUrl          string          `json:"image_url"`
	CreatedAt         time.Time       `json:"created_at"`
	TitleEnglish      sql.NullString  `json:"title_english"`
	TitleJapanese     sql.NullString  `json:"title_japanese"`
	Airing            sql.NullBool    `json:"airing"`
	Status            sql.NullString  `json:"status"`
	RelationsSyncedAt sql.NullTime    `json:"relations_synced_at"`
	DurationSeconds   sql.NullFloat64 `json:"duration_seconds"`
	PrequelTitle      string          `json:"prequel_title"`
}

func (q *Queries) GetUpcomingSeasons(ctx context.Context, userID string) ([]GetUpcomingSeasonsRow, error) {
	rows, err := q.db.QueryContext(ctx, getUpcomingSeasons, userID)
	if err != nil {
		return nil, err
	}
	var items []GetUpcomingSeasonsRow
	for rows.Next() {
		var i GetUpcomingSeasonsRow
		if err := rows.Scan(
			&i.ID,
			&i.TitleOriginal,
			&i.ImageUrl,
			&i.CreatedAt,
			&i.TitleEnglish,
			&i.TitleJapanese,
			&i.Airing,
			&i.Status,
			&i.RelationsSyncedAt,
			&i.DurationSeconds,
			&i.PrequelTitle,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getUser = `-- name: GetUser :one
SELECT id, username, password_hash, created_at, avatar_url FROM user WHERE id = ? LIMIT 1
`

func (q *Queries) GetUser(ctx context.Context, id string) (User, error) {
	row := q.db.QueryRowContext(ctx, getUser, id)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.CreatedAt,
		&i.AvatarUrl,
	)
	return i, err
}

const getUserByUsername = `-- name: GetUserByUsername :one
SELECT id, username, password_hash, created_at, avatar_url FROM user WHERE username = ? LIMIT 1
`

func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
	row := q.db.QueryRowContext(ctx, getUserByUsername, username)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.CreatedAt,
		&i.AvatarUrl,
	)
	return i, err
}

const getUserWatchList = `-- name: GetUserWatchList :many
SELECT 
    e.id, e.user_id, e.anime_id, e.status, e.created_at, e.updated_at, e.current_episode, e.last_episode_at, e.current_time_seconds,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.airing
FROM watch_list_entry e
JOIN anime a ON e.anime_id = a.id
WHERE e.user_id = ?
ORDER BY e.updated_at DESC
`

type GetUserWatchListRow struct {
	ID                 string         `json:"id"`
	UserID             string         `json:"user_id"`
	AnimeID            int64          `json:"anime_id"`
	Status             string         `json:"status"`
	CreatedAt          time.Time      `json:"created_at"`
	UpdatedAt          time.Time      `json:"updated_at"`
	CurrentEpisode     sql.NullInt64  `json:"current_episode"`
	LastEpisodeAt      sql.NullTime   `json:"last_episode_at"`
	CurrentTimeSeconds float64        `json:"current_time_seconds"`
	TitleOriginal      string         `json:"title_original"`
	TitleEnglish       sql.NullString `json:"title_english"`
	TitleJapanese      sql.NullString `json:"title_japanese"`
	ImageUrl           string         `json:"image_url"`
	Airing             sql.NullBool   `json:"airing"`
}

func (q *Queries) GetUserWatchList(ctx context.Context, userID string) ([]GetUserWatchListRow, error) {
	rows, err := q.db.QueryContext(ctx, getUserWatchList, userID)
	if err != nil {
		return nil, err
	}
	var items []GetUserWatchListRow
	for rows.Next() {
		var i GetUserWatchListRow
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.AnimeID,
			&i.Status,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.CurrentEpisode,
			&i.LastEpisodeAt,
			&i.CurrentTimeSeconds,
			&i.TitleOriginal,
			&i.TitleEnglish,
			&i.TitleJapanese,
			&i.ImageUrl,
			&i.Airing,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const getWatchListEntry = `-- name: GetWatchListEntry :one
SELECT id, user_id, anime_id, status, created_at, updated_at, current_episode, last_episode_at, current_time_seconds FROM watch_list_entry
WHERE user_id = ? AND anime_id = ? LIMIT 1
`

type GetWatchListEntryParams struct {
	UserID  string `json:"user_id"`
	AnimeID int64  `json:"anime_id"`
}

func (q *Queries) GetWatchListEntry(ctx context.Context, arg GetWatchListEntryParams) (WatchListEntry, error) {
	row := q.db.QueryRowContext(ctx, getWatchListEntry, arg.UserID, arg.AnimeID)
	var i WatchListEntry
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.AnimeID,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.CurrentEpisode,
		&i.LastEpisodeAt,
		&i.CurrentTimeSeconds,
	)
	return i, err
}

const getWatchingAnime = `-- name: GetWatchingAnime :many
SELECT 
    e.id, e.user_id, e.anime_id, e.status, e.created_at, e.updated_at, e.current_episode, e.last_episode_at, e.current_time_seconds,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.airing
FROM watch_list_entry e
JOIN anime a ON e.anime_id = a.id
WHERE e.user_id = ? AND e.status IN ('watching', 'plan_to_watch') AND a.airing = 1
ORDER BY e.updated_at DESC
`

type GetWatchingAnimeRow struct {
	ID                 string         `json:"id"`
	UserID             string         `json:"user_id"`
	AnimeID            int64          `json:"anime_id"`
	Status             string         `json:"status"`
	CreatedAt          time.Time      `json:"created_at"`
	UpdatedAt          time.Time      `json:"updated_at"`
	CurrentEpisode     sql.NullInt64  `json:"current_episode"`
	LastEpisodeAt      sql.NullTime   `json:"last_episode_at"`
	CurrentTimeSeconds float64        `json:"current_time_seconds"`
	TitleOriginal      string         `json:"title_original"`
	TitleEnglish       sql.NullString `json:"title_english"`
	TitleJapanese      sql.NullString `json:"title_japanese"`
	ImageUrl           string         `json:"image_url"`
	Airing             sql.NullBool   `json:"airing"`
}

func (q *Queries) GetWatchingAnime(ctx context.Context, userID string) ([]GetWatchingAnimeRow, error) {
	rows, err := q.db.QueryContext(ctx, getWatchingAnime, userID)
	if err != nil {
		return nil, err
	}
	var items []GetWatchingAnimeRow
	for rows.Next() {
		var i GetWatchingAnimeRow
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.AnimeID,
			&i.Status,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.CurrentEpisode,
			&i.LastEpisodeAt,
			&i.CurrentTimeSeconds,
			&i.TitleOriginal,
			&i.TitleEnglish,
			&i.TitleJapanese,
			&i.ImageUrl,
			&i.Airing,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const markAnimeFetchRetryFailed = `-- name: MarkAnimeFetchRetryFailed :exec
UPDATE anime_fetch_retry
SET attempts = attempts + 1,
    next_retry_at = datetime(CURRENT_TIMESTAMP, ?),
    last_error = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?
`

type MarkAnimeFetchRetryFailedParams struct {
	Datetime  interface{} `json:"datetime"`
	LastError string      `json:"last_error"`
	AnimeID   int64       `json:"anime_id"`
}

func (q *Queries) MarkAnimeFetchRetryFailed(ctx context.Context, arg MarkAnimeFetchRetryFailedParams) error {
	_, err := q.db.ExecContext(ctx, markAnimeFetchRetryFailed, arg.Datetime, arg.LastError, arg.AnimeID)
	return err
}

const markEpisodeAvailabilityRefreshFailed = `-- name: MarkEpisodeAvailabilityRefreshFailed :exec
UPDATE episode_availability_cache
SET last_attempt_at = ?,
    failure_count = failure_count + 1,
    last_error = ?,
    next_refresh_at = ?,
    retry_until_at = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?
`

type MarkEpisodeAvailabilityRefreshFailedParams struct {
	LastAttemptAt sql.NullTime `json:"last_attempt_at"`
	LastError     string       `json:"last_error"`
	NextRefreshAt sql.NullTime `json:"next_refresh_at"`
	RetryUntilAt  sql.NullTime `json:"retry_until_at"`
	AnimeID       int64        `json:"anime_id"`
}

func (q *Queries) MarkEpisodeAvailabilityRefreshFailed(ctx context.Context, arg MarkEpisodeAvailabilityRefreshFailedParams) error {
	_, err := q.db.ExecContext(ctx, markEpisodeAvailabilityRefreshFailed,
		arg.LastAttemptAt,
		arg.LastError,
		arg.NextRefreshAt,
		arg.RetryUntilAt,
		arg.AnimeID,
	)
	return err
}

const markRelationsSynced = `-- name: MarkRelationsSynced :exec
UPDATE anime SET relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?
`

func (q *Queries) MarkRelationsSynced(ctx context.Context, id int64) error {
	_, err := q.db.ExecContext(ctx, markRelationsSynced, id)
	return err
}

const refreshSession = `-- name: RefreshSession :exec
UPDATE session
SET expires_at = ?
WHERE id = ?
`

type RefreshSessionParams struct {
	ExpiresAt time.Time `json:"expires_at"`
	ID        string    `json:"id"`
}

func (q *Queries) RefreshSession(ctx context.Context, arg RefreshSessionParams) error {
	_, err := q.db.ExecContext(ctx, refreshSession, arg.ExpiresAt, arg.ID)
	return err
}

const revokeAllAPITokensForUser = `-- name: RevokeAllAPITokensForUser :exec
UPDATE api_token
SET revoked_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND revoked_at IS NULL
`

func (q *Queries) RevokeAllAPITokensForUser(ctx context.Context, userID string) error {
	_, err := q.db.ExecContext(ctx, revokeAllAPITokensForUser, userID)
	return err
}

const saveWatchProgress = `-- name: SaveWatchProgress :exec
UPDATE watch_list_entry
SET current_episode = ?,
    current_time_seconds = ?
WHERE user_id = ? AND anime_id = ?
`

type SaveWatchProgressParams struct {
	CurrentEpisode     sql.NullInt64 `json:"current_episode"`
	CurrentTimeSeconds float64       `json:"current_time_seconds"`
	UserID             string        `json:"user_id"`
	AnimeID            int64         `json:"anime_id"`
}

func (q *Queries) SaveWatchProgress(ctx context.Context, arg SaveWatchProgressParams) error {
	_, err := q.db.ExecContext(ctx, saveWatchProgress,
		arg.CurrentEpisode,
		arg.CurrentTimeSeconds,
		arg.UserID,
		arg.AnimeID,
	)
	return err
}

const setJikanCache = `-- name: SetJikanCache :exec
INSERT INTO jikan_cache (key, data, expires_at)
VALUES (?, ?, ?)
ON CONFLICT (key) DO UPDATE SET
    data = excluded.data,
    expires_at = excluded.expires_at,
    created_at = CURRENT_TIMESTAMP
`

type SetJikanCacheParams struct {
	Key       string    `json:"key"`
	Data      string    `json:"data"`
	ExpiresAt time.Time `json:"expires_at"`
}

func (q *Queries) SetJikanCache(ctx context.Context, arg SetJikanCacheParams) error {
	_, err := q.db.ExecContext(ctx, setJikanCache, arg.Key, arg.Data, arg.ExpiresAt)
	return err
}

const touchAPITokenLastUsedAt = `-- name: TouchAPITokenLastUsedAt :exec
UPDATE api_token
SET last_used_at = CURRENT_TIMESTAMP
WHERE id = ?
`

func (q *Queries) TouchAPITokenLastUsedAt(ctx context.Context, id string) error {
	_, err := q.db.ExecContext(ctx, touchAPITokenLastUsedAt, id)
	return err
}

const updateAnimeStatus = `-- name: UpdateAnimeStatus :exec
UPDATE anime SET status = ? WHERE id = ?
`

type UpdateAnimeStatusParams struct {
	Status sql.NullString `json:"status"`
	ID     int64          `json:"id"`
}

func (q *Queries) UpdateAnimeStatus(ctx context.Context, arg UpdateAnimeStatusParams) error {
	_, err := q.db.ExecContext(ctx, updateAnimeStatus, arg.Status, arg.ID)
	return err
}

const upsertAnime = `-- name: UpsertAnime :one
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, duration_seconds)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
    title_original = excluded.title_original,
    title_english = excluded.title_english,
    title_japanese = excluded.title_japanese,
    image_url = excluded.image_url,
    airing = excluded.airing,
    duration_seconds = excluded.duration_seconds
RETURNING id, title_original, image_url, created_at, title_english, title_japanese, airing, status, relations_synced_at, duration_seconds
`

type UpsertAnimeParams struct {
	ID              int64           `json:"id"`
	TitleOriginal   string          `json:"title_original"`
	TitleEnglish    sql.NullString  `json:"title_english"`
	TitleJapanese   sql.NullString  `json:"title_japanese"`
	ImageUrl        string          `json:"image_url"`
	Airing          sql.NullBool    `json:"airing"`
	DurationSeconds sql.NullFloat64 `json:"duration_seconds"`
}

func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error) {
	row := q.db.QueryRowContext(ctx, upsertAnime,
		arg.ID,
		arg.TitleOriginal,
		arg.TitleEnglish,
		arg.TitleJapanese,
		arg.ImageUrl,
		arg.Airing,
		arg.DurationSeconds,
	)
	var i Anime
	err := row.Scan(
		&i.ID,
		&i.TitleOriginal,
		&i.ImageUrl,
		&i.CreatedAt,
		&i.TitleEnglish,
		&i.TitleJapanese,
		&i.Airing,
		&i.Status,
		&i.RelationsSyncedAt,
		&i.DurationSeconds,
	)
	return i, err
}

const upsertAnimeRelation = `-- name: UpsertAnimeRelation :exec
INSERT INTO anime_relation (anime_id, related_anime_id, relation_type)
VALUES (?, ?, ?)
ON CONFLICT (anime_id, related_anime_id) DO UPDATE SET
    relation_type = excluded.relation_type
`

type UpsertAnimeRelationParams struct {
	AnimeID        int64  `json:"anime_id"`
	RelatedAnimeID int64  `json:"related_anime_id"`
	RelationType   string `json:"relation_type"`
}

func (q *Queries) UpsertAnimeRelation(ctx context.Context, arg UpsertAnimeRelationParams) error {
	_, err := q.db.ExecContext(ctx, upsertAnimeRelation, arg.AnimeID, arg.RelatedAnimeID, arg.RelationType)
	return err
}

const upsertContinueWatchingEntry = `-- name: UpsertContinueWatchingEntry :one
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, duration_seconds, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (user_id, anime_id) DO UPDATE SET
    current_episode = excluded.current_episode,
    current_time_seconds = excluded.current_time_seconds,
    duration_seconds = excluded.duration_seconds,
    updated_at = CURRENT_TIMESTAMP
RETURNING id, user_id, anime_id, current_episode, current_time_seconds, created_at, updated_at, duration_seconds
`

type UpsertContinueWatchingEntryParams struct {
	ID                 string          `json:"id"`
	UserID             string          `json:"user_id"`
	AnimeID            int64           `json:"anime_id"`
	CurrentEpisode     sql.NullInt64   `json:"current_episode"`
	CurrentTimeSeconds float64         `json:"current_time_seconds"`
	DurationSeconds    sql.NullFloat64 `json:"duration_seconds"`
}

func (q *Queries) UpsertContinueWatchingEntry(ctx context.Context, arg UpsertContinueWatchingEntryParams) (ContinueWatchingEntry, error) {
	row := q.db.QueryRowContext(ctx, upsertContinueWatchingEntry,
		arg.ID,
		arg.UserID,
		arg.AnimeID,
		arg.CurrentEpisode,
		arg.CurrentTimeSeconds,
		arg.DurationSeconds,
	)
	var i ContinueWatchingEntry
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.AnimeID,
		&i.CurrentEpisode,
		&i.CurrentTimeSeconds,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.DurationSeconds,
	)
	return i, err
}

const upsertEpisodeAvailabilityCache = `-- name: UpsertEpisodeAvailabilityCache :exec
INSERT INTO episode_availability_cache (
    anime_id,
    data,
    next_refresh_at,
    retry_until_at,
    last_attempt_at,
    last_success_at,
    failure_count,
    last_error,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
    data = excluded.data,
    next_refresh_at = excluded.next_refresh_at,
    retry_until_at = excluded.retry_until_at,
    last_attempt_at = excluded.last_attempt_at,
    last_success_at = excluded.last_success_at,
    failure_count = excluded.failure_count,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP
`

type UpsertEpisodeAvailabilityCacheParams struct {
	AnimeID       int64        `json:"anime_id"`
	Data          string       `json:"data"`
	NextRefreshAt sql.NullTime `json:"next_refresh_at"`
	RetryUntilAt  sql.NullTime `json:"retry_until_at"`
	LastAttemptAt sql.NullTime `json:"last_attempt_at"`
	LastSuccessAt sql.NullTime `json:"last_success_at"`
	FailureCount  int64        `json:"failure_count"`
	LastError     string       `json:"last_error"`
}

func (q *Queries) UpsertEpisodeAvailabilityCache(ctx context.Context, arg UpsertEpisodeAvailabilityCacheParams) error {
	_, err := q.db.ExecContext(ctx, upsertEpisodeAvailabilityCache,
		arg.AnimeID,
		arg.Data,
		arg.NextRefreshAt,
		arg.RetryUntilAt,
		arg.LastAttemptAt,
		arg.LastSuccessAt,
		arg.FailureCount,
		arg.LastError,
	)
	return err
}

const upsertEpisodeProviderMapping = `-- name: UpsertEpisodeProviderMapping :exec
INSERT INTO episode_provider_mapping (anime_id, provider, provider_show_id, failed_until, last_error, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id, provider) DO UPDATE SET
    provider_show_id = excluded.provider_show_id,
    failed_until = excluded.failed_until,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP
`

type UpsertEpisodeProviderMappingParams struct {
	AnimeID        int64        `json:"anime_id"`
	Provider       string       `json:"provider"`
	ProviderShowID string       `json:"provider_show_id"`
	FailedUntil    sql.NullTime `json:"failed_until"`
	LastError      string       `json:"last_error"`
}

func (q *Queries) UpsertEpisodeProviderMapping(ctx context.Context, arg UpsertEpisodeProviderMappingParams) error {
	_, err := q.db.ExecContext(ctx, upsertEpisodeProviderMapping,
		arg.AnimeID,
		arg.Provider,
		arg.ProviderShowID,
		arg.FailedUntil,
		arg.LastError,
	)
	return err
}

const upsertWatchListEntry = `-- name: UpsertWatchListEntry :one
INSERT INTO watch_list_entry (id, user_id, anime_id, status, current_episode, current_time_seconds, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (user_id, anime_id) DO UPDATE SET
    status = excluded.status,
    current_episode = excluded.current_episode,
    current_time_seconds = excluded.current_time_seconds,
    updated_at = CURRENT_TIMESTAMP
RETURNING id, user_id, anime_id, status, created_at, updated_at, current_episode, last_episode_at, current_time_seconds
`

type UpsertWatchListEntryParams struct {
	ID                 string        `json:"id"`
	UserID             string        `json:"user_id"`
	AnimeID            int64         `json:"anime_id"`
	Status             string        `json:"status"`
	CurrentEpisode     sql.NullInt64 `json:"current_episode"`
	CurrentTimeSeconds float64       `json:"current_time_seconds"`
}

func (q *Queries) UpsertWatchListEntry(ctx context.Context, arg UpsertWatchListEntryParams) (WatchListEntry, error) {
	row := q.db.QueryRowContext(ctx, upsertWatchListEntry,
		arg.ID,
		arg.UserID,
		arg.AnimeID,
		arg.Status,
		arg.CurrentEpisode,
		arg.CurrentTimeSeconds,
	)
	var i WatchListEntry
	err := row.Scan(
		&i.ID,
		&i.UserID,
		&i.AnimeID,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.CurrentEpisode,
		&i.LastEpisodeAt,
		&i.CurrentTimeSeconds,
	)
	return i, err
}
