"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.resetAnalysisRunStoreForTests = resetAnalysisRunStoreForTests;
exports.persistAnalysisRunState = persistAnalysisRunState;
exports.heartbeatAnalysisRun = heartbeatAnalysisRun;
exports.getAnalysisRunLifecycle = getAnalysisRunLifecycle;
exports.isAnalysisRunHeartbeatFresh = isAnalysisRunHeartbeatFresh;
exports.failInterruptedAnalysisRunsOnStartup = failInterruptedAnalysisRunsOnStartup;
const enterpriseDb_1 = require("./enterpriseDb");
let singletonDb = null;
let singletonDbPath = null;
function getAnalysisRunDb() {
    const dbPath = (0, enterpriseDb_1.resolveEnterpriseDbPath)();
    if (!singletonDb || singletonDbPath !== dbPath) {
        singletonDb?.close();
        singletonDb = (0, enterpriseDb_1.openEnterpriseDb)(dbPath);
        singletonDbPath = dbPath;
    }
    return singletonDb;
}
function resetAnalysisRunStoreForTests() {
    singletonDb?.close();
    singletonDb = null;
    singletonDbPath = null;
}
function isTerminalStatus(status) {
    return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'quota_exceeded';
}
function ensureAnalysisRunGraph(db, scope, now) {
    db.prepare(`
    INSERT OR IGNORE INTO organizations (id, name, status, plan, created_at, updated_at)
    VALUES (?, ?, 'active', 'enterprise', ?, ?)
  `).run(scope.tenantId, scope.tenantId, now, now);
    db.prepare(`
    INSERT OR IGNORE INTO workspaces (id, tenant_id, name, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?)
  `).run(scope.workspaceId, scope.tenantId, scope.workspaceId, now, now);
    if (scope.userId) {
        db.prepare(`
      INSERT INTO users (id, tenant_id, email, display_name, idp_subject, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(id) DO UPDATE SET
        email = excluded.email,
        display_name = excluded.display_name,
        updated_at = excluded.updated_at
    `).run(scope.userId, scope.tenantId, `${scope.userId}@analysis-run.local`, scope.userId, `analysis-run:${scope.userId}`, now, now);
    }
    db.prepare(`
    INSERT OR IGNORE INTO trace_assets
      (id, tenant_id, workspace_id, owner_user_id, local_path, size_bytes, status, metadata_json, created_at)
    VALUES
      (?, ?, ?, ?, ?, 0, 'metadata_only', ?, ?)
  `).run(scope.traceId, scope.tenantId, scope.workspaceId, scope.userId ?? null, `metadata-only:${scope.traceId}`, JSON.stringify({ source: 'analysis_run', sessionId: scope.sessionId, runId: scope.runId }), now);
    db.prepare(`
    INSERT OR IGNORE INTO analysis_sessions
      (id, tenant_id, workspace_id, trace_id, created_by, title, visibility, status, created_at, updated_at)
    VALUES
      (?, ?, ?, ?, ?, ?, 'private', 'running', ?, ?)
  `).run(scope.sessionId, scope.tenantId, scope.workspaceId, scope.traceId, scope.userId ?? null, `Agent session ${scope.sessionId}`, now, now);
    db.prepare(`
    INSERT OR IGNORE INTO analysis_runs
      (id, tenant_id, workspace_id, session_id, mode, status, question, started_at, completed_at, heartbeat_at, updated_at)
    VALUES
      (?, ?, ?, ?, ?, 'running', ?, ?, NULL, ?, ?)
  `).run(scope.runId, scope.tenantId, scope.workspaceId, scope.sessionId, scope.mode ?? 'agent', scope.query ?? '', now, now, now);
}
function persistAnalysisRunState(scope, status, options = {}) {
    const now = options.now ?? Date.now();
    const terminal = isTerminalStatus(status);
    const db = getAnalysisRunDb();
    const write = db.transaction(() => {
        ensureAnalysisRunGraph(db, scope, now);
        db.prepare(`
      UPDATE analysis_runs
      SET status = ?,
          question = CASE WHEN ? <> '' THEN ? ELSE question END,
          heartbeat_at = ?,
          updated_at = ?,
          completed_at = CASE WHEN ? THEN COALESCE(completed_at, ?) ELSE completed_at END,
          error_json = ?
      WHERE tenant_id = ?
        AND workspace_id = ?
        AND id = ?
    `).run(status, scope.query ?? '', scope.query ?? '', now, now, terminal ? 1 : 0, now, options.error ? JSON.stringify({ message: options.error }) : null, scope.tenantId, scope.workspaceId, scope.runId);
        db.prepare(`
      UPDATE analysis_sessions
      SET status = ?, updated_at = ?
      WHERE tenant_id = ?
        AND workspace_id = ?
        AND id = ?
    `).run(terminal ? status : 'running', now, scope.tenantId, scope.workspaceId, scope.sessionId);
    });
    write();
}
function heartbeatAnalysisRun(scope, now = Date.now()) {
    const db = getAnalysisRunDb();
    const write = db.transaction(() => {
        ensureAnalysisRunGraph(db, scope, now);
        db.prepare(`
      UPDATE analysis_runs
      SET heartbeat_at = ?, updated_at = ?
      WHERE tenant_id = ?
        AND workspace_id = ?
        AND id = ?
        AND status IN ('pending', 'running', 'awaiting_user')
    `).run(now, now, scope.tenantId, scope.workspaceId, scope.runId);
        db.prepare(`
      UPDATE analysis_sessions
      SET updated_at = ?
      WHERE tenant_id = ?
        AND workspace_id = ?
        AND id = ?
    `).run(now, scope.tenantId, scope.workspaceId, scope.sessionId);
    });
    write();
}
function getAnalysisRunLifecycle(scope, runId) {
    const db = getAnalysisRunDb();
    const row = db.prepare(`
    SELECT id, status, started_at, completed_at, heartbeat_at, updated_at, error_json
    FROM analysis_runs
    WHERE tenant_id = ?
      AND workspace_id = ?
      AND id = ?
    LIMIT 1
  `).get(scope.tenantId, scope.workspaceId, runId);
    if (!row)
        return null;
    return {
        id: row.id,
        status: row.status,
        startedAt: row.started_at,
        completedAt: row.completed_at,
        heartbeatAt: row.heartbeat_at,
        updatedAt: row.updated_at,
        errorJson: row.error_json,
    };
}
function isAnalysisRunHeartbeatFresh(scope, runId, now, maxStaleMs) {
    const lifecycle = getAnalysisRunLifecycle(scope, runId);
    if (!lifecycle || isTerminalStatus(lifecycle.status))
        return false;
    const heartbeatAt = lifecycle.heartbeatAt ?? lifecycle.updatedAt ?? lifecycle.startedAt;
    return now - heartbeatAt <= maxStaleMs;
}
function failInterruptedAnalysisRunsOnStartup(options = {}) {
    const now = options.now ?? Date.now();
    const errorJson = JSON.stringify({
        message: options.error ?? 'Backend restarted before analysis completed',
        source: 'backend_startup_recovery',
    });
    const db = getAnalysisRunDb();
    return db.transaction(() => {
        const interrupted = db.prepare(`
      SELECT id, tenant_id, workspace_id, session_id, status
      FROM analysis_runs
      WHERE status IN ('pending', 'running', 'awaiting_user')
      ORDER BY updated_at ASC, started_at ASC, id ASC
    `).all();
        for (const run of interrupted) {
            db.prepare(`
        UPDATE analysis_runs
        SET status = 'failed',
            heartbeat_at = ?,
            updated_at = ?,
            completed_at = COALESCE(completed_at, ?),
            error_json = COALESCE(error_json, ?)
        WHERE tenant_id = ?
          AND workspace_id = ?
          AND id = ?
          AND status = ?
      `).run(now, now, now, errorJson, run.tenant_id, run.workspace_id, run.id, run.status);
            db.prepare(`
        UPDATE analysis_sessions
        SET status = 'failed',
            updated_at = ?
        WHERE tenant_id = ?
          AND workspace_id = ?
          AND id = ?
          AND status IN ('pending', 'running', 'awaiting_user')
      `).run(now, run.tenant_id, run.workspace_id, run.session_id);
        }
        return interrupted.map(run => ({
            id: run.id,
            tenantId: run.tenant_id,
            workspaceId: run.workspace_id,
            sessionId: run.session_id,
            previousStatus: run.status,
        }));
    })();
}
//# sourceMappingURL=analysisRunStore.js.map