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

package db

import (
	"context"

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

const createChatMessage = `-- name: CreateChatMessage :one
INSERT INTO chat_message (chat_session_id, role, content, task_id, failure_reason, elapsed_ms)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms
`

type CreateChatMessageParams struct {
	ChatSessionID pgtype.UUID `json:"chat_session_id"`
	Role          string      `json:"role"`
	Content       string      `json:"content"`
	TaskID        pgtype.UUID `json:"task_id"`
	FailureReason pgtype.Text `json:"failure_reason"`
	ElapsedMs     pgtype.Int8 `json:"elapsed_ms"`
}

func (q *Queries) CreateChatMessage(ctx context.Context, arg CreateChatMessageParams) (ChatMessage, error) {
	row := q.db.QueryRow(ctx, createChatMessage,
		arg.ChatSessionID,
		arg.Role,
		arg.Content,
		arg.TaskID,
		arg.FailureReason,
		arg.ElapsedMs,
	)
	var i ChatMessage
	err := row.Scan(
		&i.ID,
		&i.ChatSessionID,
		&i.Role,
		&i.Content,
		&i.TaskID,
		&i.CreatedAt,
		&i.FailureReason,
		&i.ElapsedMs,
	)
	return i, err
}

const createChatSession = `-- name: CreateChatSession :one
INSERT INTO chat_session (workspace_id, agent_id, creator_id, title, runtime_id)
VALUES ($1, $2, $3, $4, (SELECT runtime_id FROM agent WHERE id = $2))
RETURNING id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id
`

type CreateChatSessionParams struct {
	WorkspaceID pgtype.UUID `json:"workspace_id"`
	AgentID     pgtype.UUID `json:"agent_id"`
	CreatorID   pgtype.UUID `json:"creator_id"`
	Title       string      `json:"title"`
}

func (q *Queries) CreateChatSession(ctx context.Context, arg CreateChatSessionParams) (ChatSession, error) {
	row := q.db.QueryRow(ctx, createChatSession,
		arg.WorkspaceID,
		arg.AgentID,
		arg.CreatorID,
		arg.Title,
	)
	var i ChatSession
	err := row.Scan(
		&i.ID,
		&i.WorkspaceID,
		&i.AgentID,
		&i.CreatorID,
		&i.Title,
		&i.SessionID,
		&i.WorkDir,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.UnreadSince,
		&i.RuntimeID,
	)
	return i, err
}

const createChatTask = `-- name: CreateChatTask :one
INSERT INTO agent_task_queue (agent_id, runtime_id, issue_id, status, priority, chat_session_id)
VALUES ($1, $2, NULL, 'queued', $3, $4)
RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason
`

type CreateChatTaskParams struct {
	AgentID       pgtype.UUID `json:"agent_id"`
	RuntimeID     pgtype.UUID `json:"runtime_id"`
	Priority      int32       `json:"priority"`
	ChatSessionID pgtype.UUID `json:"chat_session_id"`
}

func (q *Queries) CreateChatTask(ctx context.Context, arg CreateChatTaskParams) (AgentTaskQueue, error) {
	row := q.db.QueryRow(ctx, createChatTask,
		arg.AgentID,
		arg.RuntimeID,
		arg.Priority,
		arg.ChatSessionID,
	)
	var i AgentTaskQueue
	err := row.Scan(
		&i.ID,
		&i.AgentID,
		&i.IssueID,
		&i.Status,
		&i.Priority,
		&i.DispatchedAt,
		&i.StartedAt,
		&i.CompletedAt,
		&i.Result,
		&i.Error,
		&i.CreatedAt,
		&i.Context,
		&i.RuntimeID,
		&i.SessionID,
		&i.WorkDir,
		&i.TriggerCommentID,
		&i.ChatSessionID,
		&i.AutopilotRunID,
		&i.Attempt,
		&i.MaxAttempts,
		&i.ParentTaskID,
		&i.FailureReason,
		&i.TriggerSummary,
		&i.ForceFreshSession,
		&i.IsLeaderTask,
		&i.WaitReason,
	)
	return i, err
}

const deleteChatSession = `-- name: DeleteChatSession :exec
DELETE FROM chat_session WHERE id = $1 AND workspace_id = $2
`

type DeleteChatSessionParams struct {
	ID          pgtype.UUID `json:"id"`
	WorkspaceID pgtype.UUID `json:"workspace_id"`
}

// Hard delete. chat_message rows cascade via FK ON DELETE CASCADE; the
// chat_session_id on agent_task_queue is set NULL by FK so completed/failed
// task history survives the session being removed. Callers MUST run inside
// the same transaction that holds LockChatSessionForDelete and that has
// already cancelled any in-flight tasks (see CancelAgentTasksByChatSession)
// so the daemon does not keep running work whose result has nowhere to
// land. workspace_id in the WHERE clause is a SQL-layer tenant guard; see
// DeleteIssue.
func (q *Queries) DeleteChatSession(ctx context.Context, arg DeleteChatSessionParams) error {
	_, err := q.db.Exec(ctx, deleteChatSession, arg.ID, arg.WorkspaceID)
	return err
}

const getChatMessage = `-- name: GetChatMessage :one
SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message
WHERE id = $1
`

func (q *Queries) GetChatMessage(ctx context.Context, id pgtype.UUID) (ChatMessage, error) {
	row := q.db.QueryRow(ctx, getChatMessage, id)
	var i ChatMessage
	err := row.Scan(
		&i.ID,
		&i.ChatSessionID,
		&i.Role,
		&i.Content,
		&i.TaskID,
		&i.CreatedAt,
		&i.FailureReason,
		&i.ElapsedMs,
	)
	return i, err
}

const getChatSession = `-- name: GetChatSession :one
SELECT id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id FROM chat_session
WHERE id = $1
`

func (q *Queries) GetChatSession(ctx context.Context, id pgtype.UUID) (ChatSession, error) {
	row := q.db.QueryRow(ctx, getChatSession, id)
	var i ChatSession
	err := row.Scan(
		&i.ID,
		&i.WorkspaceID,
		&i.AgentID,
		&i.CreatorID,
		&i.Title,
		&i.SessionID,
		&i.WorkDir,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.UnreadSince,
		&i.RuntimeID,
	)
	return i, err
}

const getChatSessionInWorkspace = `-- name: GetChatSessionInWorkspace :one
SELECT id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id FROM chat_session
WHERE id = $1 AND workspace_id = $2
`

type GetChatSessionInWorkspaceParams struct {
	ID          pgtype.UUID `json:"id"`
	WorkspaceID pgtype.UUID `json:"workspace_id"`
}

func (q *Queries) GetChatSessionInWorkspace(ctx context.Context, arg GetChatSessionInWorkspaceParams) (ChatSession, error) {
	row := q.db.QueryRow(ctx, getChatSessionInWorkspace, arg.ID, arg.WorkspaceID)
	var i ChatSession
	err := row.Scan(
		&i.ID,
		&i.WorkspaceID,
		&i.AgentID,
		&i.CreatorID,
		&i.Title,
		&i.SessionID,
		&i.WorkDir,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.UnreadSince,
		&i.RuntimeID,
	)
	return i, err
}

const getLastChatTaskSession = `-- name: GetLastChatTaskSession :one
SELECT session_id, work_dir, runtime_id FROM agent_task_queue
WHERE chat_session_id = $1
  AND (
    status = 'completed'
    OR (
      status = 'failed'
      AND COALESCE(failure_reason, '') NOT IN ('iteration_limit', 'agent_fallback_message', 'api_invalid_request', 'codex_semantic_inactivity')
      AND NOT (COALESCE(error, '') ILIKE '%400%' AND COALESCE(error, '') ILIKE '%invalid_request_error%')
    )
  )
  AND session_id IS NOT NULL
ORDER BY completed_at DESC
LIMIT 1
`

type GetLastChatTaskSessionRow struct {
	SessionID pgtype.Text `json:"session_id"`
	WorkDir   pgtype.Text `json:"work_dir"`
	RuntimeID pgtype.UUID `json:"runtime_id"`
}

// Returns the most recent task in this chat session that managed to record a
// session_id. Includes both completed and failed tasks: even a failed task
// may have established a real agent session before failing, and we'd rather
// resume there than start over and lose conversation memory. Used as a
// fallback when chat_session.session_id is NULL. Resume-unsafe failures are
// excluded because replaying those sessions deterministically reproduces the
// same terminal state.
func (q *Queries) GetLastChatTaskSession(ctx context.Context, chatSessionID pgtype.UUID) (GetLastChatTaskSessionRow, error) {
	row := q.db.QueryRow(ctx, getLastChatTaskSession, chatSessionID)
	var i GetLastChatTaskSessionRow
	err := row.Scan(&i.SessionID, &i.WorkDir, &i.RuntimeID)
	return i, err
}

const getPendingChatTask = `-- name: GetPendingChatTask :one
SELECT id, status, created_at FROM agent_task_queue
WHERE chat_session_id = $1 AND status IN ('queued', 'dispatched', 'running', 'waiting_local_directory')
ORDER BY created_at DESC
LIMIT 1
`

type GetPendingChatTaskRow struct {
	ID        pgtype.UUID        `json:"id"`
	Status    string             `json:"status"`
	CreatedAt pgtype.Timestamptz `json:"created_at"`
}

// Returns the most recent in-flight task for a chat session, if any.
// Used by the frontend to recover pending state after refresh / reopen.
// created_at is the anchor for the chat StatusPill timer (it computes
// elapsed = now - task.created_at), so the pill survives refresh / reopen
// without "resetting to 0s".
func (q *Queries) GetPendingChatTask(ctx context.Context, chatSessionID pgtype.UUID) (GetPendingChatTaskRow, error) {
	row := q.db.QueryRow(ctx, getPendingChatTask, chatSessionID)
	var i GetPendingChatTaskRow
	err := row.Scan(&i.ID, &i.Status, &i.CreatedAt)
	return i, err
}

const listAllChatSessionsByCreator = `-- name: ListAllChatSessionsByCreator :many
SELECT cs.id, cs.workspace_id, cs.agent_id, cs.creator_id, cs.title, cs.session_id, cs.work_dir, cs.status, cs.created_at, cs.updated_at, cs.unread_since, cs.runtime_id,
       (cs.unread_since IS NOT NULL)::bool AS has_unread
FROM chat_session cs
WHERE cs.workspace_id = $1 AND cs.creator_id = $2
ORDER BY cs.updated_at DESC
`

type ListAllChatSessionsByCreatorParams struct {
	WorkspaceID pgtype.UUID `json:"workspace_id"`
	CreatorID   pgtype.UUID `json:"creator_id"`
}

type ListAllChatSessionsByCreatorRow struct {
	ID          pgtype.UUID        `json:"id"`
	WorkspaceID pgtype.UUID        `json:"workspace_id"`
	AgentID     pgtype.UUID        `json:"agent_id"`
	CreatorID   pgtype.UUID        `json:"creator_id"`
	Title       string             `json:"title"`
	SessionID   pgtype.Text        `json:"session_id"`
	WorkDir     pgtype.Text        `json:"work_dir"`
	Status      string             `json:"status"`
	CreatedAt   pgtype.Timestamptz `json:"created_at"`
	UpdatedAt   pgtype.Timestamptz `json:"updated_at"`
	UnreadSince pgtype.Timestamptz `json:"unread_since"`
	RuntimeID   pgtype.UUID        `json:"runtime_id"`
	HasUnread   bool               `json:"has_unread"`
}

func (q *Queries) ListAllChatSessionsByCreator(ctx context.Context, arg ListAllChatSessionsByCreatorParams) ([]ListAllChatSessionsByCreatorRow, error) {
	rows, err := q.db.Query(ctx, listAllChatSessionsByCreator, arg.WorkspaceID, arg.CreatorID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []ListAllChatSessionsByCreatorRow{}
	for rows.Next() {
		var i ListAllChatSessionsByCreatorRow
		if err := rows.Scan(
			&i.ID,
			&i.WorkspaceID,
			&i.AgentID,
			&i.CreatorID,
			&i.Title,
			&i.SessionID,
			&i.WorkDir,
			&i.Status,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.UnreadSince,
			&i.RuntimeID,
			&i.HasUnread,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listChatMessages = `-- name: ListChatMessages :many
SELECT id, chat_session_id, role, content, task_id, created_at, failure_reason, elapsed_ms FROM chat_message
WHERE chat_session_id = $1
ORDER BY created_at ASC
`

func (q *Queries) ListChatMessages(ctx context.Context, chatSessionID pgtype.UUID) ([]ChatMessage, error) {
	rows, err := q.db.Query(ctx, listChatMessages, chatSessionID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []ChatMessage{}
	for rows.Next() {
		var i ChatMessage
		if err := rows.Scan(
			&i.ID,
			&i.ChatSessionID,
			&i.Role,
			&i.Content,
			&i.TaskID,
			&i.CreatedAt,
			&i.FailureReason,
			&i.ElapsedMs,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listChatSessionsByCreator = `-- name: ListChatSessionsByCreator :many
SELECT cs.id, cs.workspace_id, cs.agent_id, cs.creator_id, cs.title, cs.session_id, cs.work_dir, cs.status, cs.created_at, cs.updated_at, cs.unread_since, cs.runtime_id,
       (cs.unread_since IS NOT NULL)::bool AS has_unread
FROM chat_session cs
WHERE cs.workspace_id = $1 AND cs.creator_id = $2 AND cs.status = 'active'
ORDER BY cs.updated_at DESC
`

type ListChatSessionsByCreatorParams struct {
	WorkspaceID pgtype.UUID `json:"workspace_id"`
	CreatorID   pgtype.UUID `json:"creator_id"`
}

type ListChatSessionsByCreatorRow struct {
	ID          pgtype.UUID        `json:"id"`
	WorkspaceID pgtype.UUID        `json:"workspace_id"`
	AgentID     pgtype.UUID        `json:"agent_id"`
	CreatorID   pgtype.UUID        `json:"creator_id"`
	Title       string             `json:"title"`
	SessionID   pgtype.Text        `json:"session_id"`
	WorkDir     pgtype.Text        `json:"work_dir"`
	Status      string             `json:"status"`
	CreatedAt   pgtype.Timestamptz `json:"created_at"`
	UpdatedAt   pgtype.Timestamptz `json:"updated_at"`
	UnreadSince pgtype.Timestamptz `json:"unread_since"`
	RuntimeID   pgtype.UUID        `json:"runtime_id"`
	HasUnread   bool               `json:"has_unread"`
}

// Returns active sessions with a boolean unread flag. Unread is strictly
// per-session: either the user has uncleared assistant replies in this
// session or they don't. Counting messages would be misleading.
func (q *Queries) ListChatSessionsByCreator(ctx context.Context, arg ListChatSessionsByCreatorParams) ([]ListChatSessionsByCreatorRow, error) {
	rows, err := q.db.Query(ctx, listChatSessionsByCreator, arg.WorkspaceID, arg.CreatorID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []ListChatSessionsByCreatorRow{}
	for rows.Next() {
		var i ListChatSessionsByCreatorRow
		if err := rows.Scan(
			&i.ID,
			&i.WorkspaceID,
			&i.AgentID,
			&i.CreatorID,
			&i.Title,
			&i.SessionID,
			&i.WorkDir,
			&i.Status,
			&i.CreatedAt,
			&i.UpdatedAt,
			&i.UnreadSince,
			&i.RuntimeID,
			&i.HasUnread,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listPendingChatTasksByCreator = `-- name: ListPendingChatTasksByCreator :many
SELECT atq.id AS task_id, atq.status, atq.chat_session_id
FROM agent_task_queue atq
JOIN chat_session cs ON cs.id = atq.chat_session_id
WHERE cs.workspace_id = $1
  AND cs.creator_id = $2
  AND atq.status IN ('queued', 'dispatched', 'running', 'waiting_local_directory')
ORDER BY atq.created_at DESC
`

type ListPendingChatTasksByCreatorParams struct {
	WorkspaceID pgtype.UUID `json:"workspace_id"`
	CreatorID   pgtype.UUID `json:"creator_id"`
}

type ListPendingChatTasksByCreatorRow struct {
	TaskID        pgtype.UUID `json:"task_id"`
	Status        string      `json:"status"`
	ChatSessionID pgtype.UUID `json:"chat_session_id"`
}

// Aggregate view of all in-flight chat tasks owned by a given creator in a
// workspace. Drives the FAB's "running" indicator when the chat window is
// closed and no single session's query is active.
func (q *Queries) ListPendingChatTasksByCreator(ctx context.Context, arg ListPendingChatTasksByCreatorParams) ([]ListPendingChatTasksByCreatorRow, error) {
	rows, err := q.db.Query(ctx, listPendingChatTasksByCreator, arg.WorkspaceID, arg.CreatorID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []ListPendingChatTasksByCreatorRow{}
	for rows.Next() {
		var i ListPendingChatTasksByCreatorRow
		if err := rows.Scan(&i.TaskID, &i.Status, &i.ChatSessionID); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const lockChatSessionForDelete = `-- name: LockChatSessionForDelete :one
SELECT id FROM chat_session
WHERE id = $1
FOR UPDATE
`

// Acquires an exclusive (FOR UPDATE) row lock on chat_session(id). Used by
// the delete path so that a concurrent SendChatMessage cannot enqueue a new
// agent_task_queue row referencing this session between our cancel and
// delete steps. The FK from agent_task_queue.chat_session_id takes a
// KEY SHARE lock on the parent row during INSERT validation, which
// conflicts with FOR UPDATE — concurrent inserts block here and then fail
// their FK check after we commit the delete.
func (q *Queries) LockChatSessionForDelete(ctx context.Context, id pgtype.UUID) (pgtype.UUID, error) {
	row := q.db.QueryRow(ctx, lockChatSessionForDelete, id)
	var id_2 pgtype.UUID
	err := row.Scan(&id_2)
	return id_2, err
}

const markChatSessionRead = `-- name: MarkChatSessionRead :exec
UPDATE chat_session SET unread_since = NULL
WHERE id = $1
`

// Clears unread_since, dropping the session's unread count to 0.
func (q *Queries) MarkChatSessionRead(ctx context.Context, id pgtype.UUID) error {
	_, err := q.db.Exec(ctx, markChatSessionRead, id)
	return err
}

const setUnreadSinceIfNull = `-- name: SetUnreadSinceIfNull :exec
UPDATE chat_session SET unread_since = now()
WHERE id = $1 AND unread_since IS NULL
`

// Atomically stamps the first unread assistant message's arrival time.
// No-op if the session is already in "has unread" state — keeps the earliest
// unread boundary stable across multiple incoming replies.
func (q *Queries) SetUnreadSinceIfNull(ctx context.Context, id pgtype.UUID) error {
	_, err := q.db.Exec(ctx, setUnreadSinceIfNull, id)
	return err
}

const touchChatSession = `-- name: TouchChatSession :exec
UPDATE chat_session SET updated_at = now()
WHERE id = $1
`

func (q *Queries) TouchChatSession(ctx context.Context, id pgtype.UUID) error {
	_, err := q.db.Exec(ctx, touchChatSession, id)
	return err
}

const updateChatSessionSession = `-- name: UpdateChatSessionSession :exec
UPDATE chat_session
SET session_id = COALESCE($1, session_id),
    work_dir = COALESCE($2, work_dir),
    runtime_id = COALESCE($3, runtime_id),
    updated_at = now()
WHERE id = $4
`

type UpdateChatSessionSessionParams struct {
	SessionID pgtype.Text `json:"session_id"`
	WorkDir   pgtype.Text `json:"work_dir"`
	RuntimeID pgtype.UUID `json:"runtime_id"`
	ID        pgtype.UUID `json:"id"`
}

// Updates the resume pointer for a chat session. Empty/NULL inputs are
// ignored via COALESCE so a task that completes without a session_id (e.g.
// the agent crashed before establishing one) cannot wipe out a previously
// recorded resume pointer. This makes the chat memory robust against
// intermittent agent failures.
func (q *Queries) UpdateChatSessionSession(ctx context.Context, arg UpdateChatSessionSessionParams) error {
	_, err := q.db.Exec(ctx, updateChatSessionSession,
		arg.SessionID,
		arg.WorkDir,
		arg.RuntimeID,
		arg.ID,
	)
	return err
}

const updateChatSessionTitle = `-- name: UpdateChatSessionTitle :one
UPDATE chat_session SET title = $2, updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, agent_id, creator_id, title, session_id, work_dir, status, created_at, updated_at, unread_since, runtime_id
`

type UpdateChatSessionTitleParams struct {
	ID    pgtype.UUID `json:"id"`
	Title string      `json:"title"`
}

func (q *Queries) UpdateChatSessionTitle(ctx context.Context, arg UpdateChatSessionTitleParams) (ChatSession, error) {
	row := q.db.QueryRow(ctx, updateChatSessionTitle, arg.ID, arg.Title)
	var i ChatSession
	err := row.Scan(
		&i.ID,
		&i.WorkspaceID,
		&i.AgentID,
		&i.CreatorID,
		&i.Title,
		&i.SessionID,
		&i.WorkDir,
		&i.Status,
		&i.CreatedAt,
		&i.UpdatedAt,
		&i.UnreadSince,
		&i.RuntimeID,
	)
	return i, err
}
