"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
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 {
    constructor($analyticsSettingsService, $errors, $hooksService, $injector, $logger, $options, $staticConfig, $extensibilityService, $optionsTracker) {
        this.$analyticsSettingsService = $analyticsSettingsService;
        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 = [];
    }
    get currentCommandData() {
        return _.last(this.commands);
    }
    allCommands(opts) {
        const commands = this.$injector.getRegisteredCommandsNames(opts.includeDevCommands);
        return _.reject(commands, (command) => _.includes(command, "|"));
    }
    executeCommandUnchecked(commandName, commandArguments) {
        return __awaiter(this, void 0, void 0, function* () {
            this.commands.push({ commandName, commandArguments });
            const command = this.$injector.resolveCommand(commandName);
            if (command) {
                if (!this.$staticConfig.disableAnalytics && !command.disableAnalytics) {
                    const analyticsService = this.$injector.resolve("analyticsService");
                    yield analyticsService.checkConsent();
                    const beautifiedCommandName = this.beautifyCommandName(commandName).replace(/\|/g, " ");
                    const googleAnalyticsPageData = {
                        googleAnalyticsDataType: "pageview",
                        path: beautifiedCommandName,
                        title: beautifiedCommandName,
                    };
                    const playgrounInfo = yield this.$analyticsSettingsService.getPlaygroundInfo(null);
                    if (playgrounInfo && playgrounInfo.id) {
                        googleAnalyticsPageData.customDimensions = {
                            ["cd7"]: playgrounInfo.id,
                            ["cd8"]: playgrounInfo.usedTutorial.toString(),
                        };
                    }
                    yield analyticsService.trackInGoogleAnalytics(googleAnalyticsPageData);
                    yield 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, "|", "-");
                    }
                    yield this.$hooksService.executeBeforeHooks(commandName);
                }
                yield command.execute(commandArguments);
                if (command.postCommandAction) {
                    yield command.postCommandAction(commandArguments);
                }
                if (shouldExecuteHooks) {
                    yield 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;
    }
    executeCommandAction(commandName, commandArguments, action) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.$errors.beginCommand(() => action.apply(this, [commandName, commandArguments]), () => this.printHelpSuggestion(commandName));
        });
    }
    tryExecuteCommandAction(commandName, commandArguments) {
        return __awaiter(this, void 0, void 0, function* () {
            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);
        });
    }
    tryExecuteCommand(commandName, commandArguments) {
        return __awaiter(this, void 0, void 0, function* () {
            const canExecuteResult = yield this.executeCommandAction(commandName, commandArguments, this.tryExecuteCommandAction);
            const canExecute = typeof canExecuteResult === "object"
                ? canExecuteResult.canExecute
                : canExecuteResult;
            if (canExecute) {
                yield 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.`);
                    yield this.printHelpSuggestion(commandName);
                }
            }
        });
    }
    canExecuteCommand(commandName, commandArguments, isDynamicCommand) {
        return __awaiter(this, void 0, void 0, function* () {
            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 yield command.canExecute(commandArguments);
                }
                if (yield this.$injector.isValidHierarchicalCommand(commandName, commandArguments)) {
                    return true;
                }
                if (yield 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 = yield this.$extensibilityService.getExtensionNameWhereCommandIsRegistered(commandInfo);
            if (extensionData) {
                this.$logger.warn(extensionData.installationMessage);
            }
            else {
                this.$logger.error("Unknown command '%s'.", beautifiedName);
                yield this.printHelpSuggestion();
                this.tryMatchCommand(commandName);
            }
            return false;
        });
    }
    validateMandatoryParams(commandArguments, mandatoryParams) {
        return __awaiter(this, void 0, void 0, function* () {
            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 (yield mandatoryParam.validate(c)) {
                            argument = c;
                            break;
                        }
                    }
                    if (argument) {
                        helpers.remove(commandArgsHelper.remainingArguments, (arg) => arg === argument);
                    }
                    else {
                        this.$errors.failWithHelp("Missing mandatory parameter.");
                    }
                }
            }
            return commandArgsHelper;
        });
    }
    validateCommandArguments(command, commandArguments) {
        return __awaiter(this, void 0, void 0, function* () {
            const mandatoryParams = _.filter(command.allowedParameters, (param) => param.mandatory);
            const commandArgsHelper = yield 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 (yield 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"));
        }
    }
    completeCommand() {
        var e_1, _a;
        return __awaiter(this, void 0, void 0, function* () {
            const tabtab = require("tabtab");
            const completeCallback = (err, data) => {
                if (err || !data) {
                    return;
                }
                const commands = this.$injector.getRegisteredCommandsNames(false);
                const splittedLine = data.line.split(/[ ]+/);
                const line = _.filter(splittedLine, (w) => w !== "");
                let commandName = line[line.length - 2];
                const childrenCommands = this.$injector.getChildrenCommandsNames(commandName);
                if (data.last && _.startsWith(data.last, "--")) {
                    return tabtab.log(_.keys(this.$options.options), data, "--");
                }
                if (data.last && _.startsWith(data.last, "-")) {
                    return tabtab.log(this.$options.shorthands, data, "-");
                }
                if (data.words === 1) {
                    const allCommands = this.allCommands({ includeDevCommands: false });
                    return tabtab.log(allCommands, data);
                }
                if (data.words >= 2) {
                    if (data.words !== line.length) {
                        commandName = `${line[data.words - 2]}|${line[data.words - 1]}`;
                    }
                    else {
                        commandName = `${line[line.length - 1]}`;
                    }
                }
                const command = this.$injector.resolveCommand(commandName);
                if (command) {
                    const completionData = command.completionData;
                    if (completionData) {
                        return tabtab.log(completionData, data);
                    }
                    else {
                        return this.logChildrenCommandsNames(commandName, commands, tabtab, data);
                    }
                }
                else if (childrenCommands) {
                    let nonDefaultSubCommands = _.reject(childrenCommands, (children) => children[0] === "*");
                    let sanitizedChildrenCommands = [];
                    if (data.words !== line.length) {
                        sanitizedChildrenCommands = nonDefaultSubCommands.map((commandToMap) => {
                            const pipePosition = commandToMap.indexOf("|");
                            return commandToMap.substring(0, pipePosition !== -1 ? pipePosition : commandToMap.length);
                        });
                    }
                    else {
                        nonDefaultSubCommands = nonDefaultSubCommands.filter((commandNameToFilter) => commandNameToFilter.indexOf("|") !== -1);
                        sanitizedChildrenCommands = nonDefaultSubCommands.map((commandToMap) => {
                            const pipePosition = commandToMap.lastIndexOf("|");
                            return commandToMap.substring(pipePosition !== -1 ? pipePosition + 1 : 0, commandToMap.length);
                        });
                    }
                    return tabtab.log(sanitizedChildrenCommands, data);
                }
                else {
                    return this.logChildrenCommandsNames(commandName, commands, tabtab, data);
                }
            };
            const aliases = ["ns", "nsc", "tns", "nativescript"];
            try {
                for (var aliases_1 = __asyncValues(aliases), aliases_1_1; aliases_1_1 = yield aliases_1.next(), !aliases_1_1.done;) {
                    const alias = aliases_1_1.value;
                    yield tabtab.complete(alias, completeCallback);
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (aliases_1_1 && !aliases_1_1.done && (_a = aliases_1.return)) yield _a.call(aliases_1);
                }
                finally { if (e_1) throw e_1.error; }
            }
            return true;
        });
    }
    beautifyCommandName(commandName) {
        if (commandName.indexOf("*") > 0) {
            return commandName.substr(0, commandName.indexOf("|"));
        }
        return commandName;
    }
    logChildrenCommandsNames(commandName, commands, tabtab, data) {
        const matchingCommands = commands
            .filter((commandToFilter) => {
            return (commandToFilter.indexOf(commandName + "|") !== -1 &&
                commandToFilter !== commandName);
        })
            .map((commandToMap) => {
            const commandResult = commandToMap.replace(commandName + "|", "");
            return commandResult.substring(0, commandResult.indexOf("|") !== -1
                ? commandResult.indexOf("|")
                : commandResult.length);
        });
        return tabtab.log(matchingCommands, data);
    }
}
exports.CommandsService = CommandsService;
yok_1.injector.register("commandsService", CommandsService);
