"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspectTaskChronologyEvidenceSync = inspectTaskChronologyEvidenceSync;
const context_capture_1 = require("../lifecycle-run-context/context-capture");
const evidence_store_1 = require("../lifecycle-run-context/evidence-store");
function inspectTaskChronologyEvidenceSync(options) {
    const evidenceEntries = [];
    const unrecordableValidationIds = new Set();
    const blockers = [];
    const openedAtMs = Date.parse(options.attemptOpenedAt);
    const baselineAtMs = options.baselineCapturedAt === null
        ? Number.NaN
        : Date.parse(options.baselineCapturedAt);
    for (const entry of options.entries) {
        if (entry.artifactPath !== options.artifactPath)
            continue;
        let envelope;
        try {
            const bytes = (0, evidence_store_1.readLifecycleEvidenceBytesSync)({
                repositoryRoot: options.repositoryRoot,
                runDirectory: options.runDirectory,
                entry,
                maximumBytes: context_capture_1.MAX_LIFECYCLE_RUN_RESULT_BYTES,
            }).bytes;
            const document = asRecord(JSON.parse(bytes.toString('utf8')));
            envelope = asRecord(document?.result) ?? document ?? {};
        }
        catch {
            blockers.push(`Immutable run entry '${entry.entryId}' could not be hash-verified and decoded.`);
            continue;
        }
        const data = asRecord(envelope.data);
        const error = asRecord(envelope.error);
        const entryAtMs = Date.parse(entry.recordedAt);
        const bindsAttempt = recordBindsAttempt(data, options.attemptId);
        const attemptOpeningProof = Number.isFinite(openedAtMs) &&
            Number.isFinite(entryAtMs) &&
            entryAtMs < openedAtMs &&
            ((entry.commandIdentity === 'task attempt-open' && bindsAttempt) ||
                isChronologyValidationError(entry, error) ||
                isUnrecordableValidation(entry, data));
        if (attemptOpeningProof) {
            evidenceEntries.push(projectEntry(entry, 'attempt-before-open'));
        }
        const suppliedFutureTimestamp = futureLifecycleTimestamp({
            entry,
            data,
            attemptId: options.attemptId,
            attemptClosedAt: options.attemptClosedAt ?? null,
        });
        if (suppliedFutureTimestamp !== null) {
            evidenceEntries.push(projectEntry(entry, 'future-lifecycle-timestamp', suppliedFutureTimestamp));
        }
        const baseline = asRecord(data?.baseline);
        const baselineProof = entry.commandIdentity === 'task baseline' &&
            entry.status === 'ok' &&
            baseline?.taskCycleId === options.attemptId &&
            typeof baseline.capturedAt === 'string' &&
            options.baselineCapturedAt !== null &&
            Date.parse(baseline.capturedAt) === baselineAtMs &&
            Number.isFinite(entryAtMs) &&
            Number.isFinite(baselineAtMs) &&
            entryAtMs < baselineAtMs;
        if (baselineProof) {
            evidenceEntries.push(projectEntry(entry, 'baseline-before-capture'));
        }
        for (const validationId of readUnrecordableValidationIds(entry, data)) {
            unrecordableValidationIds.add(validationId);
            if (!evidenceEntries.some((candidate) => candidate.entryId === entry.entryId && candidate.proofKind === 'unrecordable-validation')) {
                evidenceEntries.push(projectEntry(entry, 'unrecordable-validation'));
            }
        }
    }
    const uniqueEntries = [...new Map(evidenceEntries.map((entry) => [`${entry.entryId}:${entry.proofKind}`, entry])).values()].sort((left, right) => left.entryId.localeCompare(right.entryId));
    const chronologyProof = uniqueEntries.some((entry) => entry.proofKind !== 'unrecordable-validation');
    if (!chronologyProof) {
        blockers.push('The selected run contains no hash-verified attempt-opening, baseline-capture, or future lifecycle event proof.');
    }
    return {
        eligible: chronologyProof,
        evidenceEntries: uniqueEntries,
        unrecordableValidationIds: [...unrecordableValidationIds].sort(),
        observedAt: uniqueEntries
            .map((entry) => entry.recordedAt)
            .filter((value) => Number.isFinite(Date.parse(value)))
            .sort((left, right) => Date.parse(left) - Date.parse(right))[0] ?? null,
        blockers,
    };
}
function projectEntry(entry, proofKind, suppliedTimestamp = null) {
    return {
        entryId: entry.entryId,
        commandIdentity: entry.commandIdentity,
        evidencePath: entry.evidencePath,
        evidenceHash: entry.evidenceHash,
        recordedAt: entry.recordedAt,
        proofKind,
        suppliedTimestamp,
    };
}
function futureLifecycleTimestamp(options) {
    if (options.attemptClosedAt === null ||
        options.entry.commandIdentity !== 'task attempt-supersede' ||
        options.entry.status !== 'ok' ||
        options.data?.writeOutcome !== 'committed' ||
        options.data.supersessionState !== 'superseded' ||
        !recordBindsAttempt(options.data, options.attemptId) ||
        typeof options.data.recordedAt !== 'string')
        return null;
    if (options.data.recordedAt !== options.attemptClosedAt)
        return null;
    const suppliedAtMs = Date.parse(options.data.recordedAt);
    const closedAtMs = Date.parse(options.attemptClosedAt);
    const entryAtMs = Date.parse(options.entry.recordedAt);
    return Number.isFinite(suppliedAtMs) &&
        suppliedAtMs === closedAtMs &&
        Number.isFinite(entryAtMs) &&
        closedAtMs > entryAtMs
        ? options.data.recordedAt
        : null;
}
function isChronologyValidationError(entry, error) {
    return entry.commandIdentity === 'task validation-set' &&
        entry.status === 'error' &&
        (entry.errorCode === 'TASK_VALIDATION_BEFORE_ATTEMPT_OPEN' ||
            error?.code === 'TASK_VALIDATION_BEFORE_ATTEMPT_OPEN');
}
function isUnrecordableValidation(entry, data) {
    return readUnrecordableValidationIds(entry, data).length > 0;
}
function readUnrecordableValidationIds(entry, data) {
    if (entry.commandIdentity === 'task validation-record' &&
        entry.status === 'error' &&
        entry.errorCode === 'TASK_VALIDATION_UNRECORDABLE' &&
        isCompletedUnrecordableValidation(data)) {
        return typeof data?.validationId === 'string' ? [data.validationId] : [];
    }
    if (entry.commandIdentity !== 'task validation-set' || !Array.isArray(data?.items))
        return [];
    return data.items.flatMap((item) => {
        const record = asRecord(item);
        return isCompletedUnrecordableValidation(record) && typeof record?.validationId === 'string'
            ? [record.validationId]
            : [];
    });
}
function isCompletedUnrecordableValidation(data) {
    return data?.state === 'unrecordable' &&
        (data.status === 'passed' || data.status === 'failed') &&
        Number.isSafeInteger(data.exitCode) &&
        typeof data.recordedAt === 'string' &&
        Number.isFinite(Date.parse(data.recordedAt)) &&
        Array.isArray(data.commandArgv) &&
        data.commandArgv.length > 0 &&
        data.commandArgv.every((argument) => typeof argument === 'string' && argument.length > 0);
}
function recordBindsAttempt(data, attemptId) {
    if (data === null)
        return false;
    const candidates = [data, asRecord(data.attempt), asRecord(data.baseline), asRecord(data.validation)];
    return candidates.some((candidate) => candidate?.attemptId === attemptId ||
        candidate?.taskCycleId === attemptId ||
        candidate?.priorAttemptId === attemptId);
}
function asRecord(value) {
    return value !== null && typeof value === 'object' && !Array.isArray(value)
        ? value
        : null;
}
