"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) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrepareController = void 0;
const choki = require("chokidar");
const helpers_1 = require("../common/helpers");
const decorators_1 = require("../common/decorators");
const events_1 = require("events");
const path = require("path");
const constants_1 = require("../constants");
const yok_1 = require("../common/yok");
const _ = require("lodash");
class PrepareController extends events_1.EventEmitter {
    constructor($platformController, $hooksService, $fs, $logger, $mobileHelper, $nodeModulesDependenciesBuilder, $platformsDataService, $pluginsService, $prepareNativePlatformService, $projectChangesService, $projectDataService, $webpackCompilerService, $watchIgnoreListService, $analyticsService, $markingModeService, $projectConfigService) {
        super();
        this.$platformController = $platformController;
        this.$hooksService = $hooksService;
        this.$fs = $fs;
        this.$logger = $logger;
        this.$mobileHelper = $mobileHelper;
        this.$nodeModulesDependenciesBuilder = $nodeModulesDependenciesBuilder;
        this.$platformsDataService = $platformsDataService;
        this.$pluginsService = $pluginsService;
        this.$prepareNativePlatformService = $prepareNativePlatformService;
        this.$projectChangesService = $projectChangesService;
        this.$projectDataService = $projectDataService;
        this.$webpackCompilerService = $webpackCompilerService;
        this.$watchIgnoreListService = $watchIgnoreListService;
        this.$analyticsService = $analyticsService;
        this.$markingModeService = $markingModeService;
        this.$projectConfigService = $projectConfigService;
        this.watchersData = {};
        this.isInitialPrepareReady = false;
        this.persistedData = [];
        this.webpackCompilerHandler = null;
    }
    prepare(prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(prepareData.projectDir);
            if (this.$mobileHelper.isAndroidPlatform(prepareData.platform)) {
                yield this.$markingModeService.handleMarkingModeFullDeprecation({
                    projectDir: projectData.projectDir,
                });
            }
            yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            return this.prepareCore(prepareData, projectData);
        });
    }
    stopWatchers(projectDir, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            const platformLowerCase = platform.toLowerCase();
            if (this.watchersData &&
                this.watchersData[projectDir] &&
                this.watchersData[projectDir][platformLowerCase] &&
                this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) {
                yield this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close();
                this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null;
            }
            if (this.watchersData &&
                this.watchersData[projectDir] &&
                this.watchersData[projectDir][platformLowerCase] &&
                this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess) {
                yield this.$webpackCompilerService.stopWebpackCompiler(platformLowerCase);
                this.$webpackCompilerService.removeListener(constants_1.WEBPACK_COMPILATION_COMPLETE, this.webpackCompilerHandler);
                this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess = false;
            }
        });
    }
    prepareCore(prepareData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$platformController.addPlatformIfNeeded(prepareData);
            yield this.trackRuntimeVersion(prepareData.platform, projectData);
            this.$logger.info("Preparing project...");
            prepareData.env = prepareData.env || {};
            prepareData.env.externals = prepareData.env.externals || [];
            prepareData.env.externals.push("~/package.json");
            prepareData.env.externals.push("package.json");
            if (this.$mobileHelper.isAndroidPlatform(prepareData.platform)) {
                yield this.$projectConfigService.writeLegacyNSConfigIfNeeded(projectData.projectDir, this.$projectDataService.getRuntimePackage(projectData.projectDir, prepareData.platform));
            }
            let result = null;
            const platformData = this.$platformsDataService.getPlatformData(prepareData.platform, projectData);
            if (prepareData.watch) {
                result = yield this.startWatchersWithPrepare(platformData, projectData, prepareData);
            }
            else {
                yield this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, prepareData);
                const hasNativeChanges = yield this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
                result = {
                    hasNativeChanges,
                    platform: prepareData.platform.toLowerCase(),
                };
            }
            yield this.writeRuntimePackageJson(projectData, platformData);
            yield this.$projectChangesService.savePrepareInfo(platformData, projectData, prepareData);
            this.$logger.info(`Project successfully prepared (${prepareData.platform.toLowerCase()})`);
            return result;
        });
    }
    startWatchersWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.watchersData[projectData.projectDir]) {
                this.watchersData[projectData.projectDir] = {};
            }
            if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) {
                this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = {
                    nativeFilesWatcher: null,
                    hasWebpackCompilerProcess: false,
                };
            }
            yield this.startJSWatcherWithPrepare(platformData, projectData, prepareData);
            const hasNativeChanges = yield this.startNativeWatcherWithPrepare(platformData, projectData, prepareData);
            const result = {
                platform: platformData.platformNameLowerCase,
                hasNativeChanges,
            };
            const hasPersistedDataWithNativeChanges = this.persistedData.find((data) => data.platform === result.platform && data.hasNativeChanges);
            if (hasPersistedDataWithNativeChanges) {
                result.hasNativeChanges = true;
            }
            this.isInitialPrepareReady = true;
            if (this.persistedData && this.persistedData.length) {
                this.emitPrepareEvent({
                    files: [],
                    staleFiles: [],
                    hasOnlyHotUpdateFiles: false,
                    hasNativeChanges: result.hasNativeChanges,
                    hmrData: null,
                    platform: platformData.platformNameLowerCase,
                });
            }
            return result;
        });
    }
    startJSWatcherWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess) {
                const handler = (data) => {
                    if (data.platform.toLowerCase() === platformData.platformNameLowerCase) {
                        this.emitPrepareEvent(Object.assign(Object.assign({}, data), { hasNativeChanges: false }));
                    }
                };
                this.webpackCompilerHandler = handler.bind(this);
                this.$webpackCompilerService.on(constants_1.WEBPACK_COMPILATION_COMPLETE, this.webpackCompilerHandler);
                this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess = true;
                yield this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData);
            }
        });
    }
    startNativeWatcherWithPrepare(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            let newNativeWatchStarted = false;
            let hasNativeChanges = false;
            if (prepareData.watchNative) {
                newNativeWatchStarted = yield this.startNativeWatcher(platformData, projectData);
            }
            if (newNativeWatchStarted) {
                hasNativeChanges = yield this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
            }
            return hasNativeChanges;
        });
    }
    startNativeWatcher(platformData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) {
                return false;
            }
            const patterns = yield this.getWatcherPatterns(platformData, projectData);
            const watcherOptions = {
                ignoreInitial: true,
                cwd: projectData.projectDir,
                awaitWriteFinish: {
                    pollInterval: 100,
                    stabilityThreshold: 500,
                },
                ignored: ["**/.*", ".*"],
            };
            const watcher = choki
                .watch(patterns, watcherOptions)
                .on("all", (event, filePath) => __awaiter(this, void 0, void 0, function* () {
                filePath = path.join(projectData.projectDir, filePath);
                if (this.$watchIgnoreListService.isFileInIgnoreList(filePath)) {
                    this.$watchIgnoreListService.removeFileFromIgnoreList(filePath);
                }
                else {
                    this.$logger.info(`Chokidar raised event ${event} for ${filePath}.`);
                    yield this.writeRuntimePackageJson(projectData, platformData);
                    this.emitPrepareEvent({
                        files: [],
                        staleFiles: [],
                        hasOnlyHotUpdateFiles: false,
                        hmrData: null,
                        hasNativeChanges: true,
                        platform: platformData.platformNameLowerCase,
                    });
                }
            }));
            this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher;
            return true;
        });
    }
    getWatcherPatterns(platformData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const dependencies = this.$nodeModulesDependenciesBuilder
                .getProductionDependencies(projectData.projectDir, projectData.ignoredDependencies)
                .filter((dep) => dep.nativescript);
            const pluginsNativeDirectories = dependencies.map((dep) => path.join(dep.directory, constants_1.PLATFORMS_DIR_NAME, platformData.platformNameLowerCase));
            const pluginsPackageJsonFiles = dependencies.map((dep) => path.join(dep.directory, constants_1.PACKAGE_JSON_FILE_NAME));
            const patterns = [
                path.join(projectData.projectDir, constants_1.PACKAGE_JSON_FILE_NAME),
                path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_JS),
                path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_TS),
                path.join(projectData.getAppDirectoryPath(), constants_1.PACKAGE_JSON_FILE_NAME),
                path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName),
            ]
                .concat(pluginsNativeDirectories)
                .concat(pluginsPackageJsonFiles);
            return patterns;
        });
    }
    writeRuntimePackageJson(projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const configInfo = this.$projectConfigService.detectProjectConfigs(projectData.projectDir);
            if (configInfo.usingNSConfig) {
                return;
            }
            this.$logger.info("Updating runtime package.json with configuration values...");
            const nsConfig = this.$projectConfigService.readConfig(projectData.projectDir);
            const packageData = Object.assign(Object.assign(Object.assign({}, _.pick(projectData.packageJsonData, ["name"])), nsConfig), { main: "bundle" });
            if (platformData.platformNameLowerCase === "ios" &&
                packageData.ios &&
                packageData.ios.discardUncaughtJsExceptions) {
                packageData.discardUncaughtJsExceptions =
                    packageData.ios.discardUncaughtJsExceptions;
            }
            if (platformData.platformNameLowerCase === "android" &&
                packageData.android &&
                packageData.android.discardUncaughtJsExceptions) {
                packageData.discardUncaughtJsExceptions =
                    packageData.android.discardUncaughtJsExceptions;
            }
            let packagePath;
            if (platformData.platformNameLowerCase === "ios") {
                packagePath = path.join(platformData.projectRoot, projectData.projectName, "app", "package.json");
            }
            else {
                packagePath = path.join(platformData.projectRoot, "app", "src", "main", "assets", "app", "package.json");
            }
            try {
                const emittedPackageData = this.$fs.readJson(packagePath);
                if (emittedPackageData === null || emittedPackageData === void 0 ? void 0 : emittedPackageData.main) {
                    packageData.main = emittedPackageData.main;
                }
            }
            catch (error) {
                this.$logger.trace("Failed to read emitted package.json. Error is: ", error);
            }
            this.$fs.writeJson(packagePath, packageData);
        });
    }
    emitPrepareEvent(filesChangeEventData) {
        if (this.isInitialPrepareReady) {
            this.emit(constants_1.PREPARE_READY_EVENT_NAME, filesChangeEventData);
        }
        else {
            this.persistedData.push(filesChangeEventData);
        }
    }
    trackRuntimeVersion(platform, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { version } = this.$projectDataService.getRuntimePackage(projectData.projectDir, platform);
            if (!version) {
                this.$logger.trace(`Unable to get runtime version for project directory: ${projectData.projectDir} and platform ${platform}.`);
                return;
            }
            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                action: "Using Runtime Version",
                additionalData: `${platform.toLowerCase()}${constants_1.AnalyticsEventLabelDelimiter}${version}`,
            });
        });
    }
}
__decorate([
    decorators_1.performanceLog(),
    helpers_1.hook("prepare")
], PrepareController.prototype, "prepareCore", null);
__decorate([
    helpers_1.hook("watch")
], PrepareController.prototype, "startWatchersWithPrepare", null);
__decorate([
    helpers_1.hook("watchPatterns")
], PrepareController.prototype, "getWatcherPatterns", null);
__decorate([
    decorators_1.cache()
], PrepareController.prototype, "trackRuntimeVersion", null);
exports.PrepareController = PrepareController;
yok_1.injector.register("prepareController", PrepareController);
