"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 events_1 = require("events");
const decorators_1 = require("../../common/decorators");
const helpers_1 = require("../../common/helpers");
const constants_1 = require("../../constants");
class WebpackCompilerService extends events_1.EventEmitter {
    constructor($childProcess, $hooksService, $hostInfo, $logger, $pluginsService, $mobileHelper) {
        super();
        this.$childProcess = $childProcess;
        this.$hooksService = $hooksService;
        this.$hostInfo = $hostInfo;
        this.$logger = $logger;
        this.$pluginsService = $pluginsService;
        this.$mobileHelper = $mobileHelper;
        this.webpackProcesses = {};
    }
    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;
                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;
                            return;
                        }
                        let result;
                        if (prepareData.hmr) {
                            result = this.getUpdatedEmittedFiles(message.emittedFiles, message.webpackRuntimeFiles, message.entryPointFiles);
                        }
                        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("close", (arg) => {
                    const exitCode = typeof arg === "number" ? arg : arg && arg.code;
                    if (exitCode === 0) {
                        resolve(childProcess);
                    }
                    else {
                        const error = new Error(`Executing webpack failed with exit code ${exitCode}.`);
                        error.code = exitCode;
                        reject(error);
                    }
                });
            }));
        });
    }
    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;
                }
                const childProcess = yield this.startWebpackProcess(platformData, projectData, prepareData);
                childProcess.on("close", (arg) => {
                    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);
                    }
                });
            }));
        });
    }
    stopWebpackCompiler(platform) {
        if (platform) {
            this.stopWebpackForPlatform(platform);
        }
        else {
            Object.keys(this.webpackProcesses).forEach(pl => this.stopWebpackForPlatform(pl));
        }
    }
    startWebpackProcess(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData);
            const envParams = this.buildEnvCommandLineParams(envData, platformData, prepareData);
            yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            const args = [
                path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"),
                "--preserve-symlinks",
                `--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("node", args, { cwd: projectData.projectDir, stdio });
            this.webpackProcesses[platformData.platformNameLowerCase] = childProcess;
            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 = this.$logger.isVerbose();
        envData.production = prepareData.release;
        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, prepareData) {
        const envFlagNames = Object.keys(envData);
        const shouldSnapshot = prepareData.release && !this.$hostInfo.isWindows && this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName);
        if (envData && envData.snapshot && !shouldSnapshot) {
            this.$logger.warn("Stripping the snapshot flag. " +
                "Bear in mind that snapshot is only available in release builds and " +
                "is NOT available on Windows systems.");
            envFlagNames.splice(envFlagNames.indexOf("snapshot"), 1);
        }
        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(emittedFiles, webpackRuntimeFiles, entryPointFiles) {
        let fallbackFiles = [];
        let hotHash;
        let result = emittedFiles.slice();
        const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js'));
        if (webpackRuntimeFiles && webpackRuntimeFiles.length) {
            result = result.filter(file => webpackRuntimeFiles.indexOf(file) === -1);
        }
        if (entryPointFiles && entryPointFiles.length) {
            result = result.filter(file => entryPointFiles.indexOf(file) === -1);
        }
        hotUpdateScripts.forEach(hotUpdateScript => {
            const { name, hash } = this.parseHotUpdateChunkName(hotUpdateScript);
            hotHash = hash;
            result = result.filter(file => file !== `${name}.js`);
        });
        fallbackFiles = emittedFiles.filter(file => hotUpdateScripts.indexOf(file) === -1);
        return { emittedFiles: result, fallbackFiles, hash: hotHash };
    }
    parseHotUpdateChunkName(name) {
        const matcher = /^(.+)\.(.+)\.hot-update/gm;
        const matches = matcher.exec(name);
        return {
            name: matches[1] || "",
            hash: matches[2] || "",
        };
    }
    stopWebpackForPlatform(platform) {
        this.$logger.trace(`Stopping webpack watch for platform ${platform}.`);
        const webpackProcess = this.webpackProcesses[platform];
        if (webpackProcess) {
            webpackProcess.kill("SIGINT");
            delete this.webpackProcesses[platform];
        }
    }
}
__decorate([
    decorators_1.performanceLog(),
    helpers_1.hook('prepareJSApp')
], WebpackCompilerService.prototype, "startWebpackProcess", null);
exports.WebpackCompilerService = WebpackCompilerService;
$injector.register("webpackCompilerService", WebpackCompilerService);
