"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const semver = require("semver");
const constants = require("../constants");
const update_controller_base_1 = require("./update-controller-base");
class MigrateController extends update_controller_base_1.UpdateControllerBase {
    constructor($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $devicePlatformsConstants, $logger, $errors, $addPlatformService, $pluginsService, $projectDataService) {
        super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager);
        this.$fs = $fs;
        this.$platformCommandHelper = $platformCommandHelper;
        this.$platformsDataService = $platformsDataService;
        this.$packageInstallationManager = $packageInstallationManager;
        this.$packageManager = $packageManager;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$addPlatformService = $addPlatformService;
        this.$pluginsService = $pluginsService;
        this.$projectDataService = $projectDataService;
    }
    get verifiedPlatformVersions() {
        return {
            [this.$devicePlatformsConstants.Android.toLowerCase()]: "6.0.0-2019-06-11-172137-01",
            [this.$devicePlatformsConstants.iOS.toLowerCase()]: "6.0.0-2019-06-10-154118-03"
        };
    }
    migrate({ projectDir }) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            const backupDir = path.join(projectDir, MigrateController.backupFolder);
            try {
                this.$logger.info("Backup project configuration.");
                this.backup(MigrateController.folders, backupDir, projectData.projectDir);
                this.$logger.info("Backup project configuration complete.");
            }
            catch (error) {
                this.$logger.error(MigrateController.backupFailMessage);
                this.$fs.deleteDirectory(backupDir);
                return;
            }
            try {
                yield this.cleanUpProject(projectData);
                yield this.migrateDependencies(projectData);
            }
            catch (error) {
                this.restoreBackup(MigrateController.folders, backupDir, projectData.projectDir);
                this.$logger.error(MigrateController.migrateFailMessage);
            }
        });
    }
    shouldMigrate({ projectDir }) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            for (let i = 0; i < MigrateController.migrationDependencies.length; i++) {
                const dependency = MigrateController.migrationDependencies[i];
                const hasDependency = this.hasDependency(dependency, projectData);
                if (hasDependency && dependency.replaceWith) {
                    return true;
                }
                if (hasDependency && (yield this.shouldMigrateDependencyVersion(dependency, projectData))) {
                    return true;
                }
                if (!hasDependency && dependency.shouldAddIfMissing) {
                    return true;
                }
            }
            for (const platform in this.$devicePlatformsConstants) {
                const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData });
                if (!hasRuntimeDependency || (yield this.shouldUpdateRuntimeVersion({ targetVersion: this.verifiedPlatformVersions[platform.toLowerCase()], platform, projectData }))) {
                    return true;
                }
            }
        });
    }
    cleanUpProject(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info("Clean old project artefacts.");
            this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.HOOKS_DIR_NAME));
            this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.PLATFORMS_DIR_NAME));
            this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME));
            this.$fs.deleteFile(path.join(projectData.projectDir, constants.WEBPACK_CONFIG_NAME));
            this.$fs.deleteFile(path.join(projectData.projectDir, constants.PACKAGE_LOCK_JSON_FILE_NAME));
            if (!projectData.isShared) {
                this.$fs.deleteFile(path.join(projectData.projectDir, constants.TSCCONFIG_TNS_JSON_NAME));
            }
            this.$logger.info("Clean old project artefacts complete.");
        });
    }
    migrateDependencies(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info("Start dependencies migration.");
            for (let i = 0; i < MigrateController.migrationDependencies.length; i++) {
                const dependency = MigrateController.migrationDependencies[i];
                const hasDependency = this.hasDependency(dependency, projectData);
                if (hasDependency && dependency.replaceWith) {
                    this.$pluginsService.removeFromPackageJson(dependency.packageName, dependency.isDev, projectData.projectDir);
                    const replacementDep = _.find(MigrateController.migrationDependencies, migrationPackage => migrationPackage.packageName === dependency.replaceWith);
                    if (!replacementDep) {
                        this.$errors.failWithoutHelp("Failed to find replacement dependency.");
                    }
                    this.$logger.info(`Replacing '${dependency.packageName}' with '${replacementDep.packageName}'.`);
                    this.$pluginsService.addToPackageJson(replacementDep.packageName, replacementDep.verifiedVersion, replacementDep.isDev, projectData.projectDir);
                    continue;
                }
                if (hasDependency && (yield this.shouldMigrateDependencyVersion(dependency, projectData))) {
                    this.$logger.info(`Updating '${dependency.packageName}' to compatible version '${dependency.verifiedVersion}'`);
                    this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir);
                    continue;
                }
                if (!hasDependency && dependency.shouldAddIfMissing) {
                    this.$logger.info(`Adding '${dependency.packageName}' with version '${dependency.verifiedVersion}'`);
                    this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir);
                    continue;
                }
            }
            for (const platform in this.$devicePlatformsConstants) {
                const lowercasePlatform = platform.toLowerCase();
                const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData });
                if (!hasRuntimeDependency || (yield this.shouldUpdateRuntimeVersion({ targetVersion: this.verifiedPlatformVersions[lowercasePlatform], platform, projectData }))) {
                    const verifiedPlatformVersion = this.verifiedPlatformVersions[lowercasePlatform];
                    const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData);
                    this.$logger.info(`Updating ${platform} platform to version '${verifiedPlatformVersion}'.`);
                    yield this.$addPlatformService.setPlatformVersion(platformData, projectData, verifiedPlatformVersion);
                }
            }
            this.$logger.info("Install packages.");
            yield this.$packageManager.install(projectData.projectDir, projectData.projectDir, {
                disableNpmInstall: false,
                frameworkPath: null,
                ignoreScripts: false,
                path: projectData.projectDir
            });
            this.$logger.info("Migration complete.");
        });
    }
    shouldMigrateDependencyVersion(dependency, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const collection = dependency.isDev ? projectData.devDependencies : projectData.dependencies;
            const maxSatisfyingVersion = yield this.getMaxDependencyVersion(dependency.packageName, collection[dependency.packageName]);
            return !(maxSatisfyingVersion && semver.gte(maxSatisfyingVersion, dependency.verifiedVersion));
        });
    }
    shouldUpdateRuntimeVersion({ targetVersion, platform, projectData }) {
        return __awaiter(this, void 0, void 0, function* () {
            const maxRuntimeVersion = yield this.getMaxRuntimeVersion({ platform, projectData });
            return !(maxRuntimeVersion && semver.gte(maxRuntimeVersion, targetVersion));
        });
    }
}
MigrateController.folders = [
    constants.LIB_DIR_NAME,
    constants.HOOKS_DIR_NAME,
    constants.WEBPACK_CONFIG_NAME,
    constants.PACKAGE_JSON_FILE_NAME,
    constants.PACKAGE_LOCK_JSON_FILE_NAME,
    constants.TSCCONFIG_TNS_JSON_NAME
];
MigrateController.migrationDependencies = [
    { packageName: constants.TNS_CORE_MODULES_NAME, verifiedVersion: "6.0.0-next-2019-06-20-155941-01" },
    { packageName: constants.TNS_CORE_MODULES_WIDGETS_NAME, verifiedVersion: "6.0.0-next-2019-06-20-155941-01" },
    { packageName: "node-sass", isDev: true, verifiedVersion: "4.12.0" },
    { packageName: "typescript", isDev: true, verifiedVersion: "3.4.1" },
    { packageName: "less", isDev: true, verifiedVersion: "3.9.0" },
    { packageName: "nativescript-dev-sass", isDev: true, replaceWith: "node-sass" },
    { packageName: "nativescript-dev-typescript", isDev: true, replaceWith: "typescript" },
    { packageName: "nativescript-dev-less", isDev: true, replaceWith: "less" },
    { packageName: constants.WEBPACK_PLUGIN_NAME, isDev: true, shouldAddIfMissing: true, verifiedVersion: "0.25.0-next-2019-06-21-150426-03" },
    { packageName: "nativescript-camera", verifiedVersion: "4.5.0" },
    { packageName: "nativescript-geolocation", verifiedVersion: "5.1.0" },
    { packageName: "nativescript-imagepicker", verifiedVersion: "6.2.0" },
    { packageName: "nativescript-social-share", verifiedVersion: "1.5.2" },
    { packageName: "nativescript-ui-chart", verifiedVersion: "5.0.0-androidx-110619" },
    { packageName: "nativescript-ui-dataform", verifiedVersion: "5.0.0-androidx-110619" },
    { packageName: "nativescript-ui-gauge", verifiedVersion: "5.0.0-androidx" },
    { packageName: "nativescript-ui-listview", verifiedVersion: "7.0.0-androidx-110619" },
    { packageName: "nativescript-ui-sidedrawer", verifiedVersion: "7.0.0-androidx-110619" },
    { packageName: "nativescript-ui-calendar", verifiedVersion: "5.0.0-androidx-110619-2" },
    { packageName: "nativescript-ui-autocomplete", verifiedVersion: "5.0.0-androidx-110619" },
    { packageName: "nativescript-datetimepicker", verifiedVersion: "1.1.0" },
    { packageName: "kinvey-nativescript-sdk", verifiedVersion: "4.2.1" },
    { packageName: "nativescript-plugin-firebase", verifiedVersion: "9.0.1" },
    { packageName: "nativescript-vue", verifiedVersion: "2.3.0-rc.0" },
    { packageName: "nativescript-permissions", verifiedVersion: "1.3.0" },
    { packageName: "nativescript-cardview", verifiedVersion: "3.2.0" }
];
MigrateController.backupFolder = ".migration_backup";
MigrateController.migrateFailMessage = "Could not migrate the project!";
MigrateController.backupFailMessage = "Could not backup project folders!";
exports.MigrateController = MigrateController;
$injector.register("migrateController", MigrateController);
