import { join, resolve } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { BestAgentError, createRandomId } from "@best-agent/foundation";
export class LocalApprovalStore {
    #database;
    constructor(options) {
        this.#database = new DatabaseSync(join(resolve(options.storageRoot), "state.sqlite"));
        this.#database.exec(`
      PRAGMA journal_mode = WAL;
      PRAGMA busy_timeout = 2500;

      CREATE TABLE IF NOT EXISTS approval_grants (
        id TEXT PRIMARY KEY,
        scope TEXT NOT NULL,
        group_name TEXT NOT NULL,
        kind_name TEXT,
        action TEXT NOT NULL,
        thread_id TEXT,
        created_at TEXT NOT NULL
      );

      CREATE TABLE IF NOT EXISTS approval_reviews (
        approval_id TEXT PRIMARY KEY,
        thread_id TEXT NOT NULL,
        turn_id TEXT NOT NULL,
        group_name TEXT NOT NULL,
        kind_name TEXT NOT NULL,
        action TEXT NOT NULL,
        available_scopes TEXT NOT NULL,
        risk_class TEXT,
        review_mode TEXT,
        sandbox_profile TEXT,
        guardian_assessment TEXT,
        reason TEXT,
        requested_at TEXT NOT NULL,
        status TEXT NOT NULL,
        decision TEXT,
        scope TEXT,
        resolved_at TEXT
      );
    `);
        try {
            this.#database.exec(`ALTER TABLE approval_grants ADD COLUMN thread_id TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        try {
            this.#database.exec(`ALTER TABLE approval_grants ADD COLUMN kind_name TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        try {
            this.#database.exec(`ALTER TABLE approval_reviews ADD COLUMN risk_class TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        try {
            this.#database.exec(`ALTER TABLE approval_reviews ADD COLUMN review_mode TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        try {
            this.#database.exec(`ALTER TABLE approval_reviews ADD COLUMN sandbox_profile TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        try {
            this.#database.exec(`ALTER TABLE approval_reviews ADD COLUMN guardian_assessment TEXT`);
        }
        catch {
            // Existing databases already have the column.
        }
        this.#database.exec(`
      DROP INDEX IF EXISTS approval_grants_scope_group_action_idx;

      CREATE UNIQUE INDEX IF NOT EXISTS approval_grants_scope_group_action_idx
        ON approval_grants(scope, group_name, IFNULL(kind_name, group_name), action, IFNULL(thread_id, ''));

      CREATE INDEX IF NOT EXISTS approval_reviews_thread_idx
        ON approval_reviews(thread_id, requested_at DESC, approval_id DESC);
    `);
    }
    close() {
        this.#database.close();
    }
    async deleteGrantsForThread(threadId) {
        this.#database.prepare(`DELETE FROM approval_grants WHERE thread_id = ?`).run(threadId);
    }
    async deleteReviewsForThread(threadId) {
        this.#database.prepare(`DELETE FROM approval_reviews WHERE thread_id = ?`).run(threadId);
    }
    async findGrant(group, kind, action, input = {}) {
        if (input.threadId) {
            const threadRow = this.#database
                .prepare(`SELECT id, scope, group_name, kind_name, action, created_at, thread_id
           FROM approval_grants
           WHERE group_name = ? AND IFNULL(kind_name, group_name) = ? AND action = ? AND scope = 'thread' AND thread_id = ?
           ORDER BY created_at DESC, id DESC
           LIMIT 1`)
                .get(group, kind, action, input.threadId);
            if (threadRow) {
                return hydrateGrant(threadRow);
            }
        }
        const row = this.#database
            .prepare(`SELECT id, scope, group_name, kind_name, action, created_at, thread_id
         FROM approval_grants
         WHERE group_name = ? AND IFNULL(kind_name, group_name) = ? AND action = ? AND scope = 'workspace' AND thread_id IS NULL
         ORDER BY created_at DESC, id DESC
         LIMIT 1`)
            .get(group, kind, action);
        return row ? hydrateGrant(row) : null;
    }
    async listGrants() {
        const rows = this.#database
            .prepare(`SELECT id, scope, group_name, kind_name, action, created_at, thread_id
         FROM approval_grants
         ORDER BY created_at DESC, id DESC`)
            .all();
        return rows.map(hydrateGrant);
    }
    async listReviews(threadId, limit = 50) {
        const rows = this.#database
            .prepare(`SELECT approval_id, thread_id, turn_id, group_name, kind_name, action, available_scopes, risk_class, review_mode, sandbox_profile, guardian_assessment, reason, requested_at, status, decision, scope, resolved_at
         FROM approval_reviews
         WHERE thread_id = ?
         ORDER BY requested_at DESC, approval_id DESC
         LIMIT ?`)
            .all(threadId, limit);
        return rows.map(hydrateReview);
    }
    async revokeGrant(grantId) {
        const result = this.#database.prepare(`DELETE FROM approval_grants WHERE id = ?`).run(grantId);
        if (result.changes === 0) {
            throw new BestAgentError("APPROVAL_GRANT_NOT_FOUND", `Approval grant ${grantId} not found`);
        }
    }
    async saveGrant(input) {
        const existing = await this.#findScopedGrant(input);
        if (existing && existing.scope === input.scope && existing.threadId === input.threadId) {
            return existing;
        }
        const grant = {
            id: createRandomId("grant"),
            scope: input.scope,
            group: input.group,
            kind: input.kind,
            action: input.action,
            createdAt: new Date().toISOString(),
            threadId: input.scope === "thread" ? input.threadId : undefined,
        };
        this.#database
            .prepare(`INSERT INTO approval_grants (id, scope, group_name, kind_name, action, thread_id, created_at)
         VALUES (?, ?, ?, ?, ?, ?, ?)`)
            .run(grant.id, grant.scope, grant.group, grant.kind, grant.action, grant.threadId ?? null, grant.createdAt);
        return grant;
    }
    async saveReviewRequest(approval) {
        this.#database
            .prepare(`INSERT INTO approval_reviews (
          approval_id, thread_id, turn_id, group_name, kind_name, action, available_scopes, risk_class, review_mode, sandbox_profile, guardian_assessment, reason, requested_at, status, decision, scope, resolved_at
         )
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, NULL)
         ON CONFLICT(approval_id) DO UPDATE SET
           thread_id = excluded.thread_id,
           turn_id = excluded.turn_id,
           group_name = excluded.group_name,
           kind_name = excluded.kind_name,
           action = excluded.action,
           available_scopes = excluded.available_scopes,
           risk_class = excluded.risk_class,
           review_mode = excluded.review_mode,
           sandbox_profile = excluded.sandbox_profile,
           guardian_assessment = excluded.guardian_assessment,
           reason = excluded.reason,
           requested_at = excluded.requested_at`)
            .run(approval.id, approval.threadId, approval.turnId, approval.group, approval.kind, approval.action, JSON.stringify(approval.availableScopes), approval.riskClass, approval.reviewMode, approval.sandboxProfile, approval.guardianAssessment ? JSON.stringify(approval.guardianAssessment) : null, approval.reason ?? null, approval.requestedAt);
        return {
            ...approval,
            status: "pending",
        };
    }
    async settleReview(approvalId, input) {
        const resolvedAt = new Date().toISOString();
        const result = this.#database
            .prepare(`UPDATE approval_reviews
         SET status = ?, decision = ?, scope = ?, resolved_at = ?
         WHERE approval_id = ?`)
            .run(input.status, input.decision ?? null, input.scope ?? null, resolvedAt, approvalId);
        if (result.changes === 0) {
            return null;
        }
        const row = this.#database
            .prepare(`SELECT approval_id, thread_id, turn_id, group_name, kind_name, action, available_scopes, risk_class, review_mode, sandbox_profile, guardian_assessment, reason, requested_at, status, decision, scope, resolved_at
         FROM approval_reviews
         WHERE approval_id = ?`)
            .get(approvalId);
        return row ? hydrateReview(row) : null;
    }
    async #findScopedGrant(input) {
        if (input.scope === "thread") {
            const row = this.#database
                .prepare(`SELECT id, scope, group_name, kind_name, action, created_at, thread_id
           FROM approval_grants
           WHERE group_name = ? AND IFNULL(kind_name, group_name) = ? AND action = ? AND scope = 'thread' AND thread_id = ?
           ORDER BY created_at DESC, id DESC
           LIMIT 1`)
                .get(input.group, input.kind, input.action, input.threadId);
            return row ? hydrateGrant(row) : null;
        }
        const row = this.#database
            .prepare(`SELECT id, scope, group_name, kind_name, action, created_at, thread_id
         FROM approval_grants
         WHERE group_name = ? AND IFNULL(kind_name, group_name) = ? AND action = ? AND scope = 'workspace' AND thread_id IS NULL
         ORDER BY created_at DESC, id DESC
         LIMIT 1`)
            .get(input.group, input.kind, input.action);
        return row ? hydrateGrant(row) : null;
    }
}
function hydrateGrant(row) {
    return {
        id: row.id,
        scope: row.scope,
        group: row.group_name,
        kind: (row.kind_name ?? row.group_name),
        action: row.action,
        createdAt: row.created_at,
        threadId: row.thread_id ?? undefined,
    };
}
function hydrateReview(row) {
    return {
        id: row.approval_id,
        threadId: row.thread_id,
        turnId: row.turn_id,
        group: row.group_name,
        kind: row.kind_name,
        action: row.action,
        availableScopes: JSON.parse(row.available_scopes),
        riskClass: (row.risk_class ?? "high"),
        reviewMode: (row.review_mode ?? "guardian"),
        sandboxProfile: (row.sandbox_profile ?? "none"),
        ...(row.guardian_assessment ? { guardianAssessment: JSON.parse(row.guardian_assessment) } : {}),
        ...(row.reason ? { reason: row.reason } : {}),
        requestedAt: row.requested_at,
        status: row.status,
        ...(row.decision ? { decision: row.decision } : {}),
        ...(row.scope ? { scope: row.scope } : {}),
        ...(row.resolved_at ? { resolvedAt: row.resolved_at } : {}),
    };
}
//# sourceMappingURL=approval-store.js.map