/**
 * Local SQLite persistence for cloud-dispatched tasks.
 *
 * Every dispatch, status poll, and list query flows through this module so
 * that task history survives across CLI invocations without hitting the
 * remote provider each time.
 */
import * as fs from 'fs';
import * as path from 'path';
import Database from '../sqlite.js';
import { getCloudDir } from '../state.js';
import { registerCloudSession } from './session-index.js';
const CLOUD_DIR = getCloudDir();
const DB_PATH = path.join(CLOUD_DIR, 'tasks.db');
const SCHEMA = `
CREATE TABLE IF NOT EXISTS tasks (
  id TEXT PRIMARY KEY,
  provider TEXT NOT NULL,
  status TEXT NOT NULL,
  agent TEXT,
  prompt TEXT NOT NULL,
  repo TEXT,
  branch TEXT,
  pr_url TEXT,
  summary TEXT,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  provider_data TEXT
);
CREATE INDEX IF NOT EXISTS idx_tasks_provider ON tasks(provider);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_created ON tasks(created_at DESC);
`;
let _db = null;
/** Lazy-initialize the SQLite connection, creating the database and schema on first access. */
function db() {
    if (_db)
        return _db;
    fs.mkdirSync(CLOUD_DIR, { recursive: true });
    _db = new Database(DB_PATH);
    _db.pragma('journal_mode = WAL');
    _db.exec(SCHEMA);
    return _db;
}
/**
 * Release the tasks.db connection so the file (and its WAL sidecars) can be
 * removed. The mirror of `closeDB()` in `../session/db.ts`, which this module
 * had no counterpart for — on Windows an open handle makes the file
 * un-unlinkable, so anything tearing down a temp home must close this too.
 */
export function closeStore() {
    if (_db) {
        _db.close();
        _db = null;
    }
}
/** Persist a task snapshot, replacing any existing row with the same ID. */
export function insertTask(task) {
    db().prepare(`
    INSERT OR REPLACE INTO tasks (id, provider, status, agent, prompt, repo, branch, pr_url, summary, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(task.id, task.provider, task.status, task.agent ?? null, task.prompt, task.repo ?? null, task.branch ?? null, task.prUrl ?? null, task.summary ?? null, task.createdAt, task.updatedAt);
    // Every cloud dispatch flows through here, so this is the one chokepoint that
    // reconciles the cloud store with the session index: register a session row
    // keyed by the real execution id so the launch is mappable to a session
    // immediately, not only after a later proxy discovery (see session-index.ts).
    registerCloudSession(task);
}
/** Update a task's status and optionally patch summary, PR URL, or branch. */
export function updateTaskStatus(id, status, extra) {
    const now = new Date().toISOString();
    const sets = ['status = ?', 'updated_at = ?'];
    const params = [status, now];
    if (extra?.summary !== undefined) {
        sets.push('summary = ?');
        params.push(extra.summary);
    }
    if (extra?.prUrl !== undefined) {
        sets.push('pr_url = ?');
        params.push(extra.prUrl);
    }
    if (extra?.branch !== undefined) {
        sets.push('branch = ?');
        params.push(extra.branch);
    }
    params.push(id);
    db().prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...params);
    // Keep the session index in lockstep with the store on every poll: refresh the
    // row so its `[cloud/<status>]` label tracks the task's lifecycle (and picks up
    // a newly-opened PR url). Reads the just-written row so the label reflects the
    // update we just made, not a stale snapshot.
    const updated = getTaskById(id);
    if (updated)
        registerCloudSession(updated);
}
/** Fetch a single task by its provider-assigned ID, or null if not found locally. */
export function getTaskById(id) {
    const row = db().prepare('SELECT * FROM tasks WHERE id = ?').get(id);
    return row ? rowToTask(row) : null;
}
/** List tasks with optional provider/status filters, ordered newest-first. */
export function listTasks(filter) {
    const clauses = [];
    const params = [];
    if (filter?.provider) {
        clauses.push('provider = ?');
        params.push(filter.provider);
    }
    if (filter?.status) {
        clauses.push('status = ?');
        params.push(filter.status);
    }
    const where = clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';
    const limit = filter?.limit ?? 50;
    const rows = db().prepare(`SELECT * FROM tasks ${where} ORDER BY created_at DESC LIMIT ?`).all(...params, limit);
    return rows.map(rowToTask);
}
const TERMINAL_STATUSES = ['completed', 'failed', 'cancelled'];
/** Return tasks still in a transient state (queued, allocating, running, input_required). */
export function listActiveTasks() {
    const placeholders = TERMINAL_STATUSES.map(() => '?').join(', ');
    const rows = db().prepare(`SELECT * FROM tasks WHERE status NOT IN (${placeholders}) ORDER BY created_at DESC`).all(...TERMINAL_STATUSES);
    return rows.map(rowToTask);
}
/** Map a raw SQLite row to a typed CloudTask, converting snake_case columns to camelCase. */
function rowToTask(row) {
    return {
        id: row.id,
        provider: row.provider,
        status: row.status,
        agent: row.agent || undefined,
        prompt: row.prompt,
        repo: row.repo || undefined,
        branch: row.branch || undefined,
        prUrl: row.pr_url || undefined,
        summary: row.summary || undefined,
        createdAt: row.created_at,
        updatedAt: row.updated_at,
    };
}
