import { isRecord } from "../internal/rowMappers.js";
export class CommandInboxRepository {
    #db;
    constructor(db) {
        this.#db = db;
    }
    receiveCommand(input) {
        const now = new Date().toISOString();
        this.#db
            .prepare(`INSERT OR IGNORE INTO command_inbox (command_id, command_type, payload_json, status, received_at)
         VALUES (?, ?, ?, 'received', ?)`)
            .run(input.commandId, input.commandType, input.payloadJson, now);
        const row = this.#db
            .prepare(`SELECT command_id, command_type, payload_json, status, received_at, processed_at, failure_reason
         FROM command_inbox WHERE command_id = ?`)
            .get(input.commandId);
        if (!isRecord(row))
            throw new Error("Failed to store command.");
        return mapCommand(row);
    }
    /** Returns true if the command already exists (idempotency guard). */
    exists(commandId) {
        const row = this.#db
            .prepare("SELECT 1 FROM command_inbox WHERE command_id = ?")
            .get(commandId);
        return isRecord(row);
    }
    findCommand(commandId) {
        const row = this.#db
            .prepare(`SELECT command_id, command_type, payload_json, status, received_at, processed_at, failure_reason
         FROM command_inbox WHERE command_id = ?`)
            .get(commandId);
        return isRecord(row) ? mapCommand(row) : undefined;
    }
    listPending() {
        const rows = this.#db
            .prepare(`SELECT command_id, command_type, payload_json, status, received_at, processed_at, failure_reason
         FROM command_inbox WHERE status = 'received'
         ORDER BY received_at ASC LIMIT 50`)
            .all();
        return rows.filter(isRecord).map(mapCommand);
    }
    listAwaitingConfirmation() {
        const rows = this.#db
            .prepare(`SELECT command_id, command_type, payload_json, status, received_at, processed_at, failure_reason
         FROM command_inbox WHERE status = 'awaiting_confirmation'
         ORDER BY received_at ASC LIMIT 50`)
            .all();
        return rows.filter(isRecord).map(mapCommand);
    }
    markProcessed(commandId) {
        const now = new Date().toISOString();
        this.#db
            .prepare(`UPDATE command_inbox SET status = 'processed', processed_at = ? WHERE command_id = ?`)
            .run(now, commandId);
    }
    markFailed(commandId, reason) {
        const now = new Date().toISOString();
        this.#db
            .prepare(`UPDATE command_inbox SET status = 'failed', processed_at = ?, failure_reason = ? WHERE command_id = ?`)
            .run(now, reason, commandId);
    }
    /**
     * Parks a verified high-risk command pending local confirmation. It leaves the
     * 'received' queue (so the drainer does not re-process it) without being
     * marked processed or failed — a later confirmation resolves it.
     */
    markAwaitingConfirmation(commandId) {
        this.#db
            .prepare(`UPDATE command_inbox SET status = 'awaiting_confirmation' WHERE command_id = ?`)
            .run(commandId);
    }
    /**
     * Claims an issuer-signed rca.commandId for the given inbox (transport) row.
     * First writer wins (INSERT OR IGNORE on the rca_command_id primary key), so a
     * later transport redelivery of the same signed command cannot overwrite the
     * claim. Idempotent for the SAME inbox row (re-verification during the
     * confirm-resolve path).
     */
    claimRemoteCommand(rcaCommandId, inboxCommandId) {
        const now = new Date().toISOString();
        this.#db
            .prepare(`INSERT OR IGNORE INTO remote_command_claims (rca_command_id, inbox_command_id, claimed_at)
         VALUES (?, ?, ?)`)
            .run(rcaCommandId, inboxCommandId, now);
    }
    /**
     * True when the signed rca.commandId has already been claimed by a DIFFERENT
     * inbox (transport) row — i.e. a replay of a captured valid command under a
     * fresh transport id. The same inbox row re-checking its own claim is NOT a
     * replay (the confirm-resolve path re-verifies before dispatch).
     */
    isRemoteCommandReplay(rcaCommandId, inboxCommandId) {
        const row = this.#db
            .prepare("SELECT inbox_command_id FROM remote_command_claims WHERE rca_command_id = ?")
            .get(rcaCommandId);
        return isRecord(row) && String(row.inbox_command_id) !== inboxCommandId;
    }
}
function mapCommand(row) {
    return {
        commandId: String(row.command_id),
        commandType: String(row.command_type),
        payloadJson: String(row.payload_json),
        status: String(row.status),
        receivedAt: String(row.received_at),
        processedAt: typeof row.processed_at === "string" ? row.processed_at : null,
        failureReason: typeof row.failure_reason === "string" ? row.failure_reason : null,
    };
}
