"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_COMMAND_REPAIR_CANDIDATES = exports.MAX_COMMAND_REPAIR_TOKENS = void 0;
exports.repairCommandArgv = repairCommandArgv;
exports.quoteShellArgv = quoteShellArgv;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const command_capabilities_1 = require("./command-capabilities");
exports.MAX_COMMAND_REPAIR_TOKENS = 64;
exports.MAX_COMMAND_REPAIR_CANDIDATES = 5;
const COMMAND_REPAIR_NO_WRITE_BOUNDARY = "Command repair inspects authoritative descriptors and bounded repository-local path evidence only; it never invokes a proposed command or writes files.";
const GLOBAL_OPTIONS = new Map([
    ["--format", "value"],
    ["--protocol-version", "value"],
    ["--detail", "value"],
    ["--finding-code", "value"],
    ["--severity", "value"],
    ["--fields", "value"],
    ["--limit", "value"],
    ["--page-lane", "value"],
    ["--page-size", "value"],
    ["--page-cursor", "value"],
    ["--output-budget", "value"],
    ["--json-spacing", "value"],
    ["--output-file", "value"],
    ["--run-directory", "value"],
    ["--run-gate", "value"],
    ["--run-artifact", "value"]
]);
const LEGACY_COMMAND_SHAPES = new Map([
    ["milestone link", "link materialize"]
]);
function repairCommandArgv(rejectedArgv, options = {}) {
    const argv = normalizeRejectedArgv(rejectedArgv);
    const registry = options.registry ?? command_capabilities_1.COMMAND_DESCRIPTOR_REGISTRY;
    const repositoryRoot = node_path_1.default.resolve(options.repositoryRoot ?? process.cwd());
    const extracted = extractGlobalOptions(argv);
    const matches = rankDescriptorMatches(registry, extracted.commandArgv);
    const registryRevision = digestValue(registry.map((descriptor) => ({
        command: descriptor.command,
        availability: descriptor.availability,
        revision: descriptorRevision(descriptor)
    })));
    if (matches.length === 0) {
        return Object.freeze({
            schemaVersion: 1,
            rejectedArgv: Object.freeze([...argv]),
            descriptorRegistryRevision: registryRevision,
            confidence: "low",
            selectionRequired: false,
            candidateCount: 0,
            candidates: Object.freeze([]),
            readOnly: true,
            executed: false,
            noWriteBoundary: COMMAND_REPAIR_NO_WRITE_BOUNDARY
        });
    }
    const first = matches[0];
    const second = matches[1];
    const selectionRequired = !first.legacyShape
        && second !== undefined
        && first.score < 1000
        && first.score - second.score < 80;
    const confidence = first.score >= 900 && !selectionRequired
        ? "high"
        : first.score >= 520
            ? "medium"
            : "low";
    const candidates = matches.slice(0, exports.MAX_COMMAND_REPAIR_CANDIDATES).map((match, index) => buildProposal({
        match,
        rank: index + 1,
        commandArgv: extracted.commandArgv,
        globalArgv: extracted.globalArgv,
        globalEdits: extracted.edits,
        repositoryRoot,
        selectionRequired: selectionRequired && index === 0
    }));
    return Object.freeze({
        schemaVersion: 1,
        rejectedArgv: Object.freeze([...argv]),
        descriptorRegistryRevision: registryRevision,
        confidence,
        selectionRequired,
        candidateCount: candidates.length,
        candidates: Object.freeze(candidates),
        readOnly: true,
        executed: false,
        noWriteBoundary: COMMAND_REPAIR_NO_WRITE_BOUNDARY
    });
}
function quoteShellArgv(argv) {
    return argv.map((value) => /^[A-Za-z0-9_./:=@%+,-]+$/u.test(value)
        ? value
        : `'${value.replace(/'/g, `'"'"'`)}'`).join(" ");
}
function normalizeRejectedArgv(input) {
    if (!Array.isArray(input) || input.length === 0) {
        throw new Error("Command repair argv must contain at least one token.");
    }
    if (input.length > exports.MAX_COMMAND_REPAIR_TOKENS) {
        throw new Error(`Command repair argv may contain at most ${exports.MAX_COMMAND_REPAIR_TOKENS} tokens.`);
    }
    const normalized = input.map((value, index) => {
        if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
            throw new Error(`Command repair argv token ${index + 1} must be a non-empty string without NUL bytes.`);
        }
        return value;
    });
    if (normalized[0] === "day-shift")
        normalized.shift();
    while (normalized[0] === "--")
        normalized.shift();
    if (normalized.length === 0)
        throw new Error("Command repair argv must identify a command.");
    return normalized;
}
function extractGlobalOptions(argv) {
    const commandArgv = [];
    const globalArgv = [];
    const edits = [];
    for (let index = 0; index < argv.length; index += 1) {
        const token = argv[index];
        if (token === "--")
            continue;
        const optionName = token.split("=", 1)[0];
        const posture = GLOBAL_OPTIONS.get(optionName);
        if (posture === undefined) {
            commandArgv.push(token);
            continue;
        }
        globalArgv.push(token);
        if (posture === "value" && !token.includes("=")) {
            const value = argv[index + 1];
            if (value !== undefined) {
                globalArgv.push(value);
                index += 1;
            }
        }
        edits.push({
            kind: "move",
            index,
            from: token,
            to: token,
            reason: "Global output and lifecycle options are placed after the descriptor-owned command inputs in canonical repair argv."
        });
    }
    return { commandArgv, globalArgv, edits };
}
function rankDescriptorMatches(registry, argv) {
    const firstOption = argv.findIndex((token) => token.startsWith("-"));
    const prefixLength = firstOption === -1 ? argv.length : firstOption;
    const prefix = argv.slice(0, Math.min(prefixLength, 5));
    const legacyIdentity = prefix.slice(0, 2).join(" ");
    const legacyTarget = LEGACY_COMMAND_SHAPES.get(legacyIdentity);
    const matches = [];
    for (const descriptor of registry) {
        if (descriptor.availability !== "callable")
            continue;
        const identities = [descriptor.command, ...descriptor.aliases];
        let best = null;
        for (const identity of identities) {
            const commandTokens = identity.split(/\s+/u);
            const maximumConsumed = Math.min(prefix.length, Math.max(1, commandTokens.length + 1));
            for (let consumeCount = 1; consumeCount <= maximumConsumed; consumeCount += 1) {
                const supplied = prefix.slice(0, consumeCount);
                let score = scoreCommandTokens(commandTokens, supplied);
                const isLegacy = legacyTarget === descriptor.command && consumeCount === 2;
                if (isLegacy)
                    score = 1200;
                if (identity === descriptor.command)
                    score += 5;
                if (best === null || score > best.score) {
                    best = { descriptor, score, consumeCount, commandTokens, legacyShape: isLegacy };
                }
            }
        }
        if (best !== null && best.score >= 260)
            matches.push(best);
    }
    return matches.sort((left, right) => right.score - left.score
        || left.descriptor.command.localeCompare(right.descriptor.command));
}
function scoreCommandTokens(expected, supplied) {
    if (expected.length === supplied.length && expected.every((token, index) => token === supplied[index])) {
        return 1100;
    }
    let score = 0;
    const length = Math.max(expected.length, supplied.length);
    for (let index = 0; index < length; index += 1) {
        const expectedToken = expected[index];
        const suppliedToken = supplied[index];
        if (expectedToken === undefined || suppliedToken === undefined) {
            score -= 90;
            continue;
        }
        if (expectedToken === suppliedToken) {
            score += 360;
            continue;
        }
        const distance = levenshtein(expectedToken, suppliedToken);
        if (distance === 1)
            score += 250;
        else if (distance === 2 && Math.max(expectedToken.length, suppliedToken.length) >= 6)
            score += 150;
        else if (expected.includes(suppliedToken))
            score += 100;
        else
            score -= 120;
    }
    return score - Math.abs(expected.length - supplied.length) * 40;
}
function buildProposal(options) {
    const { descriptor } = options.match;
    const expectedCommand = descriptor.command.split(/\s+/u);
    const suppliedCommand = options.commandArgv.slice(0, options.match.consumeCount);
    const remainder = options.commandArgv.slice(options.match.consumeCount);
    const rejectedTokens = [];
    const edits = [...options.globalEdits];
    const unresolved = [];
    if (suppliedCommand.join(" ") !== descriptor.command) {
        suppliedCommand.forEach((token, index) => rejectedTokens.push({
            index,
            token,
            reason: `Token does not match authoritative command identity '${descriptor.command}'.`
        }));
        edits.push({
            kind: "replace",
            index: 0,
            from: suppliedCommand.join(" "),
            to: descriptor.command,
            reason: options.match.legacyShape
                ? "Recognized bounded legacy command shape and replaced it with the current registered descriptor identity."
                : "Replaced the rejected command prefix with the ranked authoritative descriptor identity."
        });
    }
    const argumentInputs = descriptor.inputs.filter((input) => input.kind === "argument");
    const optionInputs = descriptor.inputs.filter((input) => input.kind === "option");
    const optionByFlag = new Map(optionInputs.map((input) => [inputFlag(input), input]));
    const parsedOptions = [];
    const positional = [];
    let argumentIndex = 0;
    for (let index = 0; index < remainder.length; index += 1) {
        const token = remainder[index];
        const sourceIndex = options.match.consumeCount + index;
        if (!token.startsWith("--")) {
            const input = selectArgumentInput(argumentInputs, argumentIndex);
            positional.push({ value: token, sourceIndex, input });
            if (input === null) {
                rejectedTokens.push({ index: sourceIndex, token, reason: "Token is not accepted by the matched descriptor input contract." });
                unresolved.push(`unexpected positional token '${token}'`);
            }
            else if (!input.variadic) {
                argumentIndex += 1;
            }
            continue;
        }
        const [rawFlag, inlineValue] = splitOptionToken(token);
        let input = optionByFlag.get(rawFlag) ?? null;
        let correctedFlag = rawFlag;
        if (input === null) {
            const positionalInput = argumentInputs.find((candidate) => inputFlag(candidate) === rawFlag);
            if (positionalInput !== undefined) {
                const value = inlineValue ?? remainder[index + 1];
                rejectedTokens.push({ index: sourceIndex, token, reason: `Descriptor input '${positionalInput.name}' is positional, not an option.` });
                edits.push({
                    kind: "remove",
                    index: sourceIndex,
                    from: rawFlag,
                    to: null,
                    reason: `Removed option spelling for positional input '${positionalInput.name}'.`
                });
                if (value === undefined || value.startsWith("--")) {
                    unresolved.push(`missing positional value '${positionalInput.name}'`);
                }
                else {
                    positional.push({ value, sourceIndex: inlineValue === undefined ? sourceIndex + 1 : sourceIndex, input: positionalInput });
                    if (inlineValue === undefined)
                        index += 1;
                    argumentIndex = Math.max(argumentIndex, argumentInputs.indexOf(positionalInput) + 1);
                }
                continue;
            }
            const nearFlags = optionInputs
                .map((candidate) => ({ input: candidate, flag: inputFlag(candidate), distance: levenshtein(rawFlag, inputFlag(candidate)) }))
                .filter((candidate) => candidate.distance <= 2)
                .sort((left, right) => left.distance - right.distance || left.flag.localeCompare(right.flag));
            if (nearFlags.length === 1 || (nearFlags[0] !== undefined && nearFlags[1] !== undefined && nearFlags[0].distance < nearFlags[1].distance)) {
                input = nearFlags[0].input;
                correctedFlag = nearFlags[0].flag;
                rejectedTokens.push({ index: sourceIndex, token, reason: `Unknown option; nearest descriptor option is '${correctedFlag}'.` });
                edits.push({ kind: "replace", index: sourceIndex, from: rawFlag, to: correctedFlag, reason: "Corrected a bounded unique option-name near-match." });
            }
            else {
                rejectedTokens.push({ index: sourceIndex, token, reason: "Unknown or ambiguous option for the matched descriptor." });
                unresolved.push(`unknown option '${rawFlag}'`);
                continue;
            }
        }
        if (input.valueType === "boolean") {
            if (inlineValue !== undefined) {
                rejectedTokens.push({ index: sourceIndex, token, reason: `Boolean option '${correctedFlag}' does not accept an inline value.` });
                unresolved.push(`invalid boolean option '${correctedFlag}'`);
            }
            else {
                parsedOptions.push({ input, flag: correctedFlag, value: true, sourceIndex });
            }
            continue;
        }
        const rawValue = inlineValue ?? remainder[index + 1];
        if (rawValue === undefined || (inlineValue === undefined && rawValue.startsWith("--"))) {
            rejectedTokens.push({ index: sourceIndex, token, reason: `Option '${correctedFlag}' requires a value.` });
            unresolved.push(`missing value for '${correctedFlag}'`);
            continue;
        }
        if (inlineValue === undefined)
            index += 1;
        const normalizedValue = normalizeOptionValue({
            input,
            value: rawValue,
            repositoryRoot: options.repositoryRoot
        });
        if (normalizedValue.ambiguous) {
            rejectedTokens.push({ index: sourceIndex + (inlineValue === undefined ? 1 : 0), token: rawValue, reason: normalizedValue.reason });
            unresolved.push(normalizedValue.reason);
        }
        else if (normalizedValue.value !== rawValue) {
            edits.push({
                kind: "replace",
                index: sourceIndex + (inlineValue === undefined ? 1 : 0),
                from: rawValue,
                to: normalizedValue.value,
                reason: normalizedValue.reason
            });
            rejectedTokens.push({
                index: sourceIndex + (inlineValue === undefined ? 1 : 0),
                token: rawValue,
                reason: normalizedValue.reason
            });
        }
        parsedOptions.push({ input, flag: correctedFlag, value: normalizedValue.value, sourceIndex });
    }
    const present = new Map();
    for (const entry of positional) {
        if (entry.input !== null) {
            const previous = present.get(entry.input.name);
            present.set(entry.input.name, entry.input.variadic
                ? [...(Array.isArray(previous) ? previous : []), entry.value]
                : entry.value);
        }
    }
    for (const entry of parsedOptions)
        present.set(entry.input.name, entry.value);
    const skeletonArguments = positional.filter((entry) => entry.input !== null).map((entry) => entry.value);
    const skeletonOptions = parsedOptions.flatMap((entry) => entry.input.valueType === "boolean"
        ? [entry.flag]
        : [entry.flag, String(entry.value)]);
    const missingInputs = collectMissingInputs(descriptor, present);
    for (const input of missingInputs) {
        unresolved.push(`missing required input '${input.name}'`);
        if (input.kind === "argument")
            skeletonArguments.push(`<${input.name}>`);
        else
            skeletonOptions.push(inputFlag(input), input.valueType === "boolean" ? "<true>" : `<${input.name}>`);
        edits.push({
            kind: "insert",
            index: null,
            from: null,
            to: input.kind === "argument" ? `<${input.name}>` : `${inputFlag(input)} <${input.name}>`,
            reason: "Inserted an explicit non-callable skeleton marker for a missing required descriptor input."
        });
    }
    const conflicts = collectInputConflicts(descriptor, present);
    unresolved.push(...conflicts);
    const selectedArtifactObserved = [...present.entries()].some(([name, value]) => isPathInput(descriptor.inputs.find((input) => input.name === name))
        && typeof value === "string"
        && !value.includes("<"));
    const preconditions = descriptor.preconditions.map((precondition) => ({
        id: precondition.id,
        description: precondition.description,
        status: precondition.kind === "selection"
            ? (selectedArtifactObserved ? "satisfied" : "blocked")
            : "unverified",
        expected: precondition.required ? "required evidence present" : "optional evidence",
        observed: precondition.kind === "selection" ? (selectedArtifactObserved ? "selected input present" : null) : null
    }));
    if (descriptor.selectionRequired && !selectedArtifactObserved) {
        unresolved.push("required artifact selection is unresolved");
    }
    for (const input of missingInputs) {
        preconditions.push({
            id: `input.${input.name}`,
            description: `Descriptor input '${input.name}' is required by the current input contract.`,
            status: "blocked",
            expected: input.kind === "argument" ? input.name : inputFlag(input),
            observed: null
        });
    }
    const skeleton = ["day-shift", ...expectedCommand, ...skeletonArguments, ...skeletonOptions, ...options.globalArgv];
    const uniqueUnresolved = [...new Set(unresolved)];
    const callable = uniqueUnresolved.length === 0 && !options.selectionRequired;
    const argv = callable ? Object.freeze([...skeleton]) : null;
    const nonCallableReason = options.selectionRequired
        ? "Command identity remains ambiguous; explicit candidate selection is required."
        : uniqueUnresolved.length > 0
            ? uniqueUnresolved.join("; ")
            : null;
    return Object.freeze({
        rank: options.rank,
        score: options.match.score,
        matchedCommand: descriptor.command,
        descriptorRevision: descriptorRevision(descriptor),
        rejectedTokens: Object.freeze(rejectedTokens),
        edits: Object.freeze(edits),
        argv,
        argvSkeleton: Object.freeze(skeleton),
        shellCommand: argv === null ? null : quoteShellArgv(argv),
        callable,
        nonCallableReason,
        selectionRequired: options.selectionRequired,
        requiredPreconditions: Object.freeze(preconditions),
        writePosture: Object.freeze({
            writeCapable: descriptor.writeCapable,
            authorization: descriptor.authorization,
            confirmation: descriptor.confirmation,
            mutationPosture: descriptor.mutationPosture,
            coordinationScope: descriptor.coordinationScope,
            effects: Object.freeze({ ...descriptor.effects })
        })
    });
}
function selectArgumentInput(inputs, index) {
    if (inputs.length === 0)
        return null;
    if (index < inputs.length)
        return inputs[index];
    const last = inputs.at(-1);
    return last.variadic ? last : null;
}
function inputFlag(input) {
    return `--${input.name.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}`;
}
function splitOptionToken(token) {
    const separator = token.indexOf("=");
    return separator === -1
        ? [token, undefined]
        : [token.slice(0, separator), token.slice(separator + 1)];
}
function normalizeOptionValue(options) {
    if (isPathInput(options.input)) {
        const repaired = repairRepositoryPath(options.repositoryRoot, options.value);
        if (repaired.kind === "repaired") {
            return { value: repaired.path, ambiguous: false, reason: "Corrected a unique bounded repository-local artifact path near-match." };
        }
        if (repaired.kind === "ambiguous") {
            return { value: options.value, ambiguous: true, reason: "Repository-local artifact path near-match is ambiguous." };
        }
    }
    if (!isFreeFormInput(options.input)) {
        const candidates = extractEnumCandidates(options.input.validation);
        if (candidates.length > 1 && !candidates.includes(options.value)) {
            const ranked = candidates
                .map((candidate) => ({ candidate, distance: levenshtein(options.value.toLowerCase(), candidate.toLowerCase()) }))
                .sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate));
            const maximum = Math.min(2, Math.max(1, Math.floor(options.value.length / 4)));
            if (ranked[0] !== undefined && ranked[0].distance <= maximum
                && (ranked[1] === undefined || ranked[0].distance < ranked[1].distance)) {
                return { value: ranked[0].candidate, ambiguous: false, reason: "Corrected a unique bounded enum-value near-match from descriptor validation." };
            }
        }
    }
    return { value: options.value, ambiguous: false, reason: "Value already matches bounded repair evidence." };
}
function isPathInput(input) {
    return /(?:repository-relative|\bpath\b|\bartifact\b|\bfile\b|\bdirectory\b)/iu.test(input.validation)
        && !/(?:output format|field|identity|timestamp)/iu.test(input.validation);
}
function isFreeFormInput(input) {
    return isPathInput(input)
        || /(?:text|reason|summary|title|actor|timestamp|identity|sha-?256|json|integer|count|boolean)/iu.test(input.validation);
}
function extractEnumCandidates(validation) {
    const matches = validation.toLowerCase().match(/[a-z][a-z0-9-]*(?:\s*,\s*[a-z][a-z0-9-]*)+(?:\s*,?\s*or\s+[a-z][a-z0-9-]+)|[a-z][a-z0-9-]*\s+or\s+[a-z][a-z0-9-]*/gu) ?? [];
    const candidates = matches.flatMap((match) => match.split(/\s*,\s*|\s+or\s+/u));
    return [...new Set(candidates.filter((candidate) => /^[a-z][a-z0-9-]*$/u.test(candidate)))];
}
function repairRepositoryPath(repositoryRoot, input) {
    if (input.includes("<") || node_path_1.default.isAbsolute(input))
        return { kind: "none" };
    const normalized = input.replace(/\\/gu, "/").replace(/^\.\//u, "");
    const direct = node_path_1.default.resolve(repositoryRoot, normalized);
    if (!isInside(repositoryRoot, direct) || node_fs_1.default.existsSync(direct))
        return { kind: "none" };
    const components = normalized.split("/").filter(Boolean);
    let current = repositoryRoot;
    const repaired = [];
    let changed = false;
    for (const component of components) {
        const directComponent = node_path_1.default.join(current, component);
        if (node_fs_1.default.existsSync(directComponent)) {
            repaired.push(component);
            current = directComponent;
            continue;
        }
        if (!node_fs_1.default.existsSync(current) || !node_fs_1.default.statSync(current).isDirectory())
            return { kind: "none" };
        const entries = node_fs_1.default.readdirSync(current).slice(0, 200)
            .map((candidate) => ({ candidate, distance: levenshtein(component, candidate) }))
            .filter((candidate) => candidate.distance <= 2)
            .sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate));
        if (entries.length === 0)
            return { kind: "none" };
        if (entries[1] !== undefined && entries[0].distance === entries[1].distance)
            return { kind: "ambiguous" };
        changed = true;
        repaired.push(entries[0].candidate);
        current = node_path_1.default.join(current, entries[0].candidate);
    }
    return changed && node_fs_1.default.existsSync(current)
        ? { kind: "repaired", path: repaired.join("/") }
        : { kind: "none" };
}
function collectMissingInputs(descriptor, present) {
    const missing = new Map();
    const byName = new Map(descriptor.inputs.map((input) => [input.name, input]));
    for (const input of descriptor.inputs) {
        if (input.required && !present.has(input.name))
            missing.set(input.name, input);
    }
    for (const rule of descriptor.semantics?.conditionalInputs ?? []) {
        const source = present.has(rule.whenInput)
            ? present.get(rule.whenInput)
            : byName.get(rule.whenInput)?.defaultValue;
        if (source === rule.equals && !present.has(rule.input)) {
            const input = byName.get(rule.input);
            if (input !== undefined)
                missing.set(input.name, input);
        }
    }
    for (const rule of descriptor.semantics?.dependentInputs ?? []) {
        if (!present.has(rule.input))
            continue;
        for (const required of rule.requires) {
            if (!present.has(required)) {
                const input = byName.get(required);
                if (input !== undefined)
                    missing.set(input.name, input);
            }
        }
    }
    return [...missing.values()];
}
function collectInputConflicts(descriptor, present) {
    return (descriptor.semantics?.conflictingInputs ?? [])
        .filter((rule) => rule.inputs.filter((input) => present.has(input)).length > 1)
        .map((rule) => `conflicting inputs '${rule.inputs.join("', '")}' require explicit correction`);
}
function descriptorRevision(descriptor) {
    return digestValue(descriptor);
}
function digestValue(value) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value)).digest("hex")}`;
}
function isInside(root, candidate) {
    const relative = node_path_1.default.relative(root, candidate);
    return relative === "" || (!relative.startsWith("..") && !node_path_1.default.isAbsolute(relative));
}
function levenshtein(left, right) {
    const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
    for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
        const current = [leftIndex];
        for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
            current[rightIndex] = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
        }
        previous.splice(0, previous.length, ...current);
    }
    return previous[right.length];
}
