"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) {
    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 events_1 = require("events");
const decorators_1 = require("../../common/decorators");
const constants_1 = require("../../constants");
class WebpackCompilerService extends events_1.EventEmitter {
    constructor($errors, $childProcess, $hooksService, $hostInfo, $logger, $mobileHelper, $cleanupService, $packageInstallationManager) {
        super();
        this.$errors = $errors;
        this.$childProcess = $childProcess;
        this.$hooksService = $hooksService;
        this.$hostInfo = $hostInfo;
        this.$logger = $logger;
        this.$mobileHelper = $mobileHelper;
        this.$cleanupService = $cleanupService;
        this.$packageInstallationManager = $packageInstallationManager;
        this.webpackProcesses = {};
        this.expectedHash = null;
    }
    compileWithWatch(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                if (this.webpackProcesses[platformData.platformNameLowerCase]) {
                    resolve();
                    return;
                }
                let isFirstWebpackWatchCompilation = true;
                prepareData.watch = true;
                try {
                    const childProcess = yield this.startWebpackProcess(platformData, projectData, prepareData);
                    childProcess.on("message", (message) => {
                        if (message === "Webpack compilation complete.") {
                            this.$logger.info("Webpack build done!");
                            resolve(childProcess);
                        }
                        message = message;
                        if (message.emittedFiles) {
                            if (isFirstWebpackWatchCompilation) {
                                isFirstWebpackWatchCompilation = false;
                                this.expectedHash = message.hash;
                                return;
                            }
                            let result;
                            if (prepareData.hmr) {
                                result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles, message.hash);
                            }
                            else {
                                result = { emittedFiles: message.emittedFiles, fallbackFiles: [], hash: "" };
                            }
                            const files = result.emittedFiles
                                .map((file) => path.join(platformData.appDestinationDirectoryPath, "app", file));
                            const fallbackFiles = result.fallbackFiles
                                .map((file) => path.join(platformData.appDestinationDirectoryPath, "app", file));
                            const data = {
                                files,
                                hasOnlyHotUpdateFiles: files.every(f => f.indexOf("hot-update") > -1),
                                hmrData: {
                                    hash: result.hash,
                                    fallbackFiles
                                },
                                platform: platformData.platformNameLowerCase
                            };
                            if (data.files.length) {
                                this.emit(constants_1.WEBPACK_COMPILATION_COMPLETE, data);
                            }
                        }
                    });
                    childProcess.on("error", (err) => {
                        this.$logger.trace(`Unable to start webpack process in watch mode. Error is: ${err}`);
                        delete this.webpackProcesses[platformData.platformNameLowerCase];
                        reject(err);
                    });
                    childProcess.on("close", (arg) => __awaiter(this, void 0, void 0, function* () {
                        yield this.$cleanupService.removeKillProcess(childProcess.pid.toString());
                        const exitCode = typeof arg === "number" ? arg : arg && arg.code;
                        this.$logger.trace(`Webpack process exited with code ${exitCode} when we expected it to be long living with watch.`);
                        const error = new Error(`Executing webpack failed with exit code ${exitCode}.`);
                        error.code = exitCode;
                        delete this.webpackProcesses[platformData.platformNameLowerCase];
                        reject(error);
                    }));
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    compileWithoutWatch(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                if (this.webpackProcesses[platformData.platformNameLowerCase]) {
                    resolve();
                    return;
                }
                try {
                    const childProcess = yield this.startWebpackProcess(platformData, projectData, prepareData);
                    childProcess.on("error", (err) => {
                        this.$logger.trace(`Unable to start webpack process in non-watch mode. Error is: ${err}`);
                        delete this.webpackProcesses[platformData.platformNameLowerCase];
                        reject(err);
                    });
                    childProcess.on("close", (arg) => __awaiter(this, void 0, void 0, function* () {
                        yield this.$cleanupService.removeKillProcess(childProcess.pid.toString());
                        delete this.webpackProcesses[platformData.platformNameLowerCase];
                        const exitCode = typeof arg === "number" ? arg : arg && arg.code;
                        if (exitCode === 0) {
                            resolve();
                        }
                        else {
                            const error = new Error(`Executing webpack failed with exit code ${exitCode}.`);
                            error.code = exitCode;
                            reject(error);
                        }
                    }));
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    stopWebpackCompiler(platform) {
        return __awaiter(this, void 0, void 0, function* () {
            if (platform) {
                yield this.stopWebpackForPlatform(platform);
            }
            else {
                const webpackedPlatforms = Object.keys(this.webpackProcesses);
                for (let i = 0; i < webpackedPlatforms.length; i++) {
                    yield this.stopWebpackForPlatform(webpackedPlatforms[i]);
                }
            }
        });
    }
    startWebpackProcess(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData);
            const envParams = yield this.buildEnvCommandLineParams(envData, platformData, projectData, prepareData);
            const additionalNodeArgs = semver.major(process.version) <= 8 ? ["--harmony"] : [];
            const args = [
                ...additionalNodeArgs,
                "--preserve-symlinks",
                path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"),
                `--config=${path.join(projectData.projectDir, "webpack.config.js")}`,
                ...envParams
            ];
            if (process.arch === "x64") {
                args.unshift("--max_old_space_size=4096");
            }
            if (prepareData.watch) {
                args.push("--watch");
            }
            const stdio = prepareData.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit";
            const childProcess = this.$childProcess.spawn(process.execPath, args, { cwd: projectData.projectDir, stdio });
            this.webpackProcesses[platformData.platformNameLowerCase] = childProcess;
            yield this.$cleanupService.addKillProcess(childProcess.pid.toString());
            return childProcess;
        });
    }
    buildEnvData(platform, projectData, prepareData) {
        const { env } = prepareData;
        const envData = Object.assign({}, env, { [platform.toLowerCase()]: true });
        const appPath = projectData.getAppDirectoryRelativePath();
        const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath();
        Object.assign(envData, appPath && { appPath }, appResourcesPath && { appResourcesPath });
        envData.verbose = envData.verbose || this.$logger.isVerbose();
        envData.production = envData.production || prepareData.release;
        envData.skipSnapshotTools = prepareData.nativePrepare && prepareData.nativePrepare.skipNativePrepare;
        if (prepareData.env && (prepareData.env.sourceMap === false || prepareData.env.sourceMap === 'false')) {
            delete envData.sourceMap;
        }
        else if (!prepareData.release) {
            envData.sourceMap = true;
        }
        return envData;
    }
    buildEnvCommandLineParams(envData, platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const envFlagNames = Object.keys(envData);
            const canSnapshot = prepareData.release && this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName);
            if (envData && envData.snapshot) {
                if (!canSnapshot) {
                    this.$logger.warn("Stripping the snapshot flag. " +
                        "Bear in mind that snapshot is only available in Android release builds.");
                    envFlagNames.splice(envFlagNames.indexOf("snapshot"), 1);
                }
                else if (this.$hostInfo.isWindows) {
                    const minWebpackPluginWithWinSnapshotsVersion = "1.3.0";
                    const installedWebpackPluginVersion = yield this.$packageInstallationManager.getInstalledDependencyVersion(constants_1.WEBPACK_PLUGIN_NAME, projectData.projectDir);
                    const hasWebpackPluginWithWinSnapshotsSupport = !!installedWebpackPluginVersion ? semver.gte(semver.coerce(installedWebpackPluginVersion), minWebpackPluginWithWinSnapshotsVersion) : true;
                    if (!hasWebpackPluginWithWinSnapshotsSupport) {
                        this.$errors.fail(`In order to generate Snapshots on Windows, please upgrade your Webpack plugin version (npm i nativescript-dev-webpack@latest).`);
                    }
                }
            }
            const args = [];
            envFlagNames.map(item => {
                let envValue = envData[item];
                if (typeof envValue === "undefined") {
                    return;
                }
                if (typeof envValue === "boolean") {
                    if (envValue) {
                        args.push(`--env.${item}`);
                    }
                }
                else {
                    if (!Array.isArray(envValue)) {
                        envValue = [envValue];
                    }
                    envValue.map((value) => args.push(`--env.${item}=${value}`));
                }
            });
            return args;
        });
    }
    getUpdatedEmittedFiles(allEmittedFiles, chunkFiles, nextHash) {
        const currentHash = this.getCurrentHotUpdateHash(allEmittedFiles);
        const isHashValid = nextHash ? this.expectedHash === currentHash : true;
        this.expectedHash = nextHash;
        const emittedHotUpdatesAndAssets = isHashValid ? _.difference(allEmittedFiles, chunkFiles) : allEmittedFiles;
        return { emittedFiles: emittedHotUpdatesAndAssets, fallbackFiles: chunkFiles, hash: currentHash };
    }
    getCurrentHotUpdateHash(emittedFiles) {
        let hotHash;
        const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js'));
        if (hotUpdateScripts && hotUpdateScripts.length) {
            const hotUpdateName = hotUpdateScripts[0];
            const matcher = /^(.+)\.(.+)\.hot-update/gm;
            const matches = matcher.exec(hotUpdateName);
            hotHash = matches[2];
        }
        return hotHash || "";
    }
    stopWebpackForPlatform(platform) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.trace(`Stopping webpack watch for platform ${platform}.`);
            const webpackProcess = this.webpackProcesses[platform];
            yield this.$cleanupService.removeKillProcess(webpackProcess.pid.toString());
            if (webpackProcess) {
                webpackProcess.kill("SIGINT");
                delete this.webpackProcesses[platform];
            }
        });
    }
}
__decorate([
    decorators_1.performanceLog()
], WebpackCompilerService.prototype, "startWebpackProcess", null);
exports.WebpackCompilerService = WebpackCompilerService;
$injector.register("webpackCompilerService", WebpackCompilerService);
