"use strict";
/**
 * @license
 * Copyright Google LLC 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.dev/license
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandModuleError = exports.CommandModule = exports.CommandScope = void 0;
const core_1 = require("@angular-devkit/core");
const fs_1 = require("fs");
const path = __importStar(require("path"));
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const analytics_1 = require("../analytics/analytics");
const analytics_collector_1 = require("../analytics/analytics-collector");
const analytics_parameters_1 = require("../analytics/analytics-parameters");
const completion_1 = require("../utilities/completion");
const memoize_1 = require("../utilities/memoize");
var CommandScope;
(function (CommandScope) {
    /** Command can only run inside an Angular workspace. */
    CommandScope[CommandScope["In"] = 0] = "In";
    /** Command can only run outside an Angular workspace. */
    CommandScope[CommandScope["Out"] = 1] = "Out";
    /** Command can run inside and outside an Angular workspace. */
    CommandScope[CommandScope["Both"] = 2] = "Both";
})(CommandScope || (exports.CommandScope = CommandScope = {}));
let CommandModule = (() => {
    let _instanceExtraInitializers = [];
    let _getAnalytics_decorators;
    return class CommandModule {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            _getAnalytics_decorators = [memoize_1.memoize];
            __esDecorate(this, null, _getAnalytics_decorators, { kind: "method", name: "getAnalytics", static: false, private: false, access: { has: obj => "getAnalytics" in obj, get: obj => obj.getAnalytics }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        context = __runInitializers(this, _instanceExtraInitializers);
        shouldReportAnalytics = true;
        scope = CommandScope.Both;
        optionsWithAnalytics = new Map();
        constructor(context) {
            this.context = context;
        }
        /**
         * Description object which contains the long command descroption.
         * This is used to generate JSON help wich is used in AIO.
         *
         * `false` will result in a hidden command.
         */
        get fullDescribe() {
            return this.describe === false
                ? false
                : {
                    describe: this.describe,
                    ...(this.longDescriptionPath
                        ? {
                            longDescriptionRelativePath: path
                                .relative(path.join(__dirname, '../../../../'), this.longDescriptionPath)
                                .replace(/\\/g, path.posix.sep),
                            longDescription: (0, fs_1.readFileSync)(this.longDescriptionPath, 'utf8').replace(/\r\n/g, '\n'),
                        }
                        : {}),
                };
        }
        get commandName() {
            return this.command.split(' ', 1)[0];
        }
        async handler(args) {
            const { _, $0, ...options } = args;
            // Camelize options as yargs will return the object in kebab-case when camel casing is disabled.
            const camelCasedOptions = {};
            for (const [key, value] of Object.entries(options)) {
                camelCasedOptions[helpers_1.Parser.camelCase(key)] = value;
            }
            // Set up autocompletion if appropriate.
            const autocompletionExitCode = await (0, completion_1.considerSettingUpAutocompletion)(this.commandName, this.context.logger);
            if (autocompletionExitCode !== undefined) {
                process.exitCode = autocompletionExitCode;
                return;
            }
            // Gather and report analytics.
            const analytics = await this.getAnalytics();
            const stopPeriodicFlushes = analytics && analytics.periodFlush();
            let exitCode;
            try {
                if (analytics) {
                    this.reportCommandRunAnalytics(analytics);
                    this.reportWorkspaceInfoAnalytics(analytics);
                }
                exitCode = await this.run(camelCasedOptions);
            }
            catch (e) {
                if (e instanceof core_1.schema.SchemaValidationException) {
                    this.context.logger.fatal(`Error: ${e.message}`);
                    exitCode = 1;
                }
                else {
                    throw e;
                }
            }
            finally {
                await stopPeriodicFlushes?.();
                if (typeof exitCode === 'number' && exitCode > 0) {
                    process.exitCode = exitCode;
                }
            }
        }
        async getAnalytics() {
            if (!this.shouldReportAnalytics) {
                return undefined;
            }
            const userId = await (0, analytics_1.getAnalyticsUserId)(this.context, 
            // Don't prompt on `ng update`, 'ng version' or `ng analytics`.
            ['version', 'update', 'analytics'].includes(this.commandName));
            return userId ? new analytics_collector_1.AnalyticsCollector(this.context, userId) : undefined;
        }
        /**
         * Adds schema options to a command also this keeps track of options that are required for analytics.
         * **Note:** This method should be called from the command bundler method.
         */
        addSchemaOptionsToCommand(localYargs, options) {
            const booleanOptionsWithNoPrefix = new Set();
            for (const option of options) {
                const { default: defaultVal, positional, deprecated, description, alias, userAnalytics, type, hidden, name, choices, } = option;
                const sharedOptions = {
                    alias,
                    hidden,
                    description,
                    deprecated,
                    choices,
                    // This should only be done when `--help` is used otherwise default will override options set in angular.json.
                    ...(this.context.args.options.help ? { default: defaultVal } : {}),
                };
                let dashedName = core_1.strings.dasherize(name);
                // Handle options which have been defined in the schema with `no` prefix.
                if (type === 'boolean' && dashedName.startsWith('no-')) {
                    dashedName = dashedName.slice(3);
                    booleanOptionsWithNoPrefix.add(dashedName);
                }
                if (positional === undefined) {
                    localYargs = localYargs.option(dashedName, {
                        type,
                        ...sharedOptions,
                    });
                }
                else {
                    localYargs = localYargs.positional(dashedName, {
                        type: type === 'array' || type === 'count' ? 'string' : type,
                        ...sharedOptions,
                    });
                }
                // Record option of analytics.
                if (userAnalytics !== undefined) {
                    this.optionsWithAnalytics.set(name, userAnalytics);
                }
            }
            // Handle options which have been defined in the schema with `no` prefix.
            if (booleanOptionsWithNoPrefix.size) {
                localYargs.middleware((options) => {
                    for (const key of booleanOptionsWithNoPrefix) {
                        if (key in options) {
                            options[`no-${key}`] = !options[key];
                            delete options[key];
                        }
                    }
                }, false);
            }
            return localYargs;
        }
        getWorkspaceOrThrow() {
            const { workspace } = this.context;
            if (!workspace) {
                throw new CommandModuleError('A workspace is required for this command.');
            }
            return workspace;
        }
        /**
         * Flush on an interval (if the event loop is waiting).
         *
         * @returns a method that when called will terminate the periodic
         * flush and call flush one last time.
         */
        getAnalyticsParameters(options) {
            const parameters = {};
            const validEventCustomDimensionAndMetrics = new Set([
                ...Object.values(analytics_parameters_1.EventCustomDimension),
                ...Object.values(analytics_parameters_1.EventCustomMetric),
            ]);
            for (const [name, ua] of this.optionsWithAnalytics) {
                const value = options[name];
                if ((typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') &&
                    validEventCustomDimensionAndMetrics.has(ua)) {
                    parameters[ua] = value;
                }
            }
            return parameters;
        }
        reportCommandRunAnalytics(analytics) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const internalMethods = yargs_1.default.getInternalMethods();
            // $0 generate component [name] -> generate_component
            // $0 add <collection> -> add
            const fullCommand = internalMethods.getUsageInstance().getUsage()[0][0]
                .split(' ')
                .filter((x) => {
                const code = x.charCodeAt(0);
                return code >= 97 && code <= 122;
            })
                .join('_');
            analytics.reportCommandRunEvent(fullCommand);
        }
        reportWorkspaceInfoAnalytics(analytics) {
            const { workspace } = this.context;
            if (!workspace) {
                return;
            }
            let applicationProjectsCount = 0;
            let librariesProjectsCount = 0;
            for (const project of workspace.projects.values()) {
                switch (project.extensions['projectType']) {
                    case 'application':
                        applicationProjectsCount++;
                        break;
                    case 'library':
                        librariesProjectsCount++;
                        break;
                }
            }
            analytics.reportWorkspaceInfoEvent({
                [analytics_parameters_1.EventCustomMetric.AllProjectsCount]: librariesProjectsCount + applicationProjectsCount,
                [analytics_parameters_1.EventCustomMetric.ApplicationProjectsCount]: applicationProjectsCount,
                [analytics_parameters_1.EventCustomMetric.LibraryProjectsCount]: librariesProjectsCount,
            });
        }
    };
})();
exports.CommandModule = CommandModule;
/**
 * Creates an known command module error.
 * This is used so during executation we can filter between known validation error and real non handled errors.
 */
class CommandModuleError extends Error {
}
exports.CommandModuleError = CommandModuleError;
