"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const _ = require("lodash");
const constants_1 = require("../constants");
const helpers_1 = require("../common/helpers");
const yok_1 = require("../common/yok");
const color_1 = require("../color");
class TestInitCommand {
    constructor($packageManager, $projectData, $errors, $options, $prompter, $fs, $resources, $pluginsService, $logger, $testInitializationService) {
        this.$packageManager = $packageManager;
        this.$projectData = $projectData;
        this.$errors = $errors;
        this.$options = $options;
        this.$prompter = $prompter;
        this.$fs = $fs;
        this.$resources = $resources;
        this.$pluginsService = $pluginsService;
        this.$logger = $logger;
        this.$testInitializationService = $testInitializationService;
        this.allowedParameters = [];
        this.karmaConfigAdditionalFrameworks = {
            mocha: ["chai"],
        };
        this.$projectData.initializeProjectData();
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectDir = this.$projectData.projectDir;
            const frameworkToInstall = this.$options.framework ||
                (yield this.$prompter.promptForChoice("Select testing framework:", constants_1.TESTING_FRAMEWORKS));
            if (constants_1.TESTING_FRAMEWORKS.indexOf(frameworkToInstall) === -1) {
                this.$errors.failWithHelp(`Unknown or unsupported unit testing framework: ${frameworkToInstall}.`);
            }
            const projectFilesExtension = this.$projectData.projectType === constants_1.ProjectTypes.TsFlavorName ||
                this.$projectData.projectType === constants_1.ProjectTypes.NgFlavorName
                ? ".ts"
                : ".js";
            let modulesToInstall = [];
            try {
                modulesToInstall = this.$testInitializationService.getDependencies(frameworkToInstall);
            }
            catch (err) {
                this.$errors.fail(`Unable to install the unit testing dependencies. Error: '${err.message}'`);
            }
            modulesToInstall = modulesToInstall.filter((moduleToInstall) => !moduleToInstall.projectType ||
                moduleToInstall.projectType === projectFilesExtension);
            for (const mod of modulesToInstall) {
                let moduleToInstall = mod.name;
                moduleToInstall += `@${mod.version}`;
                yield this.$packageManager.install(moduleToInstall, projectDir, {
                    "save-dev": true,
                    "save-exact": true,
                    optional: false,
                    disableNpmInstall: this.$options.disableNpmInstall,
                    frameworkPath: this.$options.frameworkPath,
                    ignoreScripts: this.$options.ignoreScripts,
                    path: this.$options.path,
                });
                const modulePath = path.join(projectDir, "node_modules", mod.name);
                const modulePackageJsonPath = path.join(modulePath, "package.json");
                const modulePackageJsonContent = this.$fs.readJson(modulePackageJsonPath);
                const modulePeerDependencies = modulePackageJsonContent.peerDependencies || {};
                for (const peerDependency in modulePeerDependencies) {
                    const isPeerDependencyExcluded = _.includes(mod.excludedPeerDependencies, peerDependency);
                    if (isPeerDependencyExcluded) {
                        continue;
                    }
                    const dependencyVersion = modulePeerDependencies[peerDependency] || "*";
                    try {
                        yield this.$packageManager.install(`${peerDependency}@${dependencyVersion}`, projectDir, {
                            "save-dev": true,
                            "save-exact": true,
                            disableNpmInstall: false,
                            frameworkPath: this.$options.frameworkPath,
                            ignoreScripts: this.$options.ignoreScripts,
                            path: this.$options.path,
                        });
                    }
                    catch (e) {
                        this.$logger.error(e.message);
                    }
                }
            }
            yield this.$pluginsService.add("@nativescript/unit-test-runner", this.$projectData);
            this.$logger.clearScreen();
            const bufferedLogs = [];
            const testsDir = path.join(this.$projectData.appDirectoryPath, "tests");
            const projectTestsDir = path.relative(this.$projectData.projectDir, testsDir);
            const relativeTestsDir = path.relative(this.$projectData.appDirectoryPath, testsDir);
            let shouldCreateSampleTests = true;
            if (this.$fs.exists(testsDir)) {
                const specFilenamePattern = `<filename>.spec${projectFilesExtension}`;
                bufferedLogs.push(color_1.color.yellow([
                    `Note: The "${projectTestsDir}" directory already exists, will not create example tests in the project.`,
                    `You may create "${specFilenamePattern}" files anywhere you'd like.`,
                    "",
                ].join("\n")));
                shouldCreateSampleTests = false;
            }
            this.$fs.ensureDirectoryExists(testsDir);
            const frameworks = [frameworkToInstall]
                .concat(this.karmaConfigAdditionalFrameworks[frameworkToInstall] || [])
                .map((fw) => `'${fw}'`)
                .join(", ");
            const testFiles = `'${(0, helpers_1.fromWindowsRelativePathToUnix)(relativeTestsDir)}/**/*${projectFilesExtension}'`;
            const karmaConfTemplate = this.$resources.readText("test/karma.conf.js");
            const karmaConf = _.template(karmaConfTemplate)({
                frameworks,
                testFiles,
                basePath: this.$projectData.getAppDirectoryRelativePath(),
            });
            this.$fs.writeFile(path.join(projectDir, "karma.conf.js"), karmaConf);
            const exampleFilePath = this.$resources.resolvePath(`test/example.${frameworkToInstall}${projectFilesExtension}`);
            const targetExampleTestPath = path.join(testsDir, `example.spec${projectFilesExtension}`);
            if (shouldCreateSampleTests && this.$fs.exists(exampleFilePath)) {
                this.$fs.copyFile(exampleFilePath, targetExampleTestPath);
                const targetExampleTestRelativePath = path.relative(projectDir, targetExampleTestPath);
                bufferedLogs.push(`Added example test: ${color_1.color.yellow(targetExampleTestRelativePath)}`);
            }
            const testMainResourcesPath = this.$resources.resolvePath(`test/test-main${projectFilesExtension}`);
            const testMainPath = path.join(this.$projectData.appDirectoryPath, `test${projectFilesExtension}`);
            if (!this.$fs.exists(testMainPath)) {
                this.$fs.copyFile(testMainResourcesPath, testMainPath);
                const testMainRelativePath = path.relative(projectDir, testMainPath);
                bufferedLogs.push(`Main test entrypoint created: ${color_1.color.yellow(testMainRelativePath)}`);
            }
            const testTsConfigTemplate = this.$resources.readText("test/tsconfig.spec.json");
            const testTsConfig = _.template(testTsConfigTemplate)({
                basePath: this.$projectData.getAppDirectoryRelativePath(),
            });
            this.$fs.writeFile(path.join(projectDir, "tsconfig.spec.json"), testTsConfig);
            bufferedLogs.push(`Added/replaced ${color_1.color.yellow("tsconfig.spec.json")}`);
            const greyDollarSign = color_1.color.grey("$");
            this.$logger.info([
                [
                    color_1.color.green(`Tests using`),
                    color_1.color.cyan(frameworkToInstall),
                    color_1.color.green(`were successfully initialized.`),
                ].join(" "),
                "",
                ...bufferedLogs,
                "",
                color_1.color.yellow(`Note: @nativescript/unit-test-runner was included in "dependencies" as a convenience to automatically adjust your app's Info.plist on iOS and AndroidManifest.xml on Android to ensure the socket connects properly.`),
                "",
                color_1.color.yellow(`For production you may want to move to "devDependencies" and manage the settings yourself.`),
                "",
                "",
                `You can now run your tests:`,
                "",
                `  ${greyDollarSign} ${color_1.color.green("ns test ios")}`,
                `  ${greyDollarSign} ${color_1.color.green("ns test android")}`,
                "",
            ].join("\n"));
        });
    }
}
yok_1.injector.registerCommand("test|init", TestInitCommand);
