/**
 * Orchestration run store — lightweight SQLite persistence for async runs.
 *
 * Stores run records so nexus_run_status can show stage + progress even
 * after the MCP async-gate returned {queued:true, runId}.
 *
 * NOT a rewrite of orchestrator.ts. Additive only.
 * Used by: async-gate (stage updates), nexus_run_status (polling).
 */
import path from 'path';
import fs from 'fs';
import { randomUUID } from 'crypto';
import BetterSqlite3 from 'better-sqlite3';
import { resolveNexusStateDir } from '../runtime-registry.js';
// ─── Schema ───────────────────────────────────────────────────────────────────
const SCHEMA = `
CREATE TABLE IF NOT EXISTS orchestrations (
  run_id          TEXT PRIMARY KEY,
  goal            TEXT NOT NULL,
  status          TEXT NOT NULL DEFAULT 'queued'
    CHECK(status IN ('queued','running','completed','failed')),
  stage           TEXT NOT NULL DEFAULT 'intake',
  progress        INTEGER NOT NULL DEFAULT 0,
  client          TEXT NOT NULL DEFAULT 'unknown',
  started_at      INTEGER NOT NULL,
  updated_at      INTEGER NOT NULL,
  completed_at    INTEGER,
  error           TEXT,
  result_summary  TEXT
);
CREATE INDEX IF NOT EXISTS idx_orch_status ON orchestrations(status, started_at);
CREATE INDEX IF NOT EXISTS idx_orch_client ON orchestrations(client, status);

CREATE TABLE IF NOT EXISTS orch_spans (
  span_id     TEXT PRIMARY KEY,
  run_id      TEXT NOT NULL,
  parent_id   TEXT,
  name        TEXT NOT NULL,
  started_at  INTEGER NOT NULL,
  ended_at    INTEGER,
  status      TEXT NOT NULL DEFAULT 'ok' CHECK(status IN ('ok','error')),
  attrs_json  TEXT NOT NULL DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_spans_run ON orch_spans(run_id, started_at);
`;
// ─── DB singleton ─────────────────────────────────────────────────────────────
let _db = null;
function getDb() {
    if (_db)
        return _db;
    const dir = resolveNexusStateDir();
    // One-time lazy mkdir; better-sqlite3 opens sync by necessity. We keep this
    // off the hot path by caching _db — subsequent calls never hit fs.
    // eslint-disable-next-line no-restricted-syntax
    fs.mkdirSync(dir, { recursive: true });
    _db = new BetterSqlite3(path.join(dir, 'orchestrations.db'));
    _db.exec(SCHEMA);
    return _db;
}
// ─── CRUD ─────────────────────────────────────────────────────────────────────
export function createRun(runId, goal, client = 'unknown') {
    const now = Date.now();
    getDb().prepare(`
        INSERT OR IGNORE INTO orchestrations (run_id, goal, status, stage, progress, client, started_at, updated_at)
        VALUES (?, ?, 'queued', 'intake', 0, ?, ?, ?)
    `).run(runId, goal.slice(0, 512), client, now, now);
    return getRun(runId);
}
export function updateStage(runId, stage, progress) {
    getDb().prepare(`
        UPDATE orchestrations SET stage = ?, progress = ?, status = 'running', updated_at = ?
        WHERE run_id = ?
    `).run(stage, Math.min(99, progress), Date.now(), runId);
}
export function completeRun(runId, summary) {
    const now = Date.now();
    getDb().prepare(`
        UPDATE orchestrations SET status = 'completed', progress = 100, stage = 'done',
          result_summary = ?, completed_at = ?, updated_at = ?
        WHERE run_id = ?
    `).run(summary.slice(0, 1024), now, now, runId);
}
export function failRun(runId, error) {
    const now = Date.now();
    getDb().prepare(`
        UPDATE orchestrations SET status = 'failed', stage = 'error',
          error = ?, completed_at = ?, updated_at = ?
        WHERE run_id = ?
    `).run(error.slice(0, 512), now, now, runId);
}
export function getRun(runId) {
    const row = getDb().prepare('SELECT * FROM orchestrations WHERE run_id = ?').get(runId);
    if (!row)
        return null;
    return {
        runId: row.run_id,
        goal: row.goal,
        status: row.status,
        stage: row.stage,
        progress: row.progress,
        client: row.client,
        startedAt: row.started_at,
        updatedAt: row.updated_at,
        completedAt: row.completed_at,
        error: row.error,
        resultSummary: row.result_summary,
    };
}
export function listRecentRuns(limit = 20, client) {
    const rows = client
        ? getDb().prepare('SELECT * FROM orchestrations WHERE client = ? ORDER BY started_at DESC LIMIT ?').all(client, limit)
        : getDb().prepare('SELECT * FROM orchestrations ORDER BY started_at DESC LIMIT ?').all(limit);
    return rows.map((r) => ({
        runId: r.run_id, goal: r.goal, status: r.status, stage: r.stage,
        progress: r.progress, client: r.client, startedAt: r.started_at,
        updatedAt: r.updated_at, completedAt: r.completed_at,
        error: r.error, resultSummary: r.result_summary,
    }));
}
/** Prune completed runs older than retainDays (default 30). */
export function pruneOldRuns(retainDays = 30) {
    const threshold = Date.now() - retainDays * 24 * 60 * 60 * 1000;
    const result = getDb().prepare(`
        DELETE FROM orchestrations WHERE status IN ('completed','failed') AND completed_at < ?
    `).run(threshold);
    return result.changes;
}
// ─── Span API (OTel-style trace spans) ───────────────────────────────────────
export function startSpan(runId, name, parentId, attrs = {}) {
    const spanId = randomUUID();
    getDb().prepare(`
        INSERT OR IGNORE INTO orch_spans (span_id, run_id, parent_id, name, started_at, attrs_json)
        VALUES (?, ?, ?, ?, ?, ?)
    `).run(spanId, runId, parentId ?? null, name, Date.now(), JSON.stringify(attrs));
    return spanId;
}
export function endSpan(spanId, status = 'ok', attrs = {}) {
    const existing = getDb().prepare('SELECT attrs_json FROM orch_spans WHERE span_id = ?').get(spanId);
    if (!existing)
        return;
    const merged = { ...JSON.parse(existing.attrs_json ?? '{}'), ...attrs };
    getDb().prepare(`
        UPDATE orch_spans SET ended_at = ?, status = ?, attrs_json = ? WHERE span_id = ?
    `).run(Date.now(), status, JSON.stringify(merged), spanId);
}
export function getSpans(runId) {
    const rows = getDb().prepare('SELECT * FROM orch_spans WHERE run_id = ? ORDER BY started_at ASC').all(runId);
    return rows.map(r => ({
        spanId: r.span_id, runId: r.run_id, parentId: r.parent_id,
        name: r.name, startedAt: r.started_at, endedAt: r.ended_at,
        status: r.status, attrs: JSON.parse(r.attrs_json ?? '{}'),
    }));
}
