import { listAllRuntimeEvents, maxRunWallTimeSeconds, sumRunUsageCostUsd, } from "./runUsageCost.js";
export class RunCapMonitor {
    localStore;
    runEvidenceService;
    hostedEvidenceSubmitter;
    audit;
    constructor(localStore, runEvidenceService, hostedEvidenceSubmitter, audit) {
        this.localStore = localStore;
        this.runEvidenceService = runEvidenceService;
        this.hostedEvidenceSubmitter = hostedEvidenceSubmitter;
        this.audit = audit;
    }
    async evaluateAndSubmit(input) {
        const run = this.localStore.findRun(input.runId);
        if (!run)
            return { status: "not-hit", reasonCode: "run-not-found" };
        if (!run.capPolicy) {
            return { status: "not-hit", reasonCode: "cap-policy-missing" };
        }
        const existingHit = this.findExistingCapHit(run.id);
        const hit = existingHit ?? this.evaluateRun(run);
        if (!hit)
            return { status: "not-hit", reasonCode: "cap-not-hit" };
        if (!existingHit) {
            this.recordCapHit(run, hit);
        }
        const submission = this.runEvidenceService.captureBackendSubmission({
            runId: run.id,
            warnings: [{ label: `cap-hit:${hit.reasonCode}`, severity: "warning" }],
        });
        const hostedSubmission = await this.hostedEvidenceSubmitter.submit({
            submission,
            runIntentBridgeReceipt: input.runIntentBridgeReceipt,
            idempotencyKey: `cap-hit:${run.id}:${hit.reasonCode}`,
        });
        if (hostedSubmission.status === "succeeded") {
            this.audit?.append({
                type: "cap_hit_evidence_submitted",
                severity: "warning",
                message: "Run cap-hit evidence submitted.",
                metadata: {
                    runId: run.id,
                    workspaceId: run.workspaceId,
                    reasonCode: hit.reasonCode,
                    dimension: hit.dimension,
                },
            });
            return {
                status: "submitted",
                reasonCode: hit.reasonCode,
                response: hostedSubmission.response,
            };
        }
        return {
            status: "submission-failed",
            reasonCode: hit.reasonCode,
            submission: hostedSubmission,
        };
    }
    evaluateRun(run) {
        const events = listAllRuntimeEvents(this.localStore, run.id);
        const totalCostUsd = sumRunUsageCostUsd(events);
        const maxWallTimeSeconds = maxRunWallTimeSeconds(events);
        if (run.capPolicy?.costUsdPerRun !== undefined &&
            totalCostUsd > run.capPolicy.costUsdPerRun) {
            return {
                reasonCode: "blocked-budget",
                dimension: "costUsdPerRun",
            };
        }
        if (run.capPolicy?.wallTimeSecondsPerRun !== undefined &&
            maxWallTimeSeconds > run.capPolicy.wallTimeSecondsPerRun) {
            return {
                reasonCode: "blocked-timeout",
                dimension: "wallTimeSecondsPerRun",
            };
        }
        return undefined;
    }
    findExistingCapHit(runId) {
        const event = listAllRuntimeEvents(this.localStore, runId).find((candidate) => candidate.type === "cap-hit-transition");
        if (!event)
            return undefined;
        return readCapHit(event);
    }
    recordCapHit(run, hit) {
        this.localStore.updateRunStatus(run.id, hit.reasonCode, hit.reasonCode);
        this.localStore.appendRuntimeEvent({
            id: `cap-hit-${run.id}-${hit.reasonCode}`,
            source: "opencode",
            type: "cap-hit-transition",
            occurredAt: new Date().toISOString(),
            title: "Run cap hit",
            description: "The run crossed an operational cap.",
            tone: "warning",
            runId: run.id,
            ...(run.runtimeSessionId
                ? { runtimeSessionId: run.runtimeSessionId }
                : {}),
            metadata: {
                category: "cap",
                status: "blocked",
                reasonCode: hit.reasonCode,
                dimension: hit.dimension,
            },
        });
        this.audit?.append({
            type: "cap_hit_transition",
            severity: "warning",
            message: "Run transitioned into a cap-hit state.",
            metadata: {
                runId: run.id,
                workspaceId: run.workspaceId,
                reasonCode: hit.reasonCode,
                dimension: hit.dimension,
            },
        });
    }
}
function readCapHit(event) {
    if ((event.metadata.reasonCode === "blocked-budget" ||
        event.metadata.reasonCode === "blocked-timeout") &&
        (event.metadata.dimension === "costUsdPerRun" ||
            event.metadata.dimension === "wallTimeSecondsPerRun")) {
        return {
            reasonCode: event.metadata.reasonCode,
            dimension: event.metadata.dimension,
        };
    }
    return undefined;
}
