"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_VALIDATION_COMMAND_LINE_BYTES = exports.MAX_DECLARED_VALIDATION_COMMANDS = void 0;
exports.parseDeclaredValidationCommands = parseDeclaredValidationCommands;
exports.parseShellFencedValidationCommandLines = parseShellFencedValidationCommandLines;
exports.parseValidationCommands = parseValidationCommands;
exports.parseShellFreeArgv = parseShellFreeArgv;
exports.MAX_DECLARED_VALIDATION_COMMANDS = 64;
exports.MAX_VALIDATION_COMMAND_LINE_BYTES = 64 * 1024;
function parseDeclaredValidationCommands(section) {
    if (/^```/mu.test(section))
        return parseShellFencedValidationCommandLines(section);
    const commands = section.split(/\r?\n/u).flatMap((rawLine) => {
        const line = rawLine.trim();
        const match = line.match(/^-\s+(?:`([^`]+)`|(.+))$/u);
        if (match === null)
            return [];
        const command = (match[1] ?? match[2]).trim();
        return command === "" ? [] : [command];
    });
    if (commands.length === 0 || commands.length > exports.MAX_DECLARED_VALIDATION_COMMANDS) {
        throw new TypeError(`Validation Commands must declare 1-${exports.MAX_DECLARED_VALIDATION_COMMANDS} executable lines.`);
    }
    return commands;
}
function parseShellFencedValidationCommandLines(section) {
    const lines = section.split(/\r?\n/u);
    const commands = [];
    let inFence = false;
    let sawFence = false;
    for (const rawLine of lines) {
        const line = rawLine.trim();
        const fence = line.match(/^```([A-Za-z0-9_-]*)$/u);
        if (fence !== null) {
            if (!inFence) {
                if (sawFence)
                    throw new TypeError("Validation Commands must contain exactly one shell code fence.");
                if (fence[1] !== "" && !["sh", "bash", "shell"].includes(fence[1].toLowerCase())) {
                    throw new TypeError("Validation Commands must use a shell code fence.");
                }
                inFence = true;
                sawFence = true;
            }
            else {
                if (fence[1] !== "") {
                    throw new TypeError("The closing Validation Commands code fence cannot declare a language.");
                }
                inFence = false;
            }
            continue;
        }
        if (line === "" || line.startsWith("#"))
            continue;
        if (!inFence)
            throw new TypeError("Validation Commands may contain executable lines only inside its shell code fence.");
        if (Buffer.byteLength(line, "utf8") > exports.MAX_VALIDATION_COMMAND_LINE_BYTES) {
            throw new TypeError(`Declared validation command exceeds ${exports.MAX_VALIDATION_COMMAND_LINE_BYTES} bytes.`);
        }
        commands.push(line);
    }
    if (!sawFence || inFence)
        throw new TypeError("Validation Commands requires one complete shell code fence.");
    if (commands.length === 0 || commands.length > exports.MAX_DECLARED_VALIDATION_COMMANDS) {
        throw new TypeError(`Validation Commands must declare 1-${exports.MAX_DECLARED_VALIDATION_COMMANDS} executable lines.`);
    }
    return commands;
}
function parseValidationCommands(commands) {
    return commands.map((declaredCommand, commandIndex) => {
        if (Buffer.byteLength(declaredCommand, "utf8") > exports.MAX_VALIDATION_COMMAND_LINE_BYTES) {
            throw new TypeError(`Declared validation command exceeds ${exports.MAX_VALIDATION_COMMAND_LINE_BYTES} bytes.`);
        }
        return {
            commandIndex,
            declaredCommand,
            argv: parseShellFreeArgv(declaredCommand)
        };
    });
}
function parseShellFreeArgv(command) {
    const argv = [];
    let token = "";
    let tokenStarted = false;
    let quote = null;
    let escaped = false;
    for (const character of command) {
        if (escaped) {
            token += character;
            tokenStarted = true;
            escaped = false;
            continue;
        }
        if (quote === "single") {
            if (character === "'")
                quote = null;
            else
                token += character;
            tokenStarted = true;
            continue;
        }
        if (quote === "double") {
            if (character === '"')
                quote = null;
            else if (character === "\\")
                escaped = true;
            else {
                if (character === "$" || character === "`")
                    throw unsafeCommand(command);
                token += character;
            }
            tokenStarted = true;
            continue;
        }
        if (character === "\\") {
            escaped = true;
            tokenStarted = true;
        }
        else if (character === "'") {
            quote = "single";
            tokenStarted = true;
        }
        else if (character === '"') {
            quote = "double";
            tokenStarted = true;
        }
        else if (/\s/u.test(character)) {
            if (tokenStarted) {
                argv.push(token);
                token = "";
                tokenStarted = false;
            }
        }
        else {
            if (/[;&|<>`$]/u.test(character))
                throw unsafeCommand(command);
            token += character;
            tokenStarted = true;
        }
    }
    if (escaped || quote !== null) {
        throw new TypeError(`Declared validation command has an unterminated escape or quote: '${command}'.`);
    }
    if (tokenStarted)
        argv.push(token);
    if (argv.length === 0)
        throw new TypeError("Declared validation command cannot be empty.");
    const assignmentCount = argv.findIndex((entry) => !/^[A-Za-z_][A-Za-z0-9_]*=.*$/u.test(entry));
    if (assignmentCount === -1)
        throw new TypeError("Declared validation command cannot contain only environment assignments.");
    return assignmentCount > 0 ? ["env", ...argv] : argv;
}
function unsafeCommand(command) {
    return new TypeError(`Declared validation command requires shell evaluation and was rejected: '${command}'.`);
}
