"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspectRuntimeAcceptance = inspectRuntimeAcceptance;
exports.assertRuntimeAcceptanceInspection = assertRuntimeAcceptanceInspection;
exports.validateMilestoneRuntimeBreakout = validateMilestoneRuntimeBreakout;
exports.validateInheritedRuntimeAcceptance = validateInheritedRuntimeAcceptance;
exports.validateTaskRuntimeAcceptance = validateTaskRuntimeAcceptance;
exports.taskExecutionModeForRuntimeAcceptance = taskExecutionModeForRuntimeAcceptance;
exports.childRuntimeAcceptanceMetadata = childRuntimeAcceptanceMetadata;
exports.formatRuntimeAcceptanceFrontMatter = formatRuntimeAcceptanceFrontMatter;
exports.injectRuntimeAcceptanceFrontMatter = injectRuntimeAcceptanceFrontMatter;
exports.applyTaskRuntimeAcceptanceFieldUpdate = applyTaskRuntimeAcceptanceFieldUpdate;
exports.hasExecutableRuntimeEvidence = hasExecutableRuntimeEvidence;
const errors_1 = require("../errors");
const front_matter_utils_1 = require("./front-matter-utils");
const STABLE_CRITERION_ID_PATTERN = /^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)+$/;
const EXCEPTION_LIST_FIELDS = new Map([
    ["runtime_acceptance_dependencies", "runtimeAcceptanceDependencies"],
    ["distinct_deliverables", "distinctDeliverables"],
    ["independent_validation_boundaries", "independentValidationBoundaries"],
    ["independent_rollback_boundaries", "independentRollbackBoundaries"]
]);
function inspectRuntimeAcceptance(options) {
    const issues = [];
    const executableEvidence = hasExecutableRuntimeEvidence(options.markdownText);
    const block = readRuntimeAcceptanceBlock(options.frontMatterLines);
    if (block === null) {
        if (executableEvidence && options.requireExplicitForExecutableEvidence === true) {
            issues.push("Executable acceptance evidence requires explicit 'runtime_acceptance.required' metadata.");
        }
        return {
            artifactPath: (0, front_matter_utils_1.toRepositoryLikePath)(options.artifactPath),
            artifactKind: options.artifactKind,
            metadata: null,
            executableEvidence,
            issues
        };
    }
    const requiredRaw = readBlockScalar(block.lines, "required", 2);
    let required = null;
    if (requiredRaw === undefined) {
        issues.push("runtime_acceptance.required must be explicitly set to true or false.");
    }
    else if (requiredRaw === "true") {
        required = true;
    }
    else if (requiredRaw === "false") {
        required = false;
    }
    else {
        issues.push("runtime_acceptance.required must use the boolean token true or false.");
    }
    const criterionIds = readBlockList(block.lines, "criterion_ids", 2);
    const duplicateCriterionIds = criterionIds.filter((value, index) => criterionIds.indexOf(value) !== index);
    if (duplicateCriterionIds.length > 0) {
        issues.push(`runtime_acceptance.criterion_ids must be unique; duplicates: ${[...new Set(duplicateCriterionIds)].join(", ")}.`);
    }
    const invalidCriterionIds = criterionIds.filter((value) => !STABLE_CRITERION_ID_PATTERN.test(value));
    if (invalidCriterionIds.length > 0) {
        issues.push(`runtime_acceptance.criterion_ids contains unstable IDs: ${invalidCriterionIds.join(", ")}.`);
    }
    const maxContractStreakRaw = readBlockScalar(block.lines, "max_contract_streak", 2);
    let maxContractStreak = 1;
    if (maxContractStreakRaw !== undefined) {
        if (!/^\d+$/.test(maxContractStreakRaw)) {
            issues.push("runtime_acceptance.max_contract_streak must be a non-negative integer.");
        }
        else {
            maxContractStreak = Number(maxContractStreakRaw);
        }
    }
    const breakoutTask = readBlockScalar(block.lines, "breakout_task", 2)?.trim() || null;
    const exception = readRuntimeAcceptanceException(block.lines, issues);
    if (required === true && criterionIds.length === 0) {
        issues.push("Runtime-sensitive acceptance requires at least one runtime_acceptance.criterion_ids entry.");
    }
    if (required === false) {
        if (criterionIds.length > 0) {
            issues.push("A non-runtime parent must not declare runtime_acceptance.criterion_ids.");
        }
        if (executableEvidence) {
            issues.push("runtime_acceptance.required is false but Runtime Delta or executable acceptance evidence declares runtime behavior.");
        }
        if (maxContractStreakRaw !== undefined || breakoutTask !== null || exception !== null) {
            issues.push("A non-runtime parent must not declare runtime breakout policy fields.");
        }
    }
    return {
        artifactPath: (0, front_matter_utils_1.toRepositoryLikePath)(options.artifactPath),
        artifactKind: options.artifactKind,
        metadata: required === null
            ? null
            : {
                required,
                criterionIds,
                maxContractStreak,
                breakoutTask,
                exception
            },
        executableEvidence,
        issues
    };
}
function assertRuntimeAcceptanceInspection(inspection) {
    if (inspection.issues.length > 0) {
        throw (0, errors_1.createUsageError)(`Runtime acceptance contract rejected '${inspection.artifactPath}'.`, [...inspection.issues]);
    }
    return inspection.metadata;
}
function validateMilestoneRuntimeBreakout(metadata, declaredTaskSlugs) {
    if (metadata === null || !metadata.required) {
        return [];
    }
    const issues = [];
    if (metadata.breakoutTask === null) {
        issues.push("A runtime-sensitive milestone must declare runtime_acceptance.breakout_task.");
        return issues;
    }
    const breakoutIndex = declaredTaskSlugs.indexOf(metadata.breakoutTask);
    if (breakoutIndex === -1) {
        issues.push(`runtime_acceptance.breakout_task '${metadata.breakoutTask}' is not a declared task.`);
        return issues;
    }
    const extendsDefaultStreak = metadata.maxContractStreak > 1 || breakoutIndex > 1;
    if (breakoutIndex > metadata.maxContractStreak) {
        issues.push(`Runtime breakout task '${metadata.breakoutTask}' appears after ${breakoutIndex} contract positions, beyond max_contract_streak ${metadata.maxContractStreak}.`);
    }
    if (extendsDefaultStreak) {
        issues.push(...validateRuntimeAcceptanceException(metadata.exception, metadata.breakoutTask, declaredTaskSlugs));
    }
    else if (metadata.exception !== null) {
        issues.push(...validateRuntimeAcceptanceException(metadata.exception, metadata.breakoutTask, declaredTaskSlugs));
    }
    return issues;
}
function validateInheritedRuntimeAcceptance(parent, child) {
    if (child === null) {
        return ["Child runtime_acceptance metadata is missing."];
    }
    if (child.required !== parent.required) {
        return [
            `Child runtime_acceptance.required '${child.required}' contradicts authoritative parent value '${parent.required}'.`
        ];
    }
    if (!sameOrderedValues(child.criterionIds, parent.criterionIds)) {
        return [
            `Child runtime_acceptance.criterion_ids must exactly preserve authoritative parent IDs: ${parent.criterionIds.join(", ") || "(none)"}.`
        ];
    }
    return [];
}
function validateTaskRuntimeAcceptance(options) {
    const issues = [...validateInheritedRuntimeAcceptance(options.parent, options.child)];
    const executableEvidence = hasExecutableRuntimeEvidence(options.taskMarkdown);
    if (!options.parent.required && (options.executionMode === "runtime" || options.executionMode === "hybrid")) {
        issues.push(`Task execution_mode '${options.executionMode}' contradicts authoritative non-runtime parent acceptance.`);
    }
    if (!options.parent.required && executableEvidence) {
        issues.push("Task Runtime Delta or executable acceptance contradicts authoritative non-runtime parent acceptance.");
    }
    return issues;
}
function taskExecutionModeForRuntimeAcceptance(metadata, taskSlug, declaredTaskSlugs) {
    if (metadata === null) {
        return "runtime";
    }
    if (!metadata.required) {
        return "contract";
    }
    const taskIndex = declaredTaskSlugs.indexOf(taskSlug);
    const breakoutIndex = metadata.breakoutTask === null
        ? -1
        : declaredTaskSlugs.indexOf(metadata.breakoutTask);
    return taskIndex !== -1 && breakoutIndex !== -1 && taskIndex < breakoutIndex
        ? "contract"
        : "runtime";
}
function childRuntimeAcceptanceMetadata(parent, options = {}) {
    return {
        required: parent.required,
        criterionIds: [...parent.criterionIds],
        maxContractStreak: parent.required ? parent.maxContractStreak : 1,
        breakoutTask: parent.required ? options.breakoutTask ?? null : null,
        exception: null
    };
}
function formatRuntimeAcceptanceFrontMatter(metadata, options = {}) {
    const lines = ["runtime_acceptance:", `  required: ${metadata.required}`];
    if (metadata.required) {
        lines.push("  criterion_ids:");
        lines.push(...metadata.criterionIds.map((criterionId) => `    - ${criterionId}`));
        if (options.includeBreakoutPolicy === true) {
            lines.push(`  max_contract_streak: ${metadata.maxContractStreak}`);
            if (metadata.breakoutTask !== null) {
                lines.push(`  breakout_task: ${metadata.breakoutTask}`);
            }
        }
    }
    return lines;
}
function injectRuntimeAcceptanceFrontMatter(markdownText, metadata, options = {}) {
    if (metadata === null) {
        return markdownText;
    }
    const lines = markdownText.split(/\r?\n/);
    const insertionIndex = lines.findIndex((line) => line === "upstream:");
    if (insertionIndex === -1) {
        throw new TypeError("Generated artifact is missing the front matter 'upstream' insertion boundary.");
    }
    lines.splice(insertionIndex, 0, ...formatRuntimeAcceptanceFrontMatter(metadata, options));
    return lines.join("\n");
}
function applyTaskRuntimeAcceptanceFieldUpdate(markdownText, rawValue) {
    let parsed;
    try {
        parsed = JSON.parse(rawValue);
    }
    catch {
        throw new TypeError("Front matter field 'runtime_acceptance' must be a JSON object with required and criterion_ids fields.");
    }
    if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
        throw new TypeError("Front matter field 'runtime_acceptance' must be an object.");
    }
    const record = parsed;
    const unsupportedFields = Object.keys(record).filter((key) => key !== "required" && key !== "criterion_ids");
    if (unsupportedFields.length > 0) {
        throw new TypeError(`Task runtime_acceptance update contains unsupported fields: ${unsupportedFields.join(", ")}.`);
    }
    if (typeof record.required !== "boolean") {
        throw new TypeError("Task runtime_acceptance.required must be the boolean true or false.");
    }
    if (!Object.hasOwn(record, "criterion_ids")) {
        throw new TypeError("Task runtime_acceptance.criterion_ids must be supplied as the complete array.");
    }
    const criterionIds = record.criterion_ids;
    if (!Array.isArray(criterionIds) || !criterionIds.every((value) => typeof value === "string")) {
        throw new TypeError("Task runtime_acceptance.criterion_ids must be an array of strings.");
    }
    if (!record.required && criterionIds.length > 0) {
        throw new TypeError("Task runtime_acceptance.criterion_ids must be empty when runtime_acceptance.required is false.");
    }
    const nextText = upsertTaskRuntimeAcceptanceFrontMatter(markdownText, {
        required: record.required,
        criterionIds,
        maxContractStreak: 1,
        breakoutTask: null,
        exception: null
    });
    const nextLines = nextText.split(/\r?\n/u);
    const closingIndex = nextLines.findIndex((line, index) => index > 0 && line === "---");
    const inspection = inspectRuntimeAcceptance({
        artifactPath: "selected task definition",
        artifactKind: "task",
        frontMatterLines: closingIndex === -1 ? [] : nextLines.slice(1, closingIndex),
        markdownText: nextText,
        requireExplicitForExecutableEvidence: true
    });
    if (inspection.issues.length > 0) {
        throw new TypeError(inspection.issues.join(" "));
    }
    return nextText;
}
function upsertTaskRuntimeAcceptanceFrontMatter(markdownText, metadata) {
    const newline = markdownText.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdownText.split(/\r?\n/u);
    const closingIndex = lines.findIndex((line, index) => index > 0 && line === "---");
    if (closingIndex === -1) {
        throw new TypeError("Task definition is missing a complete YAML front matter block.");
    }
    const replacement = formatRuntimeAcceptanceFrontMatter(metadata);
    const existingIndex = lines.findIndex((line, index) => index > 0 && index < closingIndex && line === "runtime_acceptance:");
    if (existingIndex !== -1) {
        let existingEnd = closingIndex;
        for (let index = existingIndex + 1; index < closingIndex; index += 1) {
            if (lines[index] !== "" && !/^\s/u.test(lines[index])) {
                existingEnd = index;
                break;
            }
        }
        lines.splice(existingIndex, existingEnd - existingIndex, ...replacement);
        return lines.join(newline);
    }
    const upstreamIndex = lines.findIndex((line, index) => index > 0 && index < closingIndex && line === "upstream:");
    lines.splice(upstreamIndex === -1 ? closingIndex : upstreamIndex, 0, ...replacement);
    return lines.join(newline);
}
function hasExecutableRuntimeEvidence(markdownText) {
    const evidence = [
        extractSection(markdownText, "Acceptance Criteria"),
        extractSection(markdownText, "Runtime Delta")
    ].join("\n");
    return /\bruntime\b|\bexecutable\b|\bCLI command\b|\bcommand\s+(?:writes|creates|emits|runs|executes)\b|\b(?:writes|creates|emits)\b[^\n.]*\b(?:file|output|artifact|behavior)\b/i.test(evidence);
}
function readRuntimeAcceptanceBlock(lines) {
    const start = lines.findIndex((line) => line === "runtime_acceptance:");
    if (start === -1) {
        return null;
    }
    let end = lines.length;
    for (let index = start + 1; index < lines.length; index += 1) {
        if (lines[index] !== "" && !/^\s/.test(lines[index])) {
            end = index;
            break;
        }
    }
    return { lines: lines.slice(start + 1, end) };
}
function readBlockScalar(lines, key, indent) {
    const pattern = new RegExp(`^ {${indent}}${key}:\\s*(.*)$`);
    for (const line of lines) {
        const match = line.match(pattern);
        if (match !== null) {
            return (0, front_matter_utils_1.parseScalar)(match[1]);
        }
    }
    return undefined;
}
function readBlockList(lines, key, indent) {
    const headerPattern = new RegExp(`^ {${indent}}${key}:\\s*$`);
    const itemPattern = new RegExp(`^ {${indent + 2}}-\\s+(.+)$`);
    const start = lines.findIndex((line) => headerPattern.test(line));
    if (start === -1) {
        return [];
    }
    const values = [];
    for (let index = start + 1; index < lines.length; index += 1) {
        const match = lines[index].match(itemPattern);
        if (match !== null) {
            values.push((0, front_matter_utils_1.parseScalar)(match[1]).trim());
            continue;
        }
        if (lines[index].trim() !== "") {
            break;
        }
    }
    return values.filter((value) => value !== "");
}
function readRuntimeAcceptanceException(lines, issues) {
    const start = lines.findIndex((line) => /^ {2}(?:exception|breakout_exception):\s*$/.test(line));
    if (start === -1) {
        return null;
    }
    const exceptionLines = [];
    for (let index = start + 1; index < lines.length; index += 1) {
        if (lines[index].trim() !== "" && !/^ {4}/.test(lines[index])) {
            break;
        }
        exceptionLines.push(lines[index]);
    }
    const parsed = {
        runtimeAcceptanceDependencies: [],
        distinctDeliverables: [],
        independentValidationBoundaries: [],
        independentRollbackBoundaries: []
    };
    for (const [field, property] of EXCEPTION_LIST_FIELDS) {
        const scalar = readBlockScalar(exceptionLines, field, 4)?.trim();
        parsed[property].push(...(scalar ? [scalar] : readBlockList(exceptionLines, field, 4)));
    }
    const hybridMergeUnsafeReason = readBlockScalar(exceptionLines, "hybrid_merge_unsafe_reason", 4)?.trim() || null;
    const nextRuntimeTask = readBlockScalar(exceptionLines, "next_runtime_task", 4)?.trim() || null;
    if (exceptionLines.length === 0) {
        issues.push("runtime_acceptance.exception must contain complete exception evidence.");
    }
    return {
        ...parsed,
        hybridMergeUnsafeReason,
        nextRuntimeTask
    };
}
function validateRuntimeAcceptanceException(exception, breakoutTask, declaredTaskSlugs) {
    if (exception === null) {
        return ["A breakout beyond the default contract streak requires runtime_acceptance.exception evidence."];
    }
    const issues = [];
    const requiredLists = [
        ["runtime_acceptance_dependencies", exception.runtimeAcceptanceDependencies],
        ["distinct_deliverables", exception.distinctDeliverables],
        ["independent_validation_boundaries", exception.independentValidationBoundaries],
        ["independent_rollback_boundaries", exception.independentRollbackBoundaries]
    ];
    for (const [field, values] of requiredLists) {
        if (values.length === 0) {
            issues.push(`runtime_acceptance.exception.${field} must be non-empty.`);
        }
    }
    if (exception.hybridMergeUnsafeReason === null) {
        issues.push("runtime_acceptance.exception.hybrid_merge_unsafe_reason must be non-empty.");
    }
    if (exception.nextRuntimeTask === null) {
        issues.push("runtime_acceptance.exception.next_runtime_task must be non-empty.");
    }
    else if (!declaredTaskSlugs.includes(exception.nextRuntimeTask)) {
        issues.push(`runtime_acceptance.exception.next_runtime_task '${exception.nextRuntimeTask}' is not declared.`);
    }
    else if (exception.nextRuntimeTask !== breakoutTask) {
        issues.push("runtime_acceptance.exception.next_runtime_task must match runtime_acceptance.breakout_task.");
    }
    return issues;
}
function sameOrderedValues(left, right) {
    return left.length === right.length && left.every((value, index) => value === right[index]);
}
function extractSection(markdownText, sectionName) {
    const lines = markdownText.split(/\r?\n/);
    const start = lines.findIndex((line) => line.trim() === `## ${sectionName}`);
    if (start === -1) {
        return "";
    }
    let end = lines.length;
    for (let index = start + 1; index < lines.length; index += 1) {
        if (/^##\s+/.test(lines[index])) {
            end = index;
            break;
        }
    }
    return lines.slice(start + 1, end).join("\n");
}
