"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());
    });
};
const constants = require("../../../lib/constants");
const path = require("path");
const shelljs = require("shelljs");
const node_modules_dest_copy_1 = require("./node-modules-dest-copy");
const node_modules_dependencies_builder_1 = require("./node-modules-dependencies-builder");
const helpers_1 = require("../../../lib/common/helpers");
let glob = require("glob");
class NodeModulesBuilder {
    constructor($fs, $projectData, $injector, $lockfile, $options) {
        this.$fs = $fs;
        this.$projectData = $projectData;
        this.$injector = $injector;
        this.$lockfile = $lockfile;
        this.$options = $options;
    }
    getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime) {
        return __awaiter(this, void 0, void 0, function* () {
            let projectDir = this.$projectData.projectDir;
            let isNodeModulesModified = false;
            let nodeModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME);
            let nodeModules = {};
            if (lastModifiedTime) {
                let defer = helpers_1.deferPromise();
                let match = new glob.Glob("node_modules/**", {
                    cwd: projectDir,
                    follow: true,
                    stat: true
                }, (er, files) => {
                    while (this.$lockfile.check()) {
                        helpers_1.sleep(10);
                    }
                    this.$lockfile.lock();
                    if (er) {
                        if (!defer.isResolved()) {
                            defer.reject(er);
                        }
                        this.$lockfile.unlock();
                        match.abort();
                        return;
                    }
                    for (let i = 0, l = files.length; i < l; i++) {
                        let file = files[i], resolvedPath = path.join(projectDir, file), relativePath = path.relative(projectDir, resolvedPath);
                        let stat = match.statCache[resolvedPath] || match.statCache[relativePath];
                        if (!stat) {
                            match.statCache[resolvedPath] = stat = this.$fs.getFsStats(resolvedPath);
                        }
                        if (stat.mtime <= lastModifiedTime) {
                            continue;
                        }
                        if (file === constants.NODE_MODULES_FOLDER_NAME) {
                            isNodeModulesModified = true;
                            this.$lockfile.unlock();
                            match.abort();
                            if (!defer.isResolved()) {
                                defer.resolve();
                            }
                            return;
                        }
                        let rootModuleName = path.normalize(file).split(path.sep)[1];
                        let rootModuleFullPath = path.join(nodeModulesPath, rootModuleName);
                        nodeModules[rootModuleFullPath] = rootModuleFullPath;
                    }
                    this.$lockfile.unlock();
                });
                match.on("end", () => {
                    if (!defer.isResolved()) {
                        let intervalId = setInterval(() => {
                            if (!this.$lockfile.check() || defer.isResolved()) {
                                if (!defer.isResolved()) {
                                    defer.resolve();
                                }
                                clearInterval(intervalId);
                            }
                        }, 100);
                    }
                });
                yield defer.promise;
            }
            if (isNodeModulesModified && this.$fs.exists(absoluteOutputPath)) {
                let currentPreparedTnsModules = this.$fs.readDirectory(absoluteOutputPath);
                let tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME);
                let tnsModulesInApp = this.$fs.readDirectory(tnsModulesPath);
                let modulesToDelete = _.difference(currentPreparedTnsModules, tnsModulesInApp);
                _.each(modulesToDelete, moduleName => this.$fs.deleteDirectory(path.join(absoluteOutputPath, moduleName)));
            }
            if (!lastModifiedTime || isNodeModulesModified) {
                this.expandScopedModules(nodeModulesPath, nodeModules);
            }
            return nodeModules;
        });
    }
    expandScopedModules(nodeModulesPath, nodeModules) {
        let nodeModulesDirectories = this.$fs.exists(nodeModulesPath) ? this.$fs.readDirectory(nodeModulesPath) : [];
        _.each(nodeModulesDirectories, nodeModuleDirectoryName => {
            let isNpmScope = /^@/.test(nodeModuleDirectoryName);
            let nodeModuleFullPath = path.join(nodeModulesPath, nodeModuleDirectoryName);
            if (isNpmScope) {
                this.expandScopedModules(nodeModuleFullPath, nodeModules);
            }
            else {
                nodeModules[nodeModuleFullPath] = nodeModuleFullPath;
            }
        });
    }
    prepareNodeModules(absoluteOutputPath, platform, lastModifiedTime) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.$fs.exists(absoluteOutputPath)) {
                lastModifiedTime = null;
            }
            let dependenciesBuilder = this.$injector.resolve(node_modules_dependencies_builder_1.NodeModulesDependenciesBuilder, {});
            let productionDependencies = dependenciesBuilder.getProductionDependencies(this.$projectData.projectDir);
            if (!this.$options.bundle) {
                const tnsModulesCopy = this.$injector.resolve(node_modules_dest_copy_1.TnsModulesCopy, {
                    outputRoot: absoluteOutputPath
                });
                tnsModulesCopy.copyModules(productionDependencies, platform);
            }
            else {
                this.cleanNodeModules(absoluteOutputPath, platform);
            }
            const npmPluginPrepare = this.$injector.resolve(node_modules_dest_copy_1.NpmPluginPrepare);
            yield npmPluginPrepare.preparePlugins(productionDependencies, platform);
        });
    }
    cleanNodeModules(absoluteOutputPath, platform) {
        shelljs.rm("-rf", absoluteOutputPath);
    }
}
exports.NodeModulesBuilder = NodeModulesBuilder;
$injector.register("nodeModulesBuilder", NodeModulesBuilder);
