"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspectTaskValidationInputCoverage = inspectTaskValidationInputCoverage;
exports.inspectDeclaredValidationInputCoverage = inspectDeclaredValidationInputCoverage;
exports.inspectValidationInputCoverage = inspectValidationInputCoverage;
exports.inspectTaskDocumentValidationInputCoverage = inspectTaskDocumentValidationInputCoverage;
exports.createValidationInputCoverageFinding = createValidationInputCoverageFinding;
exports.parseValidationDependencyPaths = parseValidationDependencyPaths;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const output_1 = require("../output");
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_contract_impact_1 = require("./task-contract-impact");
const validation_command_1 = require("./validation-command");
const NON_PATH_VALUE_FLAG_PATTERN = /(?:^|[-_])(?:e|eval|expression|filter|format|grep|name|partition|pattern|project|reporter|shard|timeout)(?:$|[-_])/iu;
const DYNAMIC_PATH_TOKEN_PATTERN = /\[|\]|[{}*?$`]/u;
const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//u;
const FILE_NAME_PATTERN = /(?:^|\/)[^/]+\.[A-Za-z0-9][A-Za-z0-9._-]*$/u;
function inspectTaskValidationInputCoverage(options) {
    let declaredCommands;
    try {
        declaredCommands = (0, validation_command_1.parseDeclaredValidationCommands)(options.validationCommandsText);
    }
    catch (error) {
        return invalidCommandInspection(errorMessage(error));
    }
    return inspectDeclaredValidationInputCoverage({
        repositoryRoot: options.repositoryRoot,
        declaredCommands,
        writableTargets: options.writableTargets,
        generatedOutputs: options.generatedOutputs,
        dependencies: options.dependencies
    });
}
function inspectDeclaredValidationInputCoverage(options) {
    const commands = [];
    const issues = [];
    for (const [commandIndex, declaredCommand] of options.declaredCommands.entries()) {
        try {
            commands.push(...(0, validation_command_1.parseValidationCommands)([declaredCommand]).map((command) => ({
                ...command,
                commandIndex
            })));
        }
        catch (error) {
            issues.push({
                kind: "invalid-command",
                commandIndex,
                argvIndex: null,
                command: declaredCommand,
                raw: null,
                normalizedPath: null,
                message: `Validation command ${commandIndex + 1} is not valid shell-free argv: ${errorMessage(error)}`
            });
        }
    }
    return inspectValidationInputCoverage({
        repositoryRoot: options.repositoryRoot,
        commands,
        writableTargets: options.writableTargets,
        generatedOutputs: options.generatedOutputs,
        dependencies: options.dependencies,
        initialIssues: issues
    });
}
function inspectValidationInputCoverage(options) {
    const repositoryRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(options.repositoryRoot));
    const writableTargets = canonicalPathSet(options.writableTargets);
    const generatedOutputs = canonicalPathSet(options.generatedOutputs ?? []);
    const dependencies = canonicalPathSet(options.dependencies ?? []);
    const paths = [];
    const issues = [...(options.initialIssues ?? [])];
    for (const command of options.commands) {
        for (const [argvIndex, raw] of command.argv.entries()) {
            if (!isExplicitPathOperand(command.argv, argvIndex))
                continue;
            let normalizedPath;
            try {
                normalizedPath = (0, paths_1.parseRepositoryRelativePath)(raw).normalizedPath;
            }
            catch (error) {
                paths.push({
                    commandIndex: command.commandIndex,
                    argvIndex,
                    command: command.declaredCommand,
                    raw,
                    normalizedPath: null,
                    exists: false,
                    classification: "invalid-repository-path"
                });
                issues.push({
                    kind: "invalid-path",
                    commandIndex: command.commandIndex,
                    argvIndex,
                    command: command.declaredCommand,
                    raw,
                    normalizedPath: null,
                    message: `Validation command ${command.commandIndex + 1} references unsafe repository path operand '${raw}': ${errorMessage(error)}`
                });
                continue;
            }
            const exists = node_fs_1.default.existsSync(node_path_1.default.join(repositoryRoot, normalizedPath));
            const classification = pathSetCovers(writableTargets, normalizedPath)
                ? "declared-writable"
                : pathSetCovers(generatedOutputs, normalizedPath)
                    ? "declared-generated"
                    : pathSetCovers(dependencies, normalizedPath) && exists
                        ? "existing-dependency"
                        : exists
                            ? "existing-read-only"
                            : "missing-undeclared";
            paths.push({
                commandIndex: command.commandIndex,
                argvIndex,
                command: command.declaredCommand,
                raw,
                normalizedPath,
                exists,
                classification
            });
            if (classification === "missing-undeclared") {
                issues.push({
                    kind: "missing-undeclared",
                    commandIndex: command.commandIndex,
                    argvIndex,
                    command: command.declaredCommand,
                    raw,
                    normalizedPath,
                    message: `Validation command ${command.commandIndex + 1} references missing repository path '${normalizedPath}', but the path is not declared as writable or generated scope.`
                });
            }
        }
    }
    const fingerprint = `sha256:${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify({
        commands: options.commands,
        writableTargets: [...writableTargets].sort(),
        generatedOutputs: [...generatedOutputs].sort(),
        dependencies: [...dependencies].sort(),
        paths,
        issues
    })).digest("hex")}`;
    return {
        commands: options.commands.map((command) => ({
            ...command,
            argv: [...command.argv]
        })),
        paths,
        issues,
        fingerprint,
        complete: issues.length === 0,
        readOnly: true
    };
}
function inspectTaskDocumentValidationInputCoverage(options) {
    const sections = (0, planning_document_utils_1.parseMarkdownSections)(options.taskText);
    const summaryPath = options.taskDefinitionPath.replace(/task-definition\.md$/u, "implementation-summary.md");
    const targetEvidence = (0, declared_target_paths_1.readTaskDeclaredTargetPathEvidence)(options.taskDefinitionPath, options.taskText);
    const targetPaths = targetEvidence.explicitTargetPaths.filter((targetPath) => targetPath !== options.taskDefinitionPath && targetPath !== summaryPath);
    const dependencies = parseValidationDependencyPaths(sections.get("Dependencies") ?? "");
    const validationCommandsText = sections.get("Validation Commands") ?? "";
    const contractImpact = (0, task_contract_impact_1.inspectTaskContractImpact)({
        repositoryRoot: options.repositoryRoot,
        targetPaths,
        validationCommandsText
    });
    const contractDependencies = [...contractImpact.readOnlyDependencies, ...contractImpact.frozenDependencies]
        .flatMap((entry) => entry.path === null ? [] : [entry.path]);
    const generatedOutputs = contractImpact.generatedOutputs
        .flatMap((entry) => entry.path === null ? [] : [entry.path]);
    return {
        targetPaths,
        dependencies,
        contractImpact,
        coverage: sections.has("Validation Commands")
            ? inspectTaskValidationInputCoverage({
                repositoryRoot: options.repositoryRoot,
                validationCommandsText,
                writableTargets: targetPaths,
                generatedOutputs,
                dependencies: [...dependencies, ...contractDependencies]
            })
            : inspectDeclaredValidationInputCoverage({
                repositoryRoot: options.repositoryRoot,
                declaredCommands: [],
                writableTargets: targetPaths,
                generatedOutputs,
                dependencies: [...dependencies, ...contractDependencies]
            }),
        readOnly: true
    };
}
function createValidationInputCoverageFinding(options) {
    const affectedPath = options.issue.normalizedPath ?? options.issue.raw ?? options.artifactPath;
    const code = options.issue.kind === "missing-undeclared"
        ? `${options.codePrefix}_VALIDATION_INPUT_TARGET_COVERAGE_REQUIRED`
        : options.issue.kind === "invalid-path"
            ? `${options.codePrefix}_VALIDATION_INPUT_PATH_INVALID`
            : `${options.codePrefix}_VALIDATION_COMMAND_INVALID`;
    const recovery = options.issue.kind === "missing-undeclared"
        ? `Either create '${affectedPath}' as an existing read-only validation input before continuing, or declare it as an Implementation Target Path or contract-generated output. Then rerun the readiness boundary; this check performs no repair writes.`
        : options.issue.kind === "invalid-path"
            ? "Replace the unsafe operand with a static repository-relative path, then rerun the readiness boundary."
            : "Replace the command with one complete shell-free argv line inside the required shell fence, then rerun the readiness boundary.";
    return (0, output_1.createBlockingValidationFinding)({
        code,
        message: options.issue.message,
        path: options.artifactPath,
        affectedPaths: [{
                role: "command-evidence",
                path: affectedPath,
                description: "Static repository path operand declared by a validation command."
            }],
        blockingReason: options.issue.kind === "missing-undeclared"
            ? "unsafe-scope-mismatch"
            : "invalid-command-input",
        recovery,
        evidence: {
            category: "planning-review",
            details: {
                reviewBoundary: options.reviewBoundary,
                commandIndex: options.issue.commandIndex,
                argvIndex: options.issue.argvIndex,
                command: options.issue.command,
                rawOperand: options.issue.raw,
                normalizedPath: options.issue.normalizedPath,
                issueKind: options.issue.kind,
                noWriteBoundary: "Validation input coverage is read-only and never creates missing paths.",
                ...options.context
            }
        }
    });
}
function parseValidationDependencyPaths(section) {
    const paths = [];
    for (const line of section.split(/\r?\n/u)) {
        const match = line.trim().match(/^-\s+(?:`([^`]+)`|([^\s].*))$/u);
        if (match === null)
            continue;
        const value = (match[1] ?? match[2]).trim();
        if (/^none(?: currently)?\.?$/iu.test(value))
            continue;
        try {
            paths.push((0, paths_1.parseRepositoryRelativePath)(value).normalizedPath);
        }
        catch {
            continue;
        }
    }
    return [...new Set(paths)];
}
function invalidCommandInspection(message) {
    const issue = {
        kind: "invalid-command",
        commandIndex: 0,
        argvIndex: null,
        command: "",
        raw: null,
        normalizedPath: null,
        message
    };
    return {
        commands: [],
        paths: [],
        issues: [issue],
        fingerprint: `sha256:${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify(issue)).digest("hex")}`,
        complete: false,
        readOnly: true
    };
}
function isExplicitPathOperand(argv, argvIndex) {
    const token = argv[argvIndex];
    if (argvIndex === executableArgvIndex(argv))
        return false;
    if (token === ""
        || token.startsWith("-")
        || token.includes("=")
        || DYNAMIC_PATH_TOKEN_PATTERN.test(token)
        || URI_SCHEME_PATTERN.test(token)) {
        return false;
    }
    const previous = argv[argvIndex - 1];
    if (previous !== undefined && previous.startsWith("-") && NON_PATH_VALUE_FLAG_PATTERN.test(previous)) {
        return false;
    }
    if ((0, paths_1.isAbsolutePathLike)(token) || token === ".." || token.startsWith("../") || token.includes("/../")) {
        return true;
    }
    const normalizedSeparators = token.replace(/\\/gu, "/");
    return normalizedSeparators.startsWith("./")
        || (!normalizedSeparators.startsWith("@") && normalizedSeparators.includes("/"))
        || FILE_NAME_PATTERN.test(normalizedSeparators);
}
function executableArgvIndex(argv) {
    if (argv[0] !== "env")
        return 0;
    const executableIndex = argv.findIndex((entry, index) => index > 0 && !/^[A-Za-z_][A-Za-z0-9_]*=.*$/u.test(entry));
    return executableIndex < 0 ? 0 : executableIndex;
}
function canonicalPathSet(values) {
    const result = new Set();
    for (const value of values) {
        try {
            result.add((0, paths_1.parseRepositoryRelativePath)(value).normalizedPath);
        }
        catch {
            continue;
        }
    }
    return result;
}
function pathSetCovers(declaredPaths, candidate) {
    for (const declaredPath of declaredPaths) {
        if (candidate === declaredPath || candidate.startsWith(`${declaredPath}/`))
            return true;
    }
    return false;
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
