import { isRunValidationKind, isRunValidationVerdict, reasonCodeForRunValidationVerdict, } from "./RunValidationOutcome.js";
const defaultExpectedKinds = ["build", "test"];
const maxDurationMs = 12 * 60 * 60 * 1000;
const maxRuntimeEventPages = 10;
const runtimeEventPageSize = 100;
export class RunValidationHarvester {
    localStore;
    options;
    constructor(localStore, options = {}) {
        this.localStore = localStore;
        this.options = options;
    }
    harvest(runId) {
        const run = this.localStore.findRun(runId);
        if (!run) {
            throw new Error("Run validation evidence is invalid.");
        }
        const events = this.listRuntimeEvents(run.id);
        const outcomes = events
            .map(readValidationOutcomeFromEvent)
            .filter((outcome) => Boolean(outcome));
        const expectedKinds = this.options.expectedKinds ?? defaultExpectedKinds;
        const observedKinds = new Set(outcomes.map((outcome) => outcome.kind));
        for (const kind of expectedKinds) {
            if (!observedKinds.has(kind)) {
                outcomes.push({
                    kind,
                    command: kind,
                    verdict: "missing-evidence",
                    durationMs: 0,
                    reasonCode: "runtime-validation-missing-evidence",
                });
            }
        }
        return sortOutcomes(outcomes, expectedKinds);
    }
    listRuntimeEvents(runId) {
        const events = [];
        let cursor;
        for (let pageIndex = 0; pageIndex < maxRuntimeEventPages; pageIndex += 1) {
            const page = this.localStore.listRuntimeEventsForRun(runId, {
                limit: runtimeEventPageSize,
                ...(cursor ? { cursor } : {}),
            });
            events.push(...page.events);
            if (!page.nextCursor)
                break;
            cursor = page.nextCursor;
        }
        return events;
    }
}
function sortOutcomes(outcomes, expectedKinds) {
    const order = new Map(expectedKinds.map((kind, index) => [kind, index]));
    return outcomes
        .map((outcome, index) => ({ outcome, index }))
        .sort((left, right) => {
        const leftOrder = order.get(left.outcome.kind) ?? Number.MAX_SAFE_INTEGER;
        const rightOrder = order.get(right.outcome.kind) ?? Number.MAX_SAFE_INTEGER;
        if (leftOrder !== rightOrder)
            return leftOrder - rightOrder;
        return left.index - right.index;
    })
        .map((entry) => entry.outcome);
}
function readValidationOutcomeFromEvent(event) {
    const metadata = event.metadata;
    const kind = readKind(metadata.validationKind);
    if (!kind)
        return undefined;
    const verdict = readVerdict(metadata, event);
    const durationMs = readDurationMs(metadata.validationDurationMs);
    const exitCode = readExitCode(metadata.validationExitCode);
    const command = readCommandLabel(metadata.validationCommand, kind);
    return {
        kind,
        command,
        verdict,
        durationMs,
        reasonCode: reasonCodeForRunValidationVerdict(verdict),
        ...(typeof exitCode === "number" ? { exitCode } : {}),
    };
}
function readKind(value) {
    return isRunValidationKind(value) ? value : undefined;
}
function readVerdict(metadata, event) {
    if (isRunValidationVerdict(metadata.validationVerdict)) {
        return metadata.validationVerdict;
    }
    const exitCode = readExitCode(metadata.validationExitCode);
    if (typeof exitCode === "number")
        return exitCode === 0 ? "passed" : "failed";
    if (metadata.status === "failed" || event.tone === "destructive") {
        return "failed";
    }
    if (metadata.status === "completed")
        return "passed";
    return "missing-evidence";
}
function readCommandLabel(value, fallback) {
    if (typeof value !== "string")
        return fallback;
    const command = value.trim();
    if (!/^[A-Za-z0-9][A-Za-z0-9:_-]{0,79}$/u.test(command))
        return fallback;
    return command;
}
function readDurationMs(value) {
    if (typeof value !== "number" || !Number.isFinite(value))
        return 0;
    return Math.min(maxDurationMs, Math.max(0, Math.trunc(value)));
}
function readExitCode(value) {
    if (typeof value !== "number" || !Number.isFinite(value))
        return undefined;
    return Math.trunc(value);
}
