"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readAttemptSupplementalRuntimeBaseline = readAttemptSupplementalRuntimeBaseline;
exports.readAttemptFieldRaw = readAttemptFieldRaw;
exports.readAttemptField = readAttemptField;
exports.decodeAttemptStringScalar = decodeAttemptStringScalar;
exports.isUnknownRecord = isUnknownRecord;
exports.insertAttemptRuntimeBaseline = insertAttemptRuntimeBaseline;
exports.locateAttemptRange = locateAttemptRange;
exports.sameBaselineIdentity = sameBaselineIdentity;
const errors_1 = require("../../errors");
const paths_1 = require("../../paths");
const declared_target_paths_1 = require("../../validation/declared-target-paths");
const planning_document_utils_1 = require("../planning-document-utils");
const task_lifecycle_1 = require("../task-lifecycle");
const contracts_1 = require("./contracts");
const repository_support_1 = require("./repository-support");
function readAttemptSupplementalRuntimeBaseline(options) {
    const lines = (0, planning_document_utils_1.parseMarkdownFrontMatter)(options.summaryMarkdownText).frontMatterLines;
    const range = locateAttemptRange(lines, options.attemptId);
    if (range === null)
        return null;
    const rawBaseline = readAttemptFieldRaw(lines, range, "supplemental_runtime_attribution_baseline");
    const rawMode = readAttemptField(lines, range, "scope_recovery_mode");
    if (rawBaseline === undefined && rawMode !== "amendment")
        return null;
    const issues = [];
    if (rawMode !== "amendment") {
        issues.push("supplemental runtime-attribution evidence is eligible only for amendment-mode scope recovery.");
    }
    if (rawBaseline === undefined) {
        issues.push("amendment-mode scope recovery requires supplemental_runtime_attribution_baseline evidence.");
    }
    let decoded = null;
    if (rawBaseline !== undefined) {
        try {
            decoded = JSON.parse(decodeAttemptStringScalar(rawBaseline));
        }
        catch {
            issues.push("supplemental_runtime_attribution_baseline must encode valid JSON evidence.");
        }
    }
    const record = isUnknownRecord(decoded) ? decoded : null;
    if (rawBaseline !== undefined && record === null && issues.length === 0) {
        issues.push("supplemental_runtime_attribution_baseline must encode a JSON object.");
    }
    const scopeRecoveryId = record?.scopeRecoveryId;
    const taskCycleId = record?.taskCycleId;
    const contractImpactFingerprint = record?.contractImpactFingerprint;
    const capturedBy = record?.capturedBy;
    const capturedAt = record?.capturedAt;
    const rawTargets = record?.targets;
    if (record !== null && record.schemaVersion !== 1)
        issues.push("supplemental baseline schemaVersion must be 1.");
    if (typeof scopeRecoveryId !== "string" || !contracts_1.IDENTITY_PATTERN.test(scopeRecoveryId)) {
        issues.push("supplemental baseline scopeRecoveryId is invalid.");
    }
    if (taskCycleId !== options.attemptId) {
        issues.push(`supplemental baseline taskCycleId must match current attempt '${options.attemptId}'.`);
    }
    if (typeof contractImpactFingerprint !== "string" || !contracts_1.CONTENT_HASH_PATTERN.test(contractImpactFingerprint)) {
        issues.push("supplemental baseline contractImpactFingerprint is invalid.");
    }
    if (typeof capturedBy !== "string" || capturedBy.trim() === "") {
        issues.push("supplemental baseline capturedBy is required.");
    }
    if (typeof capturedAt !== "string" || !(0, task_lifecycle_1.isTaskLifecycleTimestamp)(capturedAt)) {
        issues.push("supplemental baseline capturedAt is invalid.");
    }
    if (!Array.isArray(rawTargets))
        issues.push("supplemental baseline targets must be an array.");
    const attemptScopeRecoveryId = readAttemptField(lines, range, "scope_recovery_id");
    const attemptFingerprint = readAttemptField(lines, range, "scope_recovery_fingerprint");
    const attemptImpactFingerprint = readAttemptField(lines, range, "scope_recovery_impact_fingerprint");
    if (scopeRecoveryId !== attemptScopeRecoveryId) {
        issues.push("supplemental baseline scopeRecoveryId must match the current attempt scope_recovery_id.");
    }
    if (contractImpactFingerprint !== attemptFingerprint
        || contractImpactFingerprint !== attemptImpactFingerprint) {
        issues.push("supplemental baseline contract-impact fingerprint must match the current attempt recovery fingerprints.");
    }
    let addedPaths = [];
    const rawAddedPaths = readAttemptFieldRaw(lines, range, "scope_recovery_added_paths");
    try {
        const parsed = rawAddedPaths === undefined ? null : JSON.parse(decodeAttemptStringScalar(rawAddedPaths));
        if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) {
            throw new TypeError("added paths must be strings");
        }
        addedPaths = parsed;
    }
    catch {
        issues.push("scope_recovery_added_paths must encode an array of repository-relative paths.");
    }
    const declaredPaths = new Set((0, declared_target_paths_1.readTaskDeclaredTargetPathEvidence)(options.taskDefinitionPath, options.taskMarkdownText).explicitTargetPaths);
    const targets = [];
    const seen = new Set();
    if (Array.isArray(rawTargets)) {
        for (const candidate of rawTargets) {
            if (!isUnknownRecord(candidate)) {
                issues.push("supplemental baseline target evidence must be an object.");
                continue;
            }
            const targetPath = candidate.path;
            const exists = candidate.exists;
            const kind = candidate.kind;
            const contentSha256 = candidate.contentSha256;
            const dirty = candidate.dirty;
            if (typeof targetPath !== "string") {
                issues.push("supplemental baseline target path is invalid.");
                continue;
            }
            try {
                if ((0, paths_1.parseRepositoryRelativePath)(targetPath).normalizedPath !== targetPath) {
                    issues.push(`Supplemental target '${targetPath}' must use its canonical repository-relative path.`);
                }
            }
            catch {
                issues.push(`Supplemental target '${targetPath}' is not repository-relative.`);
            }
            if (seen.has(targetPath))
                issues.push(`Supplemental target '${targetPath}' is duplicated.`);
            seen.add(targetPath);
            if (!declaredPaths.has(targetPath)) {
                issues.push(`Supplemental target '${targetPath}' is not declared by the current task revision.`);
            }
            if (options.primaryTargets.some((target) => (0, paths_1.pathsOverlap)(target.path, targetPath))) {
                issues.push(`Supplemental target '${targetPath}' overlaps the immutable primary baseline.`);
            }
            if (typeof exists !== "boolean" || !["absent", "file", "directory"].includes(String(kind))) {
                issues.push(`Supplemental target '${targetPath}' has invalid existence or kind evidence.`);
            }
            else if (exists !== (kind !== "absent")) {
                issues.push(`Supplemental target '${targetPath}' has contradictory existence/kind evidence.`);
            }
            if (exists === true && (typeof contentSha256 !== "string" || !contracts_1.CONTENT_HASH_PATTERN.test(contentSha256))) {
                issues.push(`Supplemental target '${targetPath}' requires a sha256 content hash.`);
            }
            if (exists === false && contentSha256 !== null) {
                issues.push(`Absent supplemental target '${targetPath}' must not carry a content hash.`);
            }
            if (dirty !== false) {
                issues.push(`Supplemental target '${targetPath}' must have been clean when amendment evidence was captured.`);
            }
            if (typeof exists === "boolean"
                && ["absent", "file", "directory"].includes(String(kind))
                && (contentSha256 === null || typeof contentSha256 === "string")
                && dirty === false) {
                targets.push({
                    path: targetPath,
                    exists,
                    kind: kind,
                    contentSha256,
                    dirty: false
                });
            }
        }
    }
    if (!(0, repository_support_1.sameValues)(targets.map((target) => target.path), addedPaths)) {
        issues.push("supplemental baseline targets must exactly match scope_recovery_added_paths.");
    }
    if (issues.length > 0) {
        throw (0, errors_1.createUsageError)(`Supplemental runtime-attribution baseline is invalid for attempt '${options.attemptId}'.`, issues);
    }
    return {
        schemaVersion: 1,
        scopeRecoveryId: scopeRecoveryId,
        taskCycleId: taskCycleId,
        contractImpactFingerprint: contractImpactFingerprint,
        capturedBy: capturedBy,
        capturedAt: capturedAt,
        targets
    };
}
function readAttemptFieldRaw(lines, range, field) {
    const pattern = new RegExp(`^    ${field}:\\s*(.*)$`);
    for (let index = range.start + 1; index < range.end; index += 1) {
        const match = lines[index]?.match(pattern);
        if (match !== null && match !== undefined)
            return match[1];
    }
    return undefined;
}
function readAttemptField(lines, range, field) {
    const raw = readAttemptFieldRaw(lines, range, field);
    if (raw === undefined)
        return undefined;
    return raw.startsWith("\"") ? decodeAttemptStringScalar(raw) : (0, planning_document_utils_1.parseScalar)(raw);
}
function decodeAttemptStringScalar(raw) {
    const decoded = JSON.parse(raw);
    if (typeof decoded !== "string")
        throw new TypeError("attempt field must be a string scalar");
    return decoded;
}
function isUnknownRecord(value) {
    return value !== null && typeof value === "object" && !Array.isArray(value);
}
function insertAttemptRuntimeBaseline(summaryMarkdownText, attemptId, baseline) {
    const newline = summaryMarkdownText.includes("\r\n") ? "\r\n" : "\n";
    const hadFinalNewline = summaryMarkdownText.endsWith("\n");
    const lines = summaryMarkdownText.split(/\r?\n/);
    const parsed = (0, planning_document_utils_1.parseMarkdownFrontMatter)(summaryMarkdownText);
    const range = locateAttemptRange(parsed.frontMatterLines, attemptId);
    if (range === null)
        throw (0, errors_1.createUsageError)(`Current attempt '${attemptId}' is not materialized.`);
    if (parsed.frontMatterLines.slice(range.start, range.end).some((line) => /^ {4}runtime_attribution_baseline:/.test(line))) {
        throw (0, errors_1.createUsageError)(`Current attempt '${attemptId}' already owns runtime-attribution baseline evidence.`);
    }
    const rendered = `    runtime_attribution_baseline: ${(0, repository_support_1.quote)(JSON.stringify(baseline))}`;
    const fullStart = range.start + 1;
    const fullEnd = range.end + 1;
    let insertion = fullStart + 1;
    for (let index = fullStart + 1; index < fullEnd; index += 1) {
        if (/^ {4}readiness_review_id:/.test(lines[index])) {
            insertion = index + 1;
            break;
        }
    }
    lines.splice(insertion, 0, rendered);
    const joined = lines.join(newline);
    return hadFinalNewline && !joined.endsWith(newline) ? `${joined}${newline}` : joined;
}
function locateAttemptRange(frontMatterLines, attemptId) {
    const attemptsIndex = frontMatterLines.findIndex((line) => /^attempts:\s*$/.test(line));
    if (attemptsIndex < 0)
        return null;
    for (let index = attemptsIndex + 1; index < frontMatterLines.length; index += 1) {
        const first = frontMatterLines[index].match(/^ {2}- attempt_id:\s*(.*)$/);
        if (first === null)
            continue;
        let end = index + 1;
        while (end < frontMatterLines.length && !/^ {2}-\s+/.test(frontMatterLines[end]) && /^\s/.test(frontMatterLines[end])) {
            end += 1;
        }
        if ((0, planning_document_utils_1.parseScalar)(first[1]) === attemptId)
            return { start: index, end };
        index = end - 1;
    }
    return null;
}
function sameBaselineIdentity(left, right) {
    return left.baselineId === right.baselineId
        && left.taskCycleId === right.taskCycleId
        && left.checkpointId === right.checkpointId;
}
