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

package dbq

import (
	"context"

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

const changeUserPassword = `-- name: ChangeUserPassword :exec
UPDATE users SET password_hash = $2 WHERE id = $1
`

type ChangeUserPasswordParams struct {
	ID           pgtype.UUID
	PasswordHash string
}

// Self-service password change. Caller (HTTP handler) verifies the
// current password before calling this. Distinct from
// ResetUserPassword which is admin-driven (no current-password
// check).
func (q *Queries) ChangeUserPassword(ctx context.Context, arg ChangeUserPasswordParams) error {
	_, err := q.db.Exec(ctx, changeUserPassword, arg.ID, arg.PasswordHash)
	return err
}

const countAdmins = `-- name: CountAdmins :one
SELECT count(*) FROM users WHERE is_admin = true
`

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

const countUsers = `-- name: CountUsers :one
SELECT count(*) FROM users
`

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

const createUser = `-- name: CreateUser :one
INSERT INTO users (username, password_hash, api_token, is_admin, display_name)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type CreateUserParams struct {
	Username     string
	PasswordHash string
	ApiToken     string
	IsAdmin      bool
	DisplayName  *string
}

func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
	row := q.db.QueryRow(ctx, createUser,
		arg.Username,
		arg.PasswordHash,
		arg.ApiToken,
		arg.IsAdmin,
		arg.DisplayName,
	)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const createUserAdmin = `-- name: CreateUserAdmin :one
INSERT INTO users (username, password_hash, api_token, is_admin, display_name)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type CreateUserAdminParams struct {
	Username     string
	PasswordHash string
	ApiToken     string
	IsAdmin      bool
	DisplayName  *string
}

// Admin-driven user creation. Distinct from CreateUser/CreateUserFirstAdminRace:
// the caller (an admin) supplies all five fields explicitly including is_admin,
// so an admin can promote on creation. Used by POST /api/admin/users.
func (q *Queries) CreateUserAdmin(ctx context.Context, arg CreateUserAdminParams) (User, error) {
	row := q.db.QueryRow(ctx, createUserAdmin,
		arg.Username,
		arg.PasswordHash,
		arg.ApiToken,
		arg.IsAdmin,
		arg.DisplayName,
	)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const createUserFirstAdminRace = `-- name: CreateUserFirstAdminRace :one
INSERT INTO users (username, password_hash, api_token, is_admin, display_name)
VALUES (
    $1, $2, $3,
    (SELECT NOT EXISTS (SELECT 1 FROM users)),
    $4
)
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type CreateUserFirstAdminRaceParams struct {
	Username     string
	PasswordHash string
	ApiToken     string
	DisplayName  *string
}

// Inserts a new user; sets is_admin=true ONLY when no users currently
// exist. The (SELECT NOT EXISTS ...) subquery is evaluated at INSERT
// time within the same statement, so the result reflects committed
// state at that moment.
//
// Race semantics: two concurrent calls in an empty-users state may
// both observe "no users" and both insert with is_admin=true. That's
// benign — having two admins from the gate is fine; what matters is
// that there's AT LEAST one. If the two calls happened to share the
// same username, the unique constraint on users.username arbitrates
// and the second caller's INSERT fails with a unique violation. The
// caller (registration handler) can retry as a regular non-admin in
// that case (or surface a "username taken" error to the user).
func (q *Queries) CreateUserFirstAdminRace(ctx context.Context, arg CreateUserFirstAdminRaceParams) (User, error) {
	row := q.db.QueryRow(ctx, createUserFirstAdminRace,
		arg.Username,
		arg.PasswordHash,
		arg.ApiToken,
		arg.DisplayName,
	)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const deleteUser = `-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1
`

// Hard delete with cascade. The schema's ON DELETE CASCADE foreign keys
// handle plays/likes/sessions/etc. Caller must enforce the last-admin
// guard at the HTTP layer (count admins, refuse if target is the only
// admin). The lidarr_quarantine, general_likes*, play_events,
// sessions, and other user-FK tables all have ON DELETE CASCADE so
// this is a clean drop.
func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error {
	_, err := q.db.Exec(ctx, deleteUser, id)
	return err
}

const getListenBrainzConfig = `-- name: GetListenBrainzConfig :one
SELECT
    u.listenbrainz_token,
    u.listenbrainz_enabled,
    (SELECT MAX(pe.scrobbled_at)::timestamptz
     FROM play_events pe
     WHERE pe.user_id = u.id) AS last_scrobbled_at
FROM users u
WHERE u.id = $1
`

type GetListenBrainzConfigRow struct {
	ListenbrainzToken   *string
	ListenbrainzEnabled bool
	LastScrobbledAt     pgtype.Timestamptz
}

// Returns the user's LB token + enabled flag and the most recent
// play_events.scrobbled_at for last-scrobbled-at status.
func (q *Queries) GetListenBrainzConfig(ctx context.Context, id pgtype.UUID) (GetListenBrainzConfigRow, error) {
	row := q.db.QueryRow(ctx, getListenBrainzConfig, id)
	var i GetListenBrainzConfigRow
	err := row.Scan(&i.ListenbrainzToken, &i.ListenbrainzEnabled, &i.LastScrobbledAt)
	return i, err
}

const getUserByAPIToken = `-- name: GetUserByAPIToken :one
SELECT id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled FROM users WHERE api_token = $1
`

func (q *Queries) GetUserByAPIToken(ctx context.Context, apiToken string) (User, error) {
	row := q.db.QueryRow(ctx, getUserByAPIToken, apiToken)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const getUserByEmail = `-- name: GetUserByEmail :one
SELECT id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled FROM users WHERE lower(email) = lower($1)
`

// Used by forgot-password lookup. Lowercase comparison both sides
// to keep the unique index happy and to make the lookup
// case-insensitive.
func (q *Queries) GetUserByEmail(ctx context.Context, lower string) (User, error) {
	row := q.db.QueryRow(ctx, getUserByEmail, lower)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const getUserByID = `-- name: GetUserByID :one
SELECT id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled FROM users WHERE id = $1
`

func (q *Queries) GetUserByID(ctx context.Context, id pgtype.UUID) (User, error) {
	row := q.db.QueryRow(ctx, getUserByID, id)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const getUserByUsername = `-- name: GetUserByUsername :one
SELECT id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled FROM users WHERE username = $1
`

func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
	row := q.db.QueryRow(ctx, getUserByUsername, username)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const listActiveUsersWithTimezones = `-- name: ListActiveUsersWithTimezones :many
SELECT u.id, u.timezone FROM users u
 WHERE EXISTS (
     SELECT 1 FROM play_events pe
      WHERE pe.user_id = u.id
        AND pe.started_at > now() - INTERVAL '7 days'
   )
`

type ListActiveUsersWithTimezonesRow struct {
	ID       pgtype.UUID
	Timezone string
}

// Returns (id, timezone) for every user with a play in the last 7
// days. The system-playlist scheduler iterates this list at startup
// and during its hourly reconciliation to discover newly-active /
// no-longer-active users.
func (q *Queries) ListActiveUsersWithTimezones(ctx context.Context) ([]ListActiveUsersWithTimezonesRow, error) {
	rows, err := q.db.Query(ctx, listActiveUsersWithTimezones)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListActiveUsersWithTimezonesRow
	for rows.Next() {
		var i ListActiveUsersWithTimezonesRow
		if err := rows.Scan(&i.ID, &i.Timezone); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listUsers = `-- name: ListUsers :many
SELECT id, username, display_name, is_admin, auto_approve_requests,
       debug_mode_enabled, created_at
  FROM users
 ORDER BY created_at DESC
`

type ListUsersRow struct {
	ID                  pgtype.UUID
	Username            string
	DisplayName         *string
	IsAdmin             bool
	AutoApproveRequests bool
	DebugModeEnabled    bool
	CreatedAt           pgtype.Timestamptz
}

// Admin user-management list. Sort newest-first.
func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) {
	rows, err := q.db.Query(ctx, listUsers)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListUsersRow
	for rows.Next() {
		var i ListUsersRow
		if err := rows.Scan(
			&i.ID,
			&i.Username,
			&i.DisplayName,
			&i.IsAdmin,
			&i.AutoApproveRequests,
			&i.DebugModeEnabled,
			&i.CreatedAt,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const regenerateApiToken = `-- name: RegenerateApiToken :one
UPDATE users SET api_token = $2 WHERE id = $1
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type RegenerateApiTokenParams struct {
	ID       pgtype.UUID
	ApiToken string
}

// Self-service: caller wants a new API token. Used by the /settings
// API Token card's "Regenerate" button.
func (q *Queries) RegenerateApiToken(ctx context.Context, arg RegenerateApiTokenParams) (User, error) {
	row := q.db.QueryRow(ctx, regenerateApiToken, arg.ID, arg.ApiToken)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const resetUserPassword = `-- name: ResetUserPassword :exec
UPDATE users
   SET password_hash = $2
 WHERE id = $1
`

type ResetUserPasswordParams struct {
	ID           pgtype.UUID
	PasswordHash string
}

// Admin-driven password reset: caller writes a new hashed password to
// the target user's row. No knowledge of the old password is required
// (this is the admin path; self-service "knows current password" is
// a separate U3 query).
func (q *Queries) ResetUserPassword(ctx context.Context, arg ResetUserPasswordParams) error {
	_, err := q.db.Exec(ctx, resetUserPassword, arg.ID, arg.PasswordHash)
	return err
}

const setDebugMode = `-- name: SetDebugMode :one
UPDATE users
   SET debug_mode_enabled = $2
 WHERE id = $1
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type SetDebugModeParams struct {
	ID               pgtype.UUID
	DebugModeEnabled bool
}

// Toggle the per-account diagnostics/debug-reporting opt-in. Admin-driven
// from /admin (flip remotely while a bug is live) or self-driven OFF from
// the client. Returns the updated row so the handler can echo it.
func (q *Queries) SetDebugMode(ctx context.Context, arg SetDebugModeParams) (User, error) {
	row := q.db.QueryRow(ctx, setDebugMode, arg.ID, arg.DebugModeEnabled)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const setListenBrainzEnabled = `-- name: SetListenBrainzEnabled :exec
UPDATE users
SET listenbrainz_enabled = $2
WHERE id = $1
`

type SetListenBrainzEnabledParams struct {
	ID                  pgtype.UUID
	ListenbrainzEnabled bool
}

func (q *Queries) SetListenBrainzEnabled(ctx context.Context, arg SetListenBrainzEnabledParams) error {
	_, err := q.db.Exec(ctx, setListenBrainzEnabled, arg.ID, arg.ListenbrainzEnabled)
	return err
}

const setListenBrainzToken = `-- name: SetListenBrainzToken :exec
UPDATE users
SET listenbrainz_token = $2,
    listenbrainz_enabled = CASE WHEN COALESCE($2, '') = '' THEN FALSE ELSE listenbrainz_enabled END
WHERE id = $1
`

type SetListenBrainzTokenParams struct {
	ID                pgtype.UUID
	ListenbrainzToken *string
}

func (q *Queries) SetListenBrainzToken(ctx context.Context, arg SetListenBrainzTokenParams) error {
	_, err := q.db.Exec(ctx, setListenBrainzToken, arg.ID, arg.ListenbrainzToken)
	return err
}

const setSubsonicPassword = `-- name: SetSubsonicPassword :exec
UPDATE users SET subsonic_password = $2 WHERE id = $1
`

type SetSubsonicPasswordParams struct {
	ID               pgtype.UUID
	SubsonicPassword *string
}

// Stores (or clears with NULL) the per-user Subsonic legacy credential used
// for t/s and p auth on /rest/*. Must be plaintext; see migration 0003.
func (q *Queries) SetSubsonicPassword(ctx context.Context, arg SetSubsonicPasswordParams) error {
	_, err := q.db.Exec(ctx, setSubsonicPassword, arg.ID, arg.SubsonicPassword)
	return err
}

const updateUserAdmin = `-- name: UpdateUserAdmin :one
UPDATE users
   SET is_admin = $2
 WHERE id = $1
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type UpdateUserAdminParams struct {
	ID      pgtype.UUID
	IsAdmin bool
}

// Sets is_admin to the given value. Returns the updated row so the
// handler can echo it back to the caller.
func (q *Queries) UpdateUserAdmin(ctx context.Context, arg UpdateUserAdminParams) (User, error) {
	row := q.db.QueryRow(ctx, updateUserAdmin, arg.ID, arg.IsAdmin)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const updateUserAutoApprove = `-- name: UpdateUserAutoApprove :one
UPDATE users
   SET auto_approve_requests = $2
 WHERE id = $1
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type UpdateUserAutoApproveParams struct {
	ID                  pgtype.UUID
	AutoApproveRequests bool
}

// Toggle the per-user auto_approve_requests flag.
func (q *Queries) UpdateUserAutoApprove(ctx context.Context, arg UpdateUserAutoApproveParams) (User, error) {
	row := q.db.QueryRow(ctx, updateUserAutoApprove, arg.ID, arg.AutoApproveRequests)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const updateUserProfile = `-- name: UpdateUserProfile :one
UPDATE users
   SET display_name = $2,
       email        = $3
 WHERE id = $1
RETURNING id, username, password_hash, api_token, is_admin, created_at, subsonic_password, listenbrainz_token, listenbrainz_enabled, display_name, auto_approve_requests, email, timezone, timezone_updated_at, debug_mode_enabled
`

type UpdateUserProfileParams struct {
	ID          pgtype.UUID
	DisplayName *string
	Email       *string
}

// Self-service: set display name and/or email. Both fields are
// nullable in the DB; pass NULL ptr to clear, non-NULL ptr to set.
func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) (User, error) {
	row := q.db.QueryRow(ctx, updateUserProfile, arg.ID, arg.DisplayName, arg.Email)
	var i User
	err := row.Scan(
		&i.ID,
		&i.Username,
		&i.PasswordHash,
		&i.ApiToken,
		&i.IsAdmin,
		&i.CreatedAt,
		&i.SubsonicPassword,
		&i.ListenbrainzToken,
		&i.ListenbrainzEnabled,
		&i.DisplayName,
		&i.AutoApproveRequests,
		&i.Email,
		&i.Timezone,
		&i.TimezoneUpdatedAt,
		&i.DebugModeEnabled,
	)
	return i, err
}

const updateUserTimezone = `-- name: UpdateUserTimezone :exec
UPDATE users
   SET timezone = $2,
       timezone_updated_at = now()
 WHERE id = $1
`

type UpdateUserTimezoneParams struct {
	ID       pgtype.UUID
	Timezone string
}

// Sets the user's IANA timezone and bumps timezone_updated_at. The
// handler validates the timezone string via time.LoadLocation before
// calling this; the DB does not re-validate.
func (q *Queries) UpdateUserTimezone(ctx context.Context, arg UpdateUserTimezoneParams) error {
	_, err := q.db.Exec(ctx, updateUserTimezone, arg.ID, arg.Timezone)
	return err
}
