"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LARGE_BUFFER = void 0;
exports.default = default_1;
exports.runCommands = runCommands;
exports.interpolateArgsIntoCommand = interpolateArgsIntoCommand;
const yargsParser = require("yargs-parser");
const is_tui_enabled_1 = require("../../tasks-runner/is-tui-enabled");
const pseudo_terminal_1 = require("../../tasks-runner/pseudo-terminal");
const utils_1 = require("../../tasks-runner/utils");
const noop_child_process_1 = require("../../tasks-runner/running-tasks/noop-child-process");
const running_tasks_1 = require("./running-tasks");
exports.LARGE_BUFFER = 1024 * 1000000;
const propKeys = [
    'command',
    'commands',
    'color',
    'no-color',
    'parallel',
    'no-parallel',
    'readyWhen',
    'cwd',
    'args',
    'envFile',
    '__unparsed__',
    'env',
    'usePty',
    'streamOutput',
    'verbose',
    'forwardAllArgs',
    'tty',
];
async function default_1(options, context) {
    const task = await runCommands(options, context);
    const results = await task.getResults();
    return {
        ...results,
        success: results.code === 0,
    };
}
async function runCommands(options, context, taskId) {
    const normalized = normalizeOptions(options);
    if (normalized.readyWhenStatus.length && !normalized.parallel) {
        throw new Error('ERROR: Bad executor config for run-commands - "readyWhen" can only be used when "parallel=true".');
    }
    if (options.commands.find((c) => c.prefix || c.prefixColor || c.color || c.bgColor) &&
        !options.parallel) {
        throw new Error('ERROR: Bad executor config for run-commands - "prefix", "prefixColor", "color" and "bgColor" can only be set when "parallel=true".');
    }
    // Handle empty commands array - return immediately with success
    if (normalized.commands.length === 0) {
        return new noop_child_process_1.NoopChildProcess({ code: 0, terminalOutput: '' });
    }
    const isSingleCommand = normalized.commands.length === 1;
    const usePseudoTerminal = (isSingleCommand || !options.parallel) && pseudo_terminal_1.PseudoTerminal.isSupported();
    const isSingleCommandAndCanUsePseudoTerminal = isSingleCommand &&
        usePseudoTerminal &&
        process.env.NX_NATIVE_COMMAND_RUNNER !== 'false' &&
        !normalized.commands[0].prefix &&
        normalized.usePty;
    const tuiEnabled = (0, is_tui_enabled_1.isTuiEnabled)();
    try {
        const resolvedTaskId = taskId ??
            (0, utils_1.createTaskId)(context.projectName, context.targetName, context.configurationName);
        const runningTask = isSingleCommandAndCanUsePseudoTerminal
            ? await (0, running_tasks_1.runSingleCommandWithPseudoTerminal)(normalized, context, resolvedTaskId)
            : options.parallel
                ? new running_tasks_1.ParallelRunningTasks(normalized, context, resolvedTaskId)
                : new running_tasks_1.SeriallyRunningTasks(normalized, context, tuiEnabled, resolvedTaskId);
        return runningTask;
    }
    catch (e) {
        if (process.env.NX_VERBOSE_LOGGING === 'true') {
            console.error(e);
        }
        throw new Error(`ERROR: Something went wrong in run-commands - ${e.message}`);
    }
}
function normalizeOptions(options) {
    if (options.readyWhen && typeof options.readyWhen === 'string') {
        options.readyWhenStatus = [
            { stringToMatch: options.readyWhen, found: false },
        ];
    }
    else {
        options.readyWhenStatus =
            options.readyWhen?.map((stringToMatch) => ({
                stringToMatch,
                found: false,
            })) ?? [];
    }
    if (options.command) {
        options.commands = [
            {
                command: Array.isArray(options.command)
                    ? options.command.join(' ')
                    : options.command,
            },
        ];
        options.parallel = options.readyWhenStatus?.length > 0;
    }
    else {
        options.commands = options.commands.map((c) => typeof c === 'string' ? { command: c } : c);
    }
    if (options.args && Array.isArray(options.args)) {
        options.args = options.args.join(' ');
    }
    const unparsedCommandArgs = yargsParser(options.__unparsed__, {
        configuration: {
            'parse-numbers': false,
            'parse-positional-numbers': false,
            'dot-notation': false,
            'camel-case-expansion': false,
        },
    });
    options.unknownOptions = Object.keys(options)
        .filter((p) => propKeys.indexOf(p) === -1 && unparsedCommandArgs[p] === undefined)
        .reduce((m, c) => ((m[c] = options[c]), m), {});
    options.parsedArgs = parseArgs(unparsedCommandArgs, options.unknownOptions, options.args);
    options.unparsedCommandArgs = unparsedCommandArgs;
    options.commands.forEach((c) => {
        c.command = interpolateArgsIntoCommand(c.command, options, c.forwardAllArgs ?? options.forwardAllArgs ?? true);
    });
    return options;
}
function interpolateArgsIntoCommand(command, opts, forwardAllArgs) {
    if (command.indexOf('{args.') > -1 && command.indexOf('{args}') > -1) {
        throw new Error('Command should not contain both {args} and {args.*} values. Please choose one to use.');
    }
    if (command.indexOf('{args.') > -1) {
        const regex = /{args\.([^}]+)}/g;
        return command.replace(regex, (_, group) => opts.parsedArgs[group] !== undefined ? opts.parsedArgs[group] : '');
    }
    else if (command.indexOf('{args}') > -1) {
        const regex = /{args}/g;
        const args = [
            ...unknownOptionsToArgsArray(opts),
            ...unparsedOptionsToArgsArray(opts),
        ];
        const argsString = `${args.join(' ')} ${opts.args ?? ''}`;
        return command.replace(regex, argsString);
    }
    else if (forwardAllArgs) {
        let args = '';
        if (Object.keys(opts.unknownOptions ?? {}).length > 0) {
            const unknownOptionsArgs = unknownOptionsToArgsArray(opts).join(' ');
            if (unknownOptionsArgs) {
                args += ` ${unknownOptionsArgs}`;
            }
        }
        if (opts.args) {
            args += ` ${opts.args}`;
        }
        if (opts.__unparsed__?.length > 0) {
            const filteredParsedOptions = unparsedOptionsToArgsArray(opts);
            if (filteredParsedOptions.length > 0) {
                args += ` ${filteredParsedOptions.join(' ')}`;
            }
        }
        return `${command}${args}`;
    }
    else {
        return command;
    }
}
function unknownOptionsToArgsArray(opts) {
    return Object.keys(opts.unknownOptions ?? {})
        .filter((k) => typeof opts.unknownOptions[k] !== 'object' &&
        opts.parsedArgs[k] === opts.unknownOptions[k])
        .map((k) => `--${k}=${opts.unknownOptions[k]}`)
        .map(wrapArgIntoQuotesIfNeeded);
}
function unparsedOptionsToArgsArray(opts) {
    const filteredParsedOptions = filterPropKeysFromUnParsedOptions(opts.__unparsed__, opts.parsedArgs);
    if (filteredParsedOptions.length > 0) {
        return filteredParsedOptions.map(wrapArgIntoQuotesIfNeeded);
    }
    return [];
}
function parseArgs(unparsedCommandArgs, unknownOptions, args) {
    if (!args) {
        return { ...unknownOptions, ...unparsedCommandArgs };
    }
    return {
        ...unknownOptions,
        ...yargsParser(args.replace(/(^"|"$)/g, ''), {
            configuration: { 'camel-case-expansion': true },
        }),
        ...unparsedCommandArgs,
    };
}
/**
 * This function filters out the prop keys from the unparsed options
 * @param __unparsed__ e.g. ['--prop1', 'value1', '--prop2=value2', '--args=test']
 * @param unparsedCommandArgs e.g. { prop1: 'value1', prop2: 'value2', args: 'test'}
 * @returns filtered options that are not part of the propKeys array e.g. ['--prop1', 'value1', '--prop2=value2']
 */
