"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 UpdateController extends update_controller_base_1.UpdateControllerBase {
    constructor($fs, $platformsDataService, $platformCommandHelper, $packageInstallationManager, $packageManager, $devicePlatformsConstants, $addPlatformService, $logger, $errors, $pluginsService, $pacoteService, $projectDataService) {
        super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService);
        this.$fs = $fs;
        this.$platformsDataService = $platformsDataService;
        this.$platformCommandHelper = $platformCommandHelper;
        this.$packageInstallationManager = $packageInstallationManager;
        this.$packageManager = $packageManager;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$addPlatformService = $addPlatformService;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$pluginsService = $pluginsService;
        this.$pacoteService = $pacoteService;
        this.$projectDataService = $projectDataService;
    }
    update(updateOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(updateOptions.projectDir);
            const backupDir = path.join(updateOptions.projectDir, UpdateController.backupFolder);
            try {
                this.backup(UpdateController.folders, backupDir, projectData.projectDir);
            }
            catch (error) {
                this.$logger.error(UpdateController.backupFailMessage);
                this.$fs.deleteDirectory(backupDir);
                return;
            }
            try {
                yield this.cleanUpProject(projectData);
                yield this.updateProject(projectData, updateOptions.version);
            }
            catch (error) {
                this.restoreBackup(UpdateController.folders, backupDir, projectData.projectDir);
                this.$logger.error(UpdateController.updateFailMessage);
            }
        });
    }
    shouldUpdate({ projectDir, version }) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            const templateManifest = yield this.getTemplateManifest(projectData, version);
            const dependencies = this.getUpdatableDependencies(templateManifest.dependencies);
            const devDependencies = this.getUpdatableDependencies(templateManifest.devDependencies);
            if ((yield this.hasDependenciesToUpdate({ dependencies, areDev: false, projectData })) ||
                (yield this.hasDependenciesToUpdate({ dependencies: devDependencies, areDev: true, projectData }))) {
                return true;
            }
            for (const platform in this.$devicePlatformsConstants) {
                const lowercasePlatform = platform.toLowerCase();
                const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData);
                const templatePlatformData = this.$projectDataService.getNSValueFromContent(templateManifest, platformData.frameworkPackageName);
                const templateRuntimeVersion = templatePlatformData && templatePlatformData.version;
                if (templateRuntimeVersion && (yield this.shouldUpdateRuntimeVersion(templateRuntimeVersion, platformData.frameworkPackageName, 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));
            this.$logger.info("Clean old project artefacts complete.");
        });
    }
    updateProject(projectData, version) {
        return __awaiter(this, void 0, void 0, function* () {
            const templateManifest = yield this.getTemplateManifest(projectData, version);
            const dependencies = this.getUpdatableDependencies(templateManifest.dependencies);
            const devDependencies = this.getUpdatableDependencies(templateManifest.devDependencies);
            this.$logger.info("Start updating dependencies.");
            yield this.updateDependencies({ dependencies, areDev: false, projectData });
            this.$logger.info("Finished updating dependencies.");
            this.$logger.info("Start updating devDependencies.");
            yield this.updateDependencies({ dependencies: devDependencies, areDev: true, projectData });
            this.$logger.info("Finished updating devDependencies.");
            this.$logger.info("Start updating runtimes.");
            yield this.updateRuntimes(templateManifest, projectData);
            this.$logger.info("Finished updating runtimes.");
            this.$logger.info("Install packages.");
            yield this.$packageManager.install(projectData.projectDir, projectData.projectDir, {
                disableNpmInstall: false,
                frameworkPath: null,
                ignoreScripts: false,
                path: projectData.projectDir
            });
        });
    }
    updateDependencies({ dependencies, areDev, projectData }) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const dependency in dependencies) {
                const templateVersion = dependencies[dependency];
                if (!this.hasDependency({ packageName: dependency, isDev: areDev }, projectData)) {
                    continue;
                }
                if (yield this.shouldUpdateDependency(dependency, templateVersion, projectData)) {
                    this.$logger.info(`Updating '${dependency}' to version '${templateVersion}'.`);
                    this.$pluginsService.addToPackageJson(dependency, templateVersion, areDev, projectData.projectDir);
                }
            }
        });
    }
    shouldUpdateDependency(dependency, targetVersion, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const devDependencies = projectData.devDependencies || {};
            const dependencies = projectData.dependencies || {};
            const projectVersion = dependencies[dependency] || devDependencies[dependency];
            const maxSatisfyingTargetVersion = yield this.getMaxDependencyVersion(dependency, targetVersion);
            const maxSatisfyingProjectVersion = yield this.getMaxDependencyVersion(dependency, projectVersion);
            return maxSatisfyingProjectVersion && maxSatisfyingTargetVersion && semver.gt(maxSatisfyingTargetVersion, maxSatisfyingProjectVersion);
        });
    }
    hasDependenciesToUpdate({ dependencies, areDev, projectData }) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const dependency in dependencies) {
                const templateVersion = dependencies[dependency];
                if (!this.hasDependency({ packageName: dependency, isDev: areDev }, projectData)) {
                    continue;
                }
                if (yield this.shouldUpdateDependency(dependency, templateVersion, projectData)) {
                    return true;
                }
            }
        });
    }
    updateRuntimes(templateManifest, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const platform in this.$devicePlatformsConstants) {
                const lowercasePlatform = platform.toLowerCase();
                const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData);
                const templatePlatformData = this.$projectDataService.getNSValueFromContent(templateManifest, platformData.frameworkPackageName);
                const templateRuntimeVersion = templatePlatformData && templatePlatformData.version;
                if (templateRuntimeVersion && (yield this.shouldUpdateRuntimeVersion(templateRuntimeVersion, platformData.frameworkPackageName, platform, projectData))) {
                    this.$logger.info(`Updating ${platform} platform to version '${templateRuntimeVersion}'.`);
                    yield this.$addPlatformService.setPlatformVersion(platformData, projectData, templateRuntimeVersion);
                }
            }
        });
    }
    shouldUpdateRuntimeVersion(templateRuntimeVersion, frameworkPackageName, platform, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData });
            if (!hasRuntimeDependency) {
                return false;
            }
            const maxTemplateRuntimeVersion = yield this.getMaxDependencyVersion(frameworkPackageName, templateRuntimeVersion);
            const maxRuntimeVersion = yield this.getMaxRuntimeVersion({ platform, projectData });
            return maxTemplateRuntimeVersion && maxRuntimeVersion && semver.gt(maxTemplateRuntimeVersion, maxRuntimeVersion);
        });
    }
    getUpdatableDependencies(dependencies) {
        return _.pickBy(dependencies, (value, key) => {
            return UpdateController.updatableDependencies.indexOf(key) > -1;
        });
    }
    getTemplateName(projectData) {
        let template;
        switch (projectData.projectType) {
            case constants.ProjectTypes.NgFlavorName: {
                template = constants.RESERVED_TEMPLATE_NAMES.angular;
                break;
            }
            case constants.ProjectTypes.VueFlavorName: {
                template = constants.RESERVED_TEMPLATE_NAMES.vue;
                break;
            }
            case constants.ProjectTypes.TsFlavorName: {
                template = constants.RESERVED_TEMPLATE_NAMES.typescript;
                break;
            }
            case constants.ProjectTypes.JsFlavorName: {
                template = constants.RESERVED_TEMPLATE_NAMES.javascript;
                break;
            }
        }
        return template;
    }
    getTemplateManifest(projectData, version) {
        return __awaiter(this, void 0, void 0, function* () {
            let templateManifest;
            const templateName = this.getTemplateName(projectData);
            version = version || (yield this.$packageInstallationManager.getLatestCompatibleVersionSafe(templateName));
            try {
                templateManifest = yield this.getPackageManifest(templateName, version);
            }
            catch (err) {
                this.$errors.fail(UpdateController.failedToGetTemplateManifestMessage, err.message);
            }
            return templateManifest;
        });
    }
}
UpdateController.updatableDependencies = [
    constants.TNS_CORE_MODULES_NAME,
    constants.TNS_CORE_MODULES_WIDGETS_NAME,
    constants.WEBPACK_PLUGIN_NAME
];
UpdateController.folders = [
    constants.LIB_DIR_NAME,
    constants.HOOKS_DIR_NAME,
    constants.WEBPACK_CONFIG_NAME,
    constants.PACKAGE_JSON_FILE_NAME,
    constants.PACKAGE_LOCK_JSON_FILE_NAME
];
UpdateController.backupFolder = ".update_backup";
UpdateController.updateFailMessage = "Could not update the project!";
UpdateController.backupFailMessage = "Could not backup project folders!";
UpdateController.failedToGetTemplateManifestMessage = "Failed to get template information for the specified version. Original error: %s";
exports.UpdateController = UpdateController;
$injector.register("updateController", UpdateController);
