"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
const core_1 = require("@angular-devkit/core");
const debug = require("debug");
const fs_1 = require("fs");
const path_1 = require("path");
const json_schema_1 = require("../utilities/json-schema");
const analytics_1 = require("./analytics");
const command_1 = require("./command");
const parser = require("./parser");
const analyticsDebug = debug('ng:analytics:commands');
// NOTE: Update commands.json if changing this.  It's still deep imported in one CI validation
const standardCommands = {
    'add': '../commands/add.json',
    'analytics': '../commands/analytics.json',
    'build': '../commands/build.json',
    'deploy': '../commands/deploy.json',
    'config': '../commands/config.json',
    'doc': '../commands/doc.json',
    'e2e': '../commands/e2e.json',
    'make-this-awesome': '../commands/easter-egg.json',
    'generate': '../commands/generate.json',
    'get': '../commands/deprecated.json',
    'set': '../commands/deprecated.json',
    'help': '../commands/help.json',
    'lint': '../commands/lint.json',
    'new': '../commands/new.json',
    'run': '../commands/run.json',
    'serve': '../commands/serve.json',
    'test': '../commands/test.json',
    'update': '../commands/update.json',
    'version': '../commands/version.json',
    'xi18n': '../commands/xi18n.json',
};
/**
 * Create the analytics instance.
 * @private
 */
async function _createAnalytics() {
    const config = analytics_1.getGlobalAnalytics();
    const maybeSharedAnalytics = analytics_1.getSharedAnalytics();
    if (config && maybeSharedAnalytics) {
        return new core_1.analytics.MultiAnalytics([config, maybeSharedAnalytics]);
    }
    else if (config) {
        return config;
    }
    else if (maybeSharedAnalytics) {
        return maybeSharedAnalytics;
    }
    else {
        return new core_1.analytics.NoopAnalytics();
    }
}
async function loadCommandDescription(name, path, registry) {
    const schemaPath = path_1.resolve(__dirname, path);
    const schemaContent = fs_1.readFileSync(schemaPath, 'utf-8');
    const schema = core_1.json.parseJson(schemaContent, core_1.JsonParseMode.Loose, { path: schemaPath });
    if (!core_1.isJsonObject(schema)) {
        throw new Error('Invalid command JSON loaded from ' + JSON.stringify(schemaPath));
    }
    return json_schema_1.parseJsonSchemaToCommandDescription(name, schemaPath, registry, schema);
}
/**
 * Run a command.
 * @param args Raw unparsed arguments.
 * @param logger The logger to use.
 * @param workspace Workspace information.
 * @param commands The map of supported commands.
 * @param options Additional options.
 */
async function runCommand(args, logger, workspace, commands = standardCommands, options = {}) {
    // This registry is exclusively used for flattening schemas, and not for validating.
    const registry = new core_1.schema.CoreSchemaRegistry([]);
    registry.registerUriHandler((uri) => {
        if (uri.startsWith('ng-cli://')) {
            const content = fs_1.readFileSync(path_1.join(__dirname, '..', uri.substr('ng-cli://'.length)), 'utf-8');
            return Promise.resolve(JSON.parse(content));
        }
        else {
            return null;
        }
    });
    let commandName = undefined;
    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if (!arg.startsWith('-')) {
            commandName = arg;
            args.splice(i, 1);
            break;
        }
    }
    let description = null;
    // if no commands were found, use `help`.
    if (!commandName) {
        if (args.length === 1 && args[0] === '--version') {
            commandName = 'version';
        }
        else {
            commandName = 'help';
        }
        if (!(commandName in commands)) {
            logger.error(core_1.tags.stripIndent `
          The "${commandName}" command seems to be disabled.
          This is an issue with the CLI itself. If you see this comment, please report it and
          provide your repository.
        `);
            return 1;
        }
    }
    if (commandName in commands) {
        description = await loadCommandDescription(commandName, commands[commandName], registry);
    }
    else {
        const commandNames = Object.keys(commands);
        // Optimize loading for common aliases
        if (commandName.length === 1) {
            commandNames.sort((a, b) => {
                const aMatch = a[0] === commandName;
                const bMatch = b[0] === commandName;
                if (aMatch && !bMatch) {
                    return -1;
                }
                else if (!aMatch && bMatch) {
                    return 1;
                }
                else {
                    return 0;
                }
            });
        }
        for (const name of commandNames) {
            const aliasDesc = await loadCommandDescription(name, commands[name], registry);
            const aliases = aliasDesc.aliases;
            if (aliases && aliases.some(alias => alias === commandName)) {
                commandName = name;
                description = aliasDesc;
                break;
            }
        }
    }
    if (!description) {
        const commandsDistance = {};
        const name = commandName;
        const allCommands = Object.keys(commands).sort((a, b) => {
            if (!(a in commandsDistance)) {
                commandsDistance[a] = core_1.strings.levenshtein(a, name);
            }
            if (!(b in commandsDistance)) {
                commandsDistance[b] = core_1.strings.levenshtein(b, name);
            }
            return commandsDistance[a] - commandsDistance[b];
        });
        logger.error(core_1.tags.stripIndent `
        The specified command ("${commandName}") is invalid. For a list of available options,
        run "ng help".

        Did you mean "${allCommands[0]}"?
    `);
        return 1;
    }
    try {
        const parsedOptions = parser.parseArguments(args, description.options, logger);
        command_1.Command.setCommandMap(async () => {
            const map = {};
            for (const [name, path] of Object.entries(commands)) {
                map[name] = await loadCommandDescription(name, path, registry);
            }
            return map;
        });
        const analytics = options.analytics || await _createAnalytics();
        const context = { workspace, analytics };
        const command = new description.impl(context, description, logger);
        // Flush on an interval (if the event loop is waiting).
        let analyticsFlushPromise = Promise.resolve();
        setInterval(() => {
            analyticsFlushPromise = analyticsFlushPromise.then(() => analytics.flush());
        }, 1000);
        const result = await command.validateAndRun(parsedOptions);
        // Flush one last time.
        await analyticsFlushPromise.then(() => analytics.flush());
        return result;
    }
    catch (e) {
        if (e instanceof parser.ParseArgumentException) {
            logger.fatal('Cannot parse arguments. See below for the reasons.');
            logger.fatal('    ' + e.comments.join('\n    '));
            return 1;
        }
        else {
            throw e;
        }
    }
}
exports.runCommand = runCommand;
