"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandsService = void 0;
const jaroWinklerDistance = require("../vendor/jaro-winkler_distance");
const helpers = require("../helpers");
const os_1 = require("os");
const _ = require("lodash");
const yok_1 = require("../yok");
class CommandArgumentsValidationHelper {
    constructor(isValid, _remainingArguments) {
        this.isValid = isValid;
        this.remainingArguments = _remainingArguments.slice();
    }
}
class CommandsService {
    get currentCommandData() {
        return _.last(this.commands);
    }
    constructor($errors, $hooksService, $injector, $logger, $options, $staticConfig, $extensibilityService, $optionsTracker) {
        this.$errors = $errors;
        this.$hooksService = $hooksService;
        this.$injector = $injector;
        this.$logger = $logger;
        this.$options = $options;
        this.$staticConfig = $staticConfig;
        this.$extensibilityService = $extensibilityService;
        this.$optionsTracker = $optionsTracker;
        this.commands = [];
    }
    allCommands(opts) {
        const commands = this.$injector.getRegisteredCommandsNames(opts.includeDevCommands);
        return _.reject(commands, (command) => _.includes(command, "|"));
    }
    async executeCommandUnchecked(commandName, commandArguments) {
        this.commands.push({ commandName, commandArguments });
        const command = this.$injector.resolveCommand(commandName);
        if (command) {
            if (!this.$staticConfig.disableAnalytics &&
                !command.disableAnalytics &&
                !this.$options.disableAnalytics) {
                const analyticsService = this.$injector.resolve("analyticsService");
                await analyticsService.checkConsent();
                const beautifiedCommandName = this.beautifyCommandName(commandName).replace(/\|/g, " ");
                const googleAnalyticsPageData = {
                    googleAnalyticsDataType: "pageview",
                    path: beautifiedCommandName,
                    title: beautifiedCommandName,
                };
                await analyticsService.trackInGoogleAnalytics(googleAnalyticsPageData);
                await this.$optionsTracker.trackOptions(this.$options);
            }
            const shouldExecuteHooks = !this.$staticConfig.disableCommandHooks &&
                (command.enableHooks === undefined || command.enableHooks === true);
            if (shouldExecuteHooks) {
                const hierarchicalCommandName = this.$injector.buildHierarchicalCommand(commandName, commandArguments);
                if (hierarchicalCommandName) {
                    commandName = helpers.stringReplaceAll(hierarchicalCommandName.commandName, "|*", "-");
                    commandName = helpers.stringReplaceAll(commandName, "|", "-");
                }
                await this.$hooksService.executeBeforeHooks(commandName);
            }
            await command.execute(commandArguments);
            if (command.postCommandAction) {
                await command.postCommandAction(commandArguments);
            }
            if (shouldExecuteHooks) {
                await this.$hooksService.executeAfterHooks(commandName);
            }
            this.commands.pop();
            return true;
        }
        this.commands.pop();
        return false;
    }
    printHelpSuggestion(commandName) {
        const command = commandName
            ? helpers.stringReplaceAll(this.beautifyCommandName(commandName), "|", " ") + " "
            : "";
        const commandHelp = `ns ${command}--help`;
        this.$logger.printMarkdown(`__Run \`${commandHelp}\` for more information.__`);
        return;
    }
    async executeCommandAction(commandName, commandArguments, action) {
        return this.$errors.beginCommand(() => action.apply(this, [commandName, commandArguments]), () => this.printHelpSuggestion(commandName));
    }
    async tryExecuteCommandAction(commandName, commandArguments) {
        const command = this.$injector.resolveCommand(commandName);
        if (!command || !command.isHierarchicalCommand) {
            const dashedOptions = command ? command.dashedOptions : null;
            this.$options.validateOptions(dashedOptions);
        }
        return this.canExecuteCommand(commandName, commandArguments);
    }
    async tryExecuteCommand(commandName, commandArguments) {
        const canExecuteResult = await this.executeCommandAction(commandName, commandArguments, this.tryExecuteCommandAction);
        const canExecute = typeof canExecuteResult === "object"
            ? canExecuteResult.canExecute
            : canExecuteResult;
        if (canExecute) {
            await this.executeCommandAction(commandName, commandArguments, this.executeCommandUnchecked);
        }
        else {
            const command = this.$injector.resolveCommand(commandName);
            if (command) {
                let commandWithArgs = commandName;
                if (commandArguments && commandArguments.length) {
                    commandWithArgs += ` ${commandArguments.join(" ")}`;
                }
                this.$logger.error(`Command '${commandWithArgs}' cannot be executed.`);
                await this.printHelpSuggestion(commandName);
            }
        }
    }
    async canExecuteCommand(commandName, commandArguments, isDynamicCommand) {
        const command = this.$injector.resolveCommand(commandName);
        const beautifiedName = helpers.stringReplaceAll(commandName, "|", " ");
        if (command) {
            if (command.isDisabled) {
                this.$errors.fail("This command is not applicable to your environment.");
            }
            if (command.canExecute) {
                return await command.canExecute(commandArguments);
            }
            if (await this.$injector.isValidHierarchicalCommand(commandName, commandArguments)) {
                return true;
            }
            if (await this.validateCommandArguments(command, commandArguments)) {
                return true;
            }
            this.$errors.fail(`Unable to execute command '${beautifiedName}'.`);
            return false;
        }
        const commandInfo = {
            inputStrings: [commandName, ...commandArguments],
            commandDelimiter: "|",
            defaultCommandDelimiter: "|*",
        };
        const extensionData = await this.$extensibilityService.getExtensionNameWhereCommandIsRegistered(commandInfo);
        if (extensionData) {
            this.$logger.warn(extensionData.installationMessage);
        }
        else {
            this.$logger.error("Unknown command '%s'.", beautifiedName);
            await this.printHelpSuggestion();
            this.tryMatchCommand(commandName);
        }
        return false;
    }
    async validateMandatoryParams(commandArguments, mandatoryParams) {
        const commandArgsHelper = new CommandArgumentsValidationHelper(true, commandArguments);
        if (mandatoryParams.length > 0) {
            if (mandatoryParams.length > commandArguments.length) {
                const customErrorMessages = _.map(mandatoryParams, (mp) => mp.errorMessage);
                customErrorMessages.splice(0, 0, "You need to provide all the required parameters.");
                this.$errors.failWithHelp(customErrorMessages.join(os_1.EOL));
            }
            for (let mandatoryParamIndex = 0; mandatoryParamIndex < mandatoryParams.length; ++mandatoryParamIndex) {
                const mandatoryParam = mandatoryParams[mandatoryParamIndex];
                let argument = null;
                for (let remainingArgsIndex = 0; remainingArgsIndex < commandArgsHelper.remainingArguments.length; ++remainingArgsIndex) {
                    const c = commandArgsHelper.remainingArguments[remainingArgsIndex];
                    if (await mandatoryParam.validate(c)) {
                        argument = c;
                        break;
                    }
                }
                if (argument) {
                    helpers.remove(commandArgsHelper.remainingArguments, (arg) => arg === argument);
                }
                else {
                    this.$errors.failWithHelp("Missing mandatory parameter.");
                }
            }
        }
        return commandArgsHelper;
    }
    async validateCommandArguments(command, commandArguments) {
        const mandatoryParams = _.filter(command.allowedParameters, (param) => param.mandatory);
        const commandArgsHelper = await this.validateMandatoryParams(commandArguments, mandatoryParams);
        if (!commandArgsHelper.isValid) {
            return false;
        }
        if (!command.allowedParameters || command.allowedParameters.length === 0) {
            if (commandArguments.length > 0) {
                this.$errors.failWithHelp("This command doesn't accept parameters.");
            }
        }
        else {
            const unverifiedAllowedParams = command.allowedParameters.filter((param) => !param.mandatory);
            for (let remainingArgsIndex = 0; remainingArgsIndex < commandArgsHelper.remainingArguments.length; ++remainingArgsIndex) {
                const argument = commandArgsHelper.remainingArguments[remainingArgsIndex];
                let parameter = null;
                for (let unverifiedIndex = 0; unverifiedIndex < unverifiedAllowedParams.length; ++unverifiedIndex) {
                    const c = unverifiedAllowedParams[unverifiedIndex];
                    if (await c.validate(argument)) {
                        parameter = c;
                        break;
                    }
                }
                if (parameter) {
                    const index = unverifiedAllowedParams.indexOf(parameter);
                    unverifiedAllowedParams.splice(index, 1);
                }
                else {
                    this.$errors.failWithHelp(`The parameter ${argument} is not valid for this command.`);
                }
            }
        }
        return true;
    }
    tryMatchCommand(commandName) {
        const allCommands = this.allCommands({ includeDevCommands: false });
        let similarCommands = [];
        _.each(allCommands, (command) => {
            if (!this.$injector.isDefaultCommand(command)) {
                command = helpers.stringReplaceAll(command, "|", " ");
                const distance = jaroWinklerDistance(commandName, command);
                if (commandName.length > 3 && command.indexOf(commandName) !== -1) {
                    similarCommands.push({ rating: 1, name: command });
                }
                else if (distance >= 0.65) {
                    similarCommands.push({ rating: distance, name: command });
                }
            }
        });
        similarCommands = _.sortBy(similarCommands, (command) => {
            return -command.rating;
        }).slice(0, 5);
        if (similarCommands.length > 0) {
            const message = ["Did you mean?"];
            _.each(similarCommands, (command) => {
                message.push("\t" + command.name);
            });
            this.$logger.fatal(message.join("\n"));
        }
    }
    beautifyCommandName(commandName) {
        if (commandName.indexOf("*") > 0) {
            return commandName.substring(0, commandName.indexOf("|"));
        }
        return commandName;
    }
}
exports.CommandsService = CommandsService;
yok_1.injector.register("commandsService", CommandsService);