function filterPropKeysFromUnParsedOptions(__unparsed__, parseArgs = {}) {
    const parsedOptions = [];
    for (let index = 0; index < __unparsed__.length; index++) {
        const element = __unparsed__[index];
        if (element.startsWith('--')) {
            const key = element.replace('--', '');
            if (element.includes('=')) {
                // key can be in the format of --key=value or --key.subkey=value (e.g. env.foo=bar)
                if (!propKeys.includes(key.split('=')[0].split('.')[0])) {
                    // check if the key is part of the propKeys array
                    parsedOptions.push(element);
                }
            }
            else {
                // check if the next element is a value for the key
                if (propKeys.includes(key)) {
                    if (index + 1 < __unparsed__.length &&
                        parseArgs[key] &&
                        __unparsed__[index + 1].toString() === parseArgs[key].toString()) {
                        index++; // skip the next element
                    }
                }
                else {
                    parsedOptions.push(element);
                }
            }
        }
        else {
            parsedOptions.push(element);
        }
    }
    return parsedOptions;
}
function wrapArgIntoQuotesIfNeeded(arg) {
    if (arg.includes('=')) {
        const [key, value] = arg.split('=');
        if (key.startsWith('--') &&
            value.includes(' ') &&
            !(value[0] === "'" || value[0] === '"')) {
            return `${key}="${value}"`;
        }
        return arg;
    }
    else if (arg.includes(' ') && !(arg[0] === "'" || arg[0] === '"')) {
        return `"${arg}"`;
    }
    else {
        return arg;
    }
}
