"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DoctorService = void 0;
const os_1 = require("os");
const path = require("path");
const _ = require("lodash");
const helpers = require("../common/helpers");
const decorators_1 = require("../common/decorators");
const constants_1 = require("../constants");
const doctor_1 = require("@nativescript/doctor");
const yok_1 = require("../common/yok");
class DoctorService {
    constructor($analyticsService, $hostInfo, $logger, $childProcess, $injector, $projectDataService, $fs, $terminalSpinnerService, $versionsService, $settingsService) {
        this.$analyticsService = $analyticsService;
        this.$hostInfo = $hostInfo;
        this.$logger = $logger;
        this.$childProcess = $childProcess;
        this.$injector = $injector;
        this.$projectDataService = $projectDataService;
        this.$fs = $fs;
        this.$terminalSpinnerService = $terminalSpinnerService;
        this.$versionsService = $versionsService;
        this.$settingsService = $settingsService;
    }
    get jsonFileSettingsPath() {
        return path.join(this.$settingsService.getProfileDir(), "doctor-cache.json");
    }
    get $jsonFileSettingsService() {
        return this.$injector.resolve("jsonFileSettingsService", { jsonFileSettingsPath: this.jsonFileSettingsPath });
    }
    printWarnings(configOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            configOptions = configOptions || {};
            const getInfosData = {
                projectDir: configOptions.projectDir,
                androidRuntimeVersion: configOptions.runtimeVersion,
            };
            const infos = yield this.$terminalSpinnerService.execute({
                text: `Getting environment information ${os_1.EOL}`,
            }, () => this.getInfos({ forceCheck: configOptions.forceCheck }, getInfosData));
            const warnings = infos.filter((info) => info.type === doctor_1.constants.WARNING_TYPE_NAME);
            const hasWarnings = warnings.length > 0;
            const hasAndroidWarnings = warnings.filter((warning) => _.includes(warning.platforms, doctor_1.constants.ANDROID_PLATFORM_NAME)).length > 0;
            if (hasAndroidWarnings) {
                this.printPackageManagerTip();
            }
            if (!configOptions || configOptions.trackResult) {
            }
            if (hasWarnings) {
                this.$logger.info("There seem to be issues with your configuration.");
                this.$fs.deleteFile(this.jsonFileSettingsPath);
            }
            else {
                this.$logger.info("No issues were detected.".bold);
                yield this.$jsonFileSettingsService.saveSetting(this.getKeyForConfiguration(getInfosData), infos);
                this.printInfosCore(infos);
            }
            try {
                yield this.$versionsService.printVersionsInformation();
            }
            catch (err) {
                this.$logger.error("Cannot get the latest versions information from npm. Please try again later.");
            }
            this.checkForDeprecatedShortImportsInAppDir(configOptions.projectDir);
            yield this.$injector
                .resolve("platformEnvironmentRequirements")
                .checkEnvironmentRequirements({
                platform: null,
                projectDir: configOptions.projectDir,
                runtimeVersion: configOptions.runtimeVersion,
                options: configOptions.options,
            });
        });
    }
    runSetupScript() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                action: "Run Setup Script",
                additionalData: "Starting",
            });
            if (this.$hostInfo.isLinux) {
                yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                    action: "Run Setup Script",
                    additionalData: "Skipped as OS is Linux",
                });
                return;
            }
            this.$logger.info("Running the setup script to try and automatically configure your environment.");
            if (this.$hostInfo.isDarwin) {
                yield this.runSetupScriptCore(DoctorService.DarwinSetupScriptLocation, []);
            }
            if (this.$hostInfo.isWindows) {
                yield this.runSetupScriptCore(DoctorService.WindowsSetupScriptExecutable, DoctorService.WindowsSetupScriptArguments);
            }
            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                action: "Run Setup Script",
                additionalData: "Finished",
            });
        });
    }
    canExecuteLocalBuild(configuration) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                action: "Check Local Build Setup",
                additionalData: "Starting",
            });
            const sysInfoConfig = {
                platform: configuration.platform,
                projectDir: configuration.projectDir,
                androidRuntimeVersion: configuration.runtimeVersion,
            };
            const infos = yield this.getInfos({ forceCheck: configuration && configuration.forceCheck }, sysInfoConfig);
            const warnings = this.filterInfosByType(infos, doctor_1.constants.WARNING_TYPE_NAME);
            const hasWarnings = warnings.length > 0;
            if (hasWarnings) {
                this.$fs.deleteFile(this.jsonFileSettingsPath);
                yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                    action: "Check Local Build Setup",
                    additionalData: `Warnings:${warnings.map((w) => w.message).join("__")}`,
                });
                this.printInfosCore(infos);
            }
            else {
                infos.map((info) => this.$logger.trace(info.message));
                yield this.$jsonFileSettingsService.saveSetting(this.getKeyForConfiguration(sysInfoConfig), infos);
            }
            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                action: "Check Local Build Setup",
                additionalData: `Finished: Is setup correct: ${!hasWarnings}`,
            });
            return !hasWarnings;
        });
    }
    checkForDeprecatedShortImportsInAppDir(projectDir) {
        if (projectDir) {
            try {
                const files = this.$projectDataService.getAppExecutableFiles(projectDir);
                const shortImports = this.getDeprecatedShortImportsInFiles(files, projectDir);
                if (shortImports.length) {
                    this.$logger.printMarkdown("Detected short imports in your application. Please note that `short imports are deprecated` since NativeScript 5.2.0. More information can be found in this blogpost https://www.nativescript.org/blog/say-goodbye-to-short-imports-in-nativescript");
                    shortImports.forEach((shortImport) => {
                        this.$logger.printMarkdown(`In file \`${shortImport.file}\` line \`${shortImport.line}\` is short import. Add \`tns-core-modules/\` in front of the required/imported module.`);
                    });
                }
            }
            catch (err) {
                this.$logger.trace(`Unable to validate if project has short imports. Error is`, err);
            }
        }
    }
    getDeprecatedShortImportsInFiles(files, projectDir) {
        const shortImportRegExp = this.getShortImportRegExp(projectDir);
        const shortImports = [];
        for (const file of files) {
            const fileContent = this.$fs.readText(file);
            const strippedComments = helpers.stripComments(fileContent);
            const linesToCheck = _.flatten(strippedComments.split(/\r?\n/).map((line) => line.split(";")));
            const linesWithRequireStatements = linesToCheck.filter((line) => /\btns-core-modules\b/.exec(line) === null &&
                (/\bimport\b/.exec(line) || /\brequire\b/.exec(line)));
            for (const line of linesWithRequireStatements) {
                const matches = line.match(shortImportRegExp);
                if (matches && matches.length) {
                    shortImports.push({ file, line });
                }
            }
        }
        return shortImports;
    }
    getShortImportRegExp(projectDir) {
        const pathToTnsCoreModules = path.join(projectDir, constants_1.NODE_MODULES_FOLDER_NAME, constants_1.TNS_CORE_MODULES_NAME);
        const coreModulesSubDirs = this.$fs
            .readDirectory(pathToTnsCoreModules)
            .filter((entry) => this.$fs
            .getFsStats(path.join(pathToTnsCoreModules, entry))
            .isDirectory());
        const stringRegularExpressionsPerDir = coreModulesSubDirs.map((c) => {
            const subDirPart = `[\"\']${c}[\"\'/]`;
            return `(\\brequire\\s*?\\(\\s*?${subDirPart})|(\\bimport\\b.*?from\\s*?${subDirPart})`;
        });
        return new RegExp(stringRegularExpressionsPerDir.join("|"), "g");
    }
    runSetupScriptCore(executablePath, setupScriptArgs) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.$childProcess.spawnFromEvent(executablePath, setupScriptArgs, "close", { stdio: "inherit" });
        });
    }
    printPackageManagerTip() {
        if (this.$hostInfo.isWindows) {
            this.$logger.info("TIP: To avoid setting up the necessary environment variables, you can use the chocolatey package manager to install the Android SDK and its dependencies." +
                os_1.EOL);
        }
        else if (this.$hostInfo.isDarwin) {
            this.$logger.info("TIP: To avoid setting up the necessary environment variables, you can use the Homebrew package manager to install the Android SDK and its dependencies." +
                os_1.EOL);
        }
    }
    printInfosCore(infos) {
        if (!helpers.isInteractive()) {
            infos.map((info) => {
                let message = info.message;
                if (info.type === doctor_1.constants.WARNING_TYPE_NAME) {
                    message = `WARNING: ${info.message.yellow} ${os_1.EOL} ${info.additionalInformation} ${os_1.EOL}`;
                }
                this.$logger.info(message);
            });
        }
        infos
            .filter((info) => info.type === doctor_1.constants.INFO_TYPE_NAME)
            .map((info) => {
            const spinner = this.$terminalSpinnerService.createSpinner();
            spinner.text = info.message;
            spinner.succeed();
        });
        infos
            .filter((info) => info.type === doctor_1.constants.WARNING_TYPE_NAME)
            .map((info) => {
            const spinner = this.$terminalSpinnerService.createSpinner();
            spinner.text = `${info.message.yellow} ${os_1.EOL} ${info.additionalInformation} ${os_1.EOL}`;
            spinner.fail();
        });
    }
    filterInfosByType(infos, type) {
        return infos.filter((info) => info.type === type);
    }
    getKeyForConfiguration(sysInfoConfig) {
        const nativeScriptData = sysInfoConfig &&
            sysInfoConfig.projectDir &&
            JSON.stringify(this.$fs.readJson(path.join(sysInfoConfig.projectDir, "package.json"))
                .nativescript);
        const delimiter = "__";
        const key = [
            JSON.stringify(sysInfoConfig),
            process.env.ANDROID_HOME,
            process.env.JAVA_HOME,
            process.env["CommonProgramFiles(x86)"],
            process.env["CommonProgramFiles"],
            process.env.PROCESSOR_ARCHITEW6432,
            process.env.ProgramFiles,
            process.env["ProgramFiles(x86)"],
            nativeScriptData,
        ]
            .filter((a) => !!a)
            .join(delimiter);
        const data = helpers.getHash(key, { algorithm: "md5" });
        return data;
    }
    getInfos(cacheConfig, sysInfoConfig) {
        return __awaiter(this, void 0, void 0, function* () {
            const key = this.getKeyForConfiguration(sysInfoConfig);
            const infosFromCache = cacheConfig.forceCheck
                ? null
                : yield this.$jsonFileSettingsService.getSettingValue(key);
            this.$logger.trace(`getInfos cacheConfig options:`, cacheConfig, " current info from cache: ", infosFromCache);
            const infos = infosFromCache || (yield doctor_1.doctor.getInfos(sysInfoConfig));
            return infos;
        });
    }
}
DoctorService.DarwinSetupScriptLocation = path.join(__dirname, "..", "..", "setup", "mac-startup-shell-script.sh");
DoctorService.WindowsSetupScriptExecutable = "powershell.exe";
DoctorService.WindowsSetupScriptArguments = [
    "start-process",
    "-FilePath",
    "PowerShell.exe",
    "-NoNewWindow",
    "-Wait",
    "-ArgumentList",
    "\"-NoProfile -ExecutionPolicy Bypass -Command iex ((new-object net.webclient).DownloadString('https://www.nativescript.org/setup/win'))\"",
];
__decorate([
    decorators_1.cache()
], DoctorService.prototype, "jsonFileSettingsPath", null);
__decorate([
    decorators_1.cache()
], DoctorService.prototype, "$jsonFileSettingsService", null);
exports.DoctorService = DoctorService;
yok_1.injector.register("doctorService", DoctorService);
