"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readTaskAttempts = readTaskAttempts;
function readTaskAttempts(lines, findings, compatibility, operations) {
    const attempts = [];
    const attemptsIndex = lines.findIndex((line) => /^attempts:/.test(line));
    if (attemptsIndex < 0) {
        compatibility.push(operations.compatibilityEvidence('unmaterialized-legacy-attempt', 'implementation_summary', 'attempts', null, null));
        return attempts;
    }
    const attemptsHeader = lines[attemptsIndex].match(/^attempts:\s*(.*)$/);
    if (attemptsHeader === null) {
        findings.push(operations.unsupportedAttemptsShapeFinding());
        return attempts;
    }
    const collectionValue = attemptsHeader[1].trim();
    if (collectionValue === '[]')
        return attempts;
    if (collectionValue !== '') {
        findings.push(operations.unsupportedAttemptsShapeFinding());
        return attempts;
    }
    readAttemptRows(lines, attemptsIndex, attempts, { findings, compatibility, operations });
    if (attempts.length === 0)
        findings.push(operations.unsupportedAttemptsShapeFinding());
    validateAttemptSet(attempts, findings, operations);
    return attempts;
}
function readAttemptRows(lines, attemptsIndex, attempts, context) {
    let fields = null;
    let unsupportedShape = false;
    const rejectShape = () => {
        if (!unsupportedShape)
            context.findings.push(context.operations.unsupportedAttemptsShapeFinding());
        unsupportedShape = true;
    };
    const commit = () => {
        if (fields === null)
            return;
        attempts.push(projectAttempt(fields, context));
    };
    for (let index = attemptsIndex + 1; index < lines.length; index += 1) {
        const line = lines[index];
        if (line.trim() === '' || /^\s*#/.test(line))
            continue;
        if (/^[^\s]/.test(line))
            break;
        context.operations.validateControlledAttemptKeySyntax(line, context.findings);
        const first = line.match(/^\s{2}-\s+([A-Za-z0-9_]+):\s*(.*)$/);
        if (first !== null) {
            commit();
            fields = { [first[1]]: first[2] };
            continue;
        }
        const field = line.match(/^\s{4}([A-Za-z0-9_]+):\s*(.*)$/);
        if (field !== null && fields !== null) {
            if (Object.hasOwn(fields, field[1])) {
                context.findings.push(context.operations.finding('duplicate-attempt-field', 'implementation_summary', `attempts.${field[1]}`, `Current attempt field '${field[1]}' is duplicated.`));
            }
            fields[field[1]] = field[2];
            continue;
        }
        if (/^\s{2}-/.test(line) ||
            /^\s{2}\S/.test(line) ||
            /^\s{4}\?/.test(line) ||
            (fields === null && /^\s{4}\S/.test(line))) {
            rejectShape();
        }
    }
    commit();
}
function projectAttempt(fields, context) {
    const { operations, findings, compatibility } = context;
    const implementationStatus = operations.canonicalToken(fields.implementation_status, operations.implementationStatuses, 'implementation_status', findings);
    const reviewDisposition = operations.canonicalToken(fields.review_disposition, operations.reviewDispositions, 'review_disposition', findings);
    for (const [field, raw, projected] of [
        ['implementation_status', fields.implementation_status, implementationStatus],
        ['review_disposition', fields.review_disposition, reviewDisposition],
    ]) {
        const sourceValue = operations.nullable(raw);
        if (sourceValue !== null && projected !== null && sourceValue !== projected) {
            compatibility.push(operations.compatibilityEvidence('legacy-attempt-lifecycle-token', 'implementation_summary', `attempts.${field}`, sourceValue, projected));
        }
    }
    for (const field of operations.wrongOwnerFields) {
        if (Object.hasOwn(fields, field)) {
            findings.push(operations.finding('wrong-owner-lifecycle-field', 'implementation_summary', `attempts.${field}`, `Lifecycle field '${field}' is not owned by an implementation attempt.`));
        }
    }
    const nullable = operations.nullable;
    return {
        attemptId: nullable(fields.attempt_id),
        readinessReviewId: nullable(fields.readiness_review_id),
        readinessAuthorizationKind: operations.projectReadinessAuthorizationKind(fields.readiness_authorization_kind),
        readinessWaiverId: nullable(fields.readiness_waiver_id),
        openedAt: nullable(fields.opened_at),
        closedAt: nullable(fields.closed_at),
        implementationStatus,
        reviewDisposition,
        blockerReason: nullable(fields.blocker_reason),
        blockerNext: nullable(fields.blocker_next),
        dispositionBy: nullable(fields.disposition_by),
        dispositionAt: nullable(fields.disposition_at),
        dispositionReason: nullable(fields.disposition_reason),
        blockedBy: nullable(fields.blocked_by),
        blockedAt: nullable(fields.blocked_at),
        reviewWaiverId: nullable(fields.review_waiver_id),
        predecessorAttemptId: nullable(fields.predecessor_attempt_id),
        successorKind: operations.projectSuccessorKind(fields.successor_kind, findings),
        scopeRecoveryId: nullable(fields.scope_recovery_id),
        contractRevisionRecoveryId: nullable(fields.contract_revision_recovery_id),
        predecessorContractRevisionRecoveryId: nullable(fields.predecessor_contract_revision_recovery_id),
        contractRevisionCheckpointId: nullable(fields.contract_revision_checkpoint_id),
        contractRevisionRunId: nullable(fields.contract_revision_run_id),
        contractRevisionOldTaskRevision: nullable(fields.contract_revision_old_task_revision),
        contractRevisionNewTaskRevision: nullable(fields.contract_revision_new_task_revision),
        chronologyRecoveryId: nullable(fields.chronology_recovery_id),
        predecessorChronologyRecoveryId: nullable(fields.predecessor_chronology_recovery_id),
        chronologyRecoveryRunId: nullable(fields.chronology_recovery_run_id),
        chronologyRecoveryTaskRevision: nullable(fields.chronology_recovery_task_revision),
        chronologyObservedOpenedAt: nullable(fields.chronology_observed_opened_at),
        chronologyObservedBaselineAt: nullable(fields.chronology_observed_baseline_at),
        chronologyEvidenceEntryIds: nullable(fields.chronology_evidence_entry_ids),
        chronologyEvidenceHashes: nullable(fields.chronology_evidence_hashes),
        chronologyUnrecordableValidationIds: nullable(fields.chronology_unrecordable_validation_ids),
        chronologyOriginalClosedAt: nullable(fields.chronology_original_closed_at),
        chronologyEffectiveClosedAt: nullable(fields.chronology_effective_closed_at),
        chronologySourceEntryId: nullable(fields.chronology_source_entry_id),
        chronologySourceEvidenceHash: nullable(fields.chronology_source_evidence_hash),
        chronologyRecoveryReason: nullable(fields.chronology_recovery_reason),
        chronologyRecoveredBy: nullable(fields.chronology_recovered_by),
        chronologyRecoveredAt: nullable(fields.chronology_recovered_at),
        repairReason: nullable(fields.repair_reason),
        repairReadinessReviewId: nullable(fields.repair_readiness_review_id),
        repairTaskRevision: nullable(fields.repair_task_revision),
        repairReviewedBy: nullable(fields.repair_reviewed_by),
        repairReviewedAt: nullable(fields.repair_reviewed_at),
        supersededTaskRevision: nullable(fields.superseded_task_revision),
        supersededSummaryRevision: nullable(fields.superseded_summary_revision),
    };
}
function validateAttemptSet(attempts, findings, operations) {
    const ids = new Set();
    for (const attempt of attempts) {
        validateAttemptIdentity(attempt, ids, findings, operations);
        validateAttemptClosure(attempt, findings, operations);
        validateReviewRepairSuccessor(attempt, findings, operations);
        validateContractRevisionEvidence(attempt, findings, operations);
        validateChronologyRecoveryEvidence(attempt, findings, operations);
    }
    if (attempts.filter((attempt) => attempt.closedAt === null).length > 1) {
        findings.push(operations.finding('multiple-open-attempts', 'implementation_summary', 'attempts.closed_at', 'At most one implementation attempt may remain open.'));
    }
}
function validateChronologyRecoveryEvidence(attempt, findings, operations) {
    const predecessorFields = [
        attempt.chronologyRecoveryId,
        attempt.chronologyRecoveryRunId,
        attempt.chronologyRecoveryTaskRevision,
        attempt.chronologyObservedOpenedAt,
        attempt.chronologyObservedBaselineAt,
        attempt.chronologyEvidenceEntryIds,
        attempt.chronologyEvidenceHashes,
        attempt.chronologyUnrecordableValidationIds,
        attempt.chronologyOriginalClosedAt,
        attempt.chronologyEffectiveClosedAt,
        attempt.chronologySourceEntryId,
        attempt.chronologySourceEvidenceHash,
        attempt.chronologyRecoveryReason,
        attempt.chronologyRecoveredBy,
        attempt.chronologyRecoveredAt,
    ];
    const futureTimestampRecovery = attempt.chronologyOriginalClosedAt !== null ||
        attempt.chronologyEffectiveClosedAt !== null ||
        attempt.chronologySourceEntryId !== null ||
        attempt.chronologySourceEvidenceHash !== null ||
        attempt.chronologyRecoveryReason !== null ||
        attempt.chronologyRecoveredBy !== null ||
        attempt.chronologyRecoveredAt !== null;
    const recoveryPredecessor = attempt.reviewDisposition === 'invalid_chronology' ||
        (attempt.reviewDisposition === 'superseded' && futureTimestampRecovery);
    const recoverySuccessor = attempt.successorKind === 'chronology_repair';
    if (!recoveryPredecessor && predecessorFields.some((value) => value !== null)) {
        findings.push(operations.finding('orphaned-chronology-recovery-evidence', 'implementation_summary', 'attempts.chronology_recovery_id', 'Chronology recovery evidence belongs only to an invalid_chronology predecessor or a superseded predecessor with an append-only future-close correction.'));
    }
    if (!recoverySuccessor && attempt.predecessorChronologyRecoveryId !== null) {
        findings.push(operations.finding('orphaned-chronology-repair-successor-evidence', 'implementation_summary', 'attempts.predecessor_chronology_recovery_id', 'Incoming chronology recovery evidence requires successor_kind chronology_repair.'));
    }
    if (recoverySuccessor && (!operations.hasNonEmptyValue(attempt.predecessorAttemptId) ||
        !operations.hasNonEmptyValue(attempt.predecessorChronologyRecoveryId))) {
        findings.push(operations.finding('incomplete-chronology-repair-successor-evidence', 'implementation_summary', 'attempts.successor_kind', 'A chronology_repair successor requires predecessor attempt and chronology recovery identities.'));
    }
    if (!recoveryPredecessor)
        return;
    const evidenceEntryIds = readJsonStringArray(attempt.chronologyEvidenceEntryIds);
    const evidenceHashes = readJsonStringArray(attempt.chronologyEvidenceHashes);
    const unrecordableValidationIds = readJsonStringArray(attempt.chronologyUnrecordableValidationIds);
    if (!operations.hasNonEmptyValue(attempt.chronologyRecoveryId) ||
        !operations.hasNonEmptyValue(attempt.chronologyRecoveryRunId) ||
        !/^[a-f0-9]{64}$/.test(attempt.chronologyRecoveryTaskRevision ?? '') ||
        !operations.isValidTimestamp(attempt.chronologyObservedOpenedAt) ||
        evidenceEntryIds === null ||
        evidenceEntryIds.length === 0 ||
        !evidenceEntryIds.every((entry) => /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(entry)) ||
        evidenceHashes === null ||
        evidenceHashes.length !== evidenceEntryIds.length ||
        !evidenceHashes.every((entry) => /^sha256:[a-f0-9]{64}$/.test(entry)) ||
        unrecordableValidationIds === null ||
        !unrecordableValidationIds.every((entry) => /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(entry))) {
        findings.push(operations.finding('incomplete-chronology-recovery-evidence', 'implementation_summary', 'attempts.chronology_recovery_id', 'An invalid_chronology predecessor requires recovery/run identities, the observed opening time, and immutable evidence entry/hash identities.'));
    }
    if (futureTimestampRecovery && (attempt.reviewDisposition !== 'superseded' ||
        attempt.closedAt === null ||
        attempt.chronologyOriginalClosedAt !== attempt.closedAt ||
        !operations.isValidTimestamp(attempt.chronologyEffectiveClosedAt) ||
        operations.timestampPrecedes(attempt.chronologyEffectiveClosedAt, attempt.openedAt) ||
        !operations.timestampPrecedes(attempt.chronologyEffectiveClosedAt, attempt.chronologyOriginalClosedAt) ||
        !operations.hasNonEmptyValue(attempt.chronologySourceEntryId) ||
        !/^sha256:[a-f0-9]{64}$/.test(attempt.chronologySourceEvidenceHash ?? '') ||
        !operations.hasNonEmptyValue(attempt.chronologyRecoveryReason) ||
        !operations.hasNonEmptyValue(attempt.chronologyRecoveredBy) ||
        !operations.isValidTimestamp(attempt.chronologyRecoveredAt))) {
        findings.push(operations.finding('incomplete-future-timestamp-recovery-evidence', 'implementation_summary', 'attempts.chronology_effective_closed_at', 'A future-timestamp recovery requires the unchanged original close, a bounded effective close, source entry/hash evidence, and recovery audit fields.'));
    }
}
function readJsonStringArray(value) {
    if (value === null)
        return null;
    try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed) && parsed.every((entry) => typeof entry === 'string' && entry.length > 0)
            ? parsed
            : null;
    }
    catch {
        return null;
    }
}
function validateContractRevisionEvidence(attempt, findings, operations) {
    const outgoingFields = [
        attempt.contractRevisionRecoveryId,
        attempt.contractRevisionCheckpointId,
        attempt.contractRevisionRunId,
        attempt.contractRevisionOldTaskRevision,
        attempt.contractRevisionNewTaskRevision,
    ];
    const recoveryPredecessor = attempt.reviewDisposition === 'contract_gap' && attempt.scopeRecoveryId === null;
    const recoverySuccessor = attempt.successorKind === 'contract_revision';
    const legacyIncomingOnly = recoverySuccessor &&
        attempt.predecessorContractRevisionRecoveryId === null &&
        attempt.contractRevisionRecoveryId !== null &&
        outgoingFields.slice(1).every((value) => value === null);
    const incomingRecoveryId = attempt.predecessorContractRevisionRecoveryId ?? (legacyIncomingOnly ? attempt.contractRevisionRecoveryId : null);
    if (!recoveryPredecessor && !recoverySuccessor) {
        if (outgoingFields.some((value) => value !== null) || incomingRecoveryId !== null) {
            findings.push(operations.finding('orphaned-contract-revision-recovery-evidence', 'implementation_summary', 'attempts.contract_revision_recovery_id', 'Contract-revision recovery evidence requires a contract_gap predecessor or contract_revision successor.'));
        }
        return;
    }
    if (recoverySuccessor) {
        if (!operations.hasNonEmptyValue(attempt.predecessorAttemptId) ||
            !operations.hasNonEmptyValue(incomingRecoveryId)) {
            findings.push(operations.finding('incomplete-contract-revision-successor-evidence', 'implementation_summary', 'attempts.successor_kind', 'A contract_revision successor requires predecessor identity and the matching recovery identity.'));
        }
        if (!recoveryPredecessor && !legacyIncomingOnly && outgoingFields.some((value) => value !== null)) {
            findings.push(operations.finding('orphaned-contract-revision-checkpoint-evidence', 'implementation_summary', 'attempts.contract_revision_checkpoint_id', 'Checkpoint and revision audit fields belong only to the preserved contract_gap predecessor.'));
        }
        if (!recoveryPredecessor)
            return;
    }
    if (!operations.hasNonEmptyValue(attempt.contractRevisionRecoveryId) ||
        !operations.hasNonEmptyValue(attempt.contractRevisionCheckpointId) ||
        !operations.hasNonEmptyValue(attempt.contractRevisionRunId) ||
        !/^[a-f0-9]{64}$/.test(attempt.contractRevisionOldTaskRevision ?? '') ||
        !/^[a-f0-9]{64}$/.test(attempt.contractRevisionNewTaskRevision ?? '') ||
        attempt.contractRevisionOldTaskRevision === attempt.contractRevisionNewTaskRevision) {
        findings.push(operations.finding('incomplete-contract-revision-recovery-evidence', 'implementation_summary', 'attempts.contract_revision_recovery_id', 'A recovered contract_gap predecessor requires recovery, checkpoint, run, and distinct exact old/new task revisions.'));
    }
}
function validateAttemptIdentity(attempt, ids, findings, operations) {
    if (!operations.hasNonEmptyValue(attempt.attemptId)) {
        findings.push(operations.finding('missing-attempt-identity', 'implementation_summary', 'attempts.attempt_id', 'Every materialized attempt requires an attempt_id.'));
    }
    else if (ids.has(attempt.attemptId)) {
        findings.push(operations.finding('duplicate-attempt-identity', 'implementation_summary', 'attempts.attempt_id', `Attempt identity '${attempt.attemptId}' is duplicated.`));
    }
    else {
        ids.add(attempt.attemptId);
    }
    if (attempt.openedAt === null) {
        findings.push(operations.finding('missing-attempt-opened-at', 'implementation_summary', 'attempts.opened_at', 'Every materialized attempt requires an opened_at time.'));
    }
    else if (!operations.isValidTimestamp(attempt.openedAt)) {
        findings.push(operations.finding('invalid-attempt-opened-at', 'implementation_summary', 'attempts.opened_at', 'Every materialized attempt opened_at value must be a valid timestamp.'));
    }
    if (!operations.hasNonEmptyValue(attempt.readinessReviewId)) {
        findings.push(operations.finding('missing-attempt-readiness-reference', 'implementation_summary', 'attempts.readiness_review_id', 'Every materialized attempt requires its readiness review identity.'));
    }
    if (attempt.closedAt !== null && !operations.isValidTimestamp(attempt.closedAt)) {
        findings.push(operations.finding('invalid-attempt-closed-at', 'implementation_summary', 'attempts.closed_at', 'A materialized attempt closed_at value must be null or a valid timestamp.'));
    }
}
function validateAttemptClosure(attempt, findings, operations) {
    const terminalReview = [
        'changes_requested',
        'accepted',
        'accepted_with_follow_up',
        'cancelled',
        'superseded',
        'contract_gap',
        'invalid_chronology',
        'waived',
    ].includes(attempt.reviewDisposition ?? '');
    const openReview = attempt.reviewDisposition === 'pending' || attempt.reviewDisposition === 'blocked';
    for (const [field, timestamp] of [
        ['attempts.blocked_at', attempt.blockedAt],
        ['attempts.disposition_at', attempt.dispositionAt],
        ['attempts.closed_at', attempt.closedAt],
    ]) {
        if (attempt.reviewDisposition !== 'invalid_chronology' &&
            operations.timestampPrecedes(timestamp, attempt.openedAt)) {
            findings.push(operations.finding('attempt-event-before-open', 'implementation_summary', field, `${field} cannot precede the attempt opened_at time.`));
        }
    }
    if (terminalReview && attempt.closedAt === null) {
        findings.push(operations.finding('attempt-closure-required', 'implementation_summary', 'attempts.closed_at', `Review disposition '${attempt.reviewDisposition}' requires a closed attempt.`));
    }
    if (openReview && attempt.closedAt !== null) {
        findings.push(operations.finding('attempt-must-remain-open', 'implementation_summary', 'attempts.closed_at', `Review disposition '${attempt.reviewDisposition}' requires an open attempt.`));
    }
    const reviewedImplementation = [
        'changes_requested',
        'accepted',
        'accepted_with_follow_up',
        'blocked',
        'waived',
    ].includes(attempt.reviewDisposition ?? '');
    if (reviewedImplementation && attempt.implementationStatus !== 'completed') {
        findings.push(operations.finding('review-before-implementation-complete', 'implementation_summary', 'attempts.review_disposition', `Review disposition '${attempt.reviewDisposition}' requires completed implementation evidence.`));
    }
    if (terminalReview &&
        (!operations.hasNonEmptyValue(attempt.dispositionBy) || !operations.isValidTimestamp(attempt.dispositionAt))) {
        findings.push(operations.finding('incomplete-disposition-audit', 'implementation_summary', 'attempts.disposition_by', `Review disposition '${attempt.reviewDisposition}' requires a disposition actor and valid time.`));
    }
    if (terminalReview &&
        attempt.closedAt !== null &&
        attempt.dispositionAt !== null &&
        attempt.closedAt !== attempt.dispositionAt) {
        findings.push(operations.finding('disposition-closure-time-mismatch', 'implementation_summary', 'attempts.closed_at', 'A terminal disposition must close the attempt at the same time it is recorded.'));
    }
    const reasonRequired = [
        'changes_requested',
        'accepted_with_follow_up',
        'cancelled',
        'superseded',
        'contract_gap',
        'invalid_chronology',
        'waived',
    ].includes(attempt.reviewDisposition ?? '');
    if (reasonRequired && !operations.hasNonEmptyValue(attempt.dispositionReason)) {
        findings.push(operations.finding('missing-disposition-reason', 'implementation_summary', 'attempts.disposition_reason', `Review disposition '${attempt.reviewDisposition}' requires a reason.`));
    }
    if (attempt.reviewDisposition === 'waived' && !operations.hasNonEmptyValue(attempt.reviewWaiverId)) {
        findings.push(operations.finding('missing-review-waiver-reference', 'implementation_summary', 'attempts.review_waiver_id', 'A waived review disposition requires its explicit review waiver identity.'));
    }
    if (attempt.reviewDisposition === 'superseded' &&
        (!/^[a-f0-9]{64}$/.test(attempt.supersededTaskRevision ?? '') ||
            !/^[a-f0-9]{64}$/.test(attempt.supersededSummaryRevision ?? ''))) {
        findings.push(operations.finding('incomplete-attempt-supersession-revisions', 'implementation_summary', 'attempts.superseded_task_revision', 'A superseded attempt requires the exact pre-transition task and implementation-summary revisions.'));
    }
}
function validateReviewRepairSuccessor(attempt, findings, operations) {
    const repairFields = [
        attempt.repairReason,
        attempt.repairReadinessReviewId,
        attempt.repairTaskRevision,
        attempt.repairReviewedBy,
        attempt.repairReviewedAt,
    ];
    if (attempt.successorKind !== 'review_repair') {
        if (repairFields.some((value) => value !== null)) {
            findings.push(operations.finding('orphaned-review-repair-evidence', 'implementation_summary', 'attempts.successor_kind', "Review-repair evidence requires successor_kind 'review_repair'."));
        }
        return;
    }
    if (!operations.hasNonEmptyValue(attempt.predecessorAttemptId) ||
        !operations.hasNonEmptyValue(attempt.repairReason) ||
        !operations.hasNonEmptyValue(attempt.repairReadinessReviewId) ||
        !/^[a-f0-9]{64}$/.test(attempt.repairTaskRevision ?? '') ||
        !operations.hasNonEmptyValue(attempt.repairReviewedBy) ||
        !operations.isValidTimestamp(attempt.repairReviewedAt)) {
        findings.push(operations.finding('incomplete-review-repair-evidence', 'implementation_summary', 'attempts.successor_kind', 'A review_repair successor requires predecessor identity, reason, readiness-review identity, exact task revision, reviewer, and review time.'));
    }
    if (operations.timestampPrecedes(attempt.openedAt, attempt.repairReviewedAt)) {
        findings.push(operations.finding('attempt-before-review-repair-authorization', 'implementation_summary', 'attempts.opened_at', 'A review-repair successor cannot open before its repair readiness review.'));
    }
}
