"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 constants_1 = require("../constants");
const decorators_1 = require("../common/decorators");
const helpers_1 = require("../common/helpers");
const constants_2 = require("../common/constants");
const events_1 = require("events");
const preview_app_constants_1 = require("../services/livesync/playground/preview-app-constants");
class PreviewAppController extends events_1.EventEmitter {
    constructor($analyticsService, $devicePlatformsConstants, $errors, $hmrStatusService, $logger, $hooksService, $mobileHelper, $pluginsService, $prepareController, $previewAppFilesService, $previewAppPluginsService, $previewDevicesService, $previewQrCodeService, $previewSdkService, $prepareDataService, $projectDataService, $markingModeService) {
        super();
        this.$analyticsService = $analyticsService;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$errors = $errors;
        this.$hmrStatusService = $hmrStatusService;
        this.$logger = $logger;
        this.$hooksService = $hooksService;
        this.$mobileHelper = $mobileHelper;
        this.$pluginsService = $pluginsService;
        this.$prepareController = $prepareController;
        this.$previewAppFilesService = $previewAppFilesService;
        this.$previewAppPluginsService = $previewAppPluginsService;
        this.$previewDevicesService = $previewDevicesService;
        this.$previewQrCodeService = $previewQrCodeService;
        this.$previewSdkService = $previewSdkService;
        this.$prepareDataService = $prepareDataService;
        this.$projectDataService = $projectDataService;
        this.$markingModeService = $markingModeService;
        this.prepareReadyEventHandler = null;
        this.deviceInitializationPromise = {};
        this.devicesLiveSyncChain = {};
        this.devicesCanExecuteHmr = {};
        this.devicesHmrFiles = {};
        this.devicesAppFiles = {};
        this.devicesCurrentHmrHash = {};
    }
    startPreview(data) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.previewCore(data);
            const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload });
            const result = yield this.$previewQrCodeService.getLiveSyncQrCode(url);
            return result;
        });
    }
    stopPreview(data) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$previewSdkService.stop();
            this.$previewDevicesService.updateConnectedDevices([]);
            yield this.$prepareController.stopWatchers(data.projectDir, this.$devicePlatformsConstants.Android);
            yield this.$prepareController.stopWatchers(data.projectDir, this.$devicePlatformsConstants.iOS);
            if (this.prepareReadyEventHandler) {
                this.$prepareController.removeListener(constants_1.PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler);
                this.prepareReadyEventHandler = null;
            }
        });
    }
    previewCore(data) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(data.projectDir);
            yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            yield this.$previewSdkService.initialize(data.projectDir, (device) => __awaiter(this, void 0, void 0, function* () {
                if (this.$mobileHelper.isAndroidPlatform(device.platform)) {
                    yield this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: projectData.projectDir });
                }
                try {
                    if (!device) {
                        this.$errors.fail("Sending initial preview files without a specified device is not supported.");
                    }
                    if (this.deviceInitializationPromise[device.id]) {
                        return null;
                    }
                    this.deviceInitializationPromise[device.id] = true;
                    if (device.uniqueId) {
                        yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                            action: "Preview App Data",
                            platform: device.platform,
                            additionalData: device.uniqueId
                        });
                    }
                    yield this.$hooksService.executeBeforeHooks("preview-sync", { hookArgs: Object.assign({}, data, { platform: device.platform, projectData }) });
                    if (data.useHotModuleReload) {
                        this.$hmrStatusService.attachToHmrStatusEvent();
                        this.devicesCanExecuteHmr[device.id] = true;
                    }
                    yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
                    if (!this.prepareReadyEventHandler) {
                        const handler = (currentPrepareData) => __awaiter(this, void 0, void 0, function* () {
                            yield this.handlePrepareReadyEvent(data, currentPrepareData);
                        });
                        this.prepareReadyEventHandler = handler.bind(this);
                        this.$prepareController.on(constants_1.PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler);
                    }
                    data.env = data.env || {};
                    data.env.externals = this.$previewAppPluginsService.getExternalPlugins(device);
                    const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), Object.assign({}, data, { nativePrepare: { skipNativePrepare: true }, watch: true, watchNative: false }));
                    yield this.$prepareController.prepare(prepareData);
                    try {
                        const payloads = yield this.getInitialFilesForDeviceSafe(data, device);
                        return payloads;
                    }
                    finally {
                        this.deviceInitializationPromise[device.id] = null;
                    }
                }
                catch (error) {
                    this.$logger.trace(`Error while sending files on device '${device && device.id}'. Error is`, error);
                    this.emit(preview_app_constants_1.PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, {
                        error,
                        data,
                        platform: device.platform,
                        deviceId: device.id
                    });
                }
            }));
            return null;
        });
    }
    handlePrepareReadyEvent(data, currentPrepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { hmrData, files, platform } = currentPrepareData;
            const platformHmrData = _.cloneDeep(hmrData) || {};
            const connectedDevices = this.$previewDevicesService.getDevicesForPlatform(platform);
            if (!connectedDevices || !connectedDevices.length) {
                this.$logger.warn(`Unable to find any connected devices for platform '${platform}'. In order to execute live sync, open your Preview app and optionally re-scan the QR code using the Playground app.`);
                return;
            }
            yield Promise.all(_.map(connectedDevices, (device) => __awaiter(this, void 0, void 0, function* () {
                const previousSync = this.devicesLiveSyncChain[device.id] || Promise.resolve();
                const currentSyncDeferPromise = helpers_1.deferPromise();
                this.devicesLiveSyncChain[device.id] = currentSyncDeferPromise.promise;
                this.devicesCurrentHmrHash[device.id] = this.devicesCurrentHmrHash[device.id] || platformHmrData.hash;
                if (data.useHotModuleReload) {
                    this.devicesHmrFiles[device.id] = this.devicesHmrFiles[device.id] || [];
                    this.devicesHmrFiles[device.id].push(...files);
                    this.devicesAppFiles[device.id] = platformHmrData.fallbackFiles;
                }
                else {
                    this.devicesHmrFiles[device.id] = [];
                    this.devicesAppFiles[device.id] = files;
                }
                yield previousSync;
                try {
                    let canExecuteHmrSync = false;
                    const hmrHash = this.devicesCurrentHmrHash[device.id];
                    this.devicesCurrentHmrHash[device.id] = null;
                    if (data.useHotModuleReload && hmrHash) {
                        if (this.devicesCanExecuteHmr[device.id]) {
                            canExecuteHmrSync = true;
                        }
                    }
                    const filesToSync = canExecuteHmrSync ? this.devicesHmrFiles[device.id] : this.devicesAppFiles[device.id];
                    if (!filesToSync || !filesToSync.length) {
                        this.$logger.info(`Skipping files sync for device ${this.getDeviceDisplayName(device)}. The changes are already batch transferred in a previous sync.`);
                        currentSyncDeferPromise.resolve();
                        return;
                    }
                    this.devicesHmrFiles[device.id] = [];
                    this.devicesAppFiles[device.id] = [];
                    if (canExecuteHmrSync) {
                        this.$hmrStatusService.watchHmrStatus(device.id, hmrHash);
                        yield this.syncFilesForPlatformSafe(data, { filesToSync }, platform, device);
                        const status = yield this.$hmrStatusService.getHmrStatus(device.id, hmrHash);
                        if (!status) {
                            this.devicesCanExecuteHmr[device.id] = false;
                            this.$logger.warn(`Unable to get LiveSync status from the Preview app for device ${this.getDeviceDisplayName(device)}. Ensure the app is running in order to sync changes.`);
                        }
                        else {
                            this.devicesCanExecuteHmr[device.id] = status === constants_2.HmrConstants.HMR_SUCCESS_STATUS;
                        }
                    }
                    else {
                        const noHmrData = _.assign({}, data, { useHotModuleReload: false });
                        yield this.syncFilesForPlatformSafe(noHmrData, { filesToSync }, platform, device);
                        this.devicesCanExecuteHmr[device.id] = true;
                    }
                    currentSyncDeferPromise.resolve();
                }
                catch (e) {
                    currentSyncDeferPromise.resolve();
                }
            })));
        });
    }
    getDeviceDisplayName(device) {
        return `${device.name} (${device.id})`.cyan;
    }
    getInitialFilesForDeviceSafe(data, device) {
        return __awaiter(this, void 0, void 0, function* () {
            const platform = device.platform;
            this.$logger.info(`Start sending initial files for device ${this.getDeviceDisplayName(device)}.`);
            try {
                const payloads = this.$previewAppFilesService.getInitialFilesPayload(data, platform);
                this.$logger.info(`Successfully sent initial files for device ${this.getDeviceDisplayName(device)}.`);
                return payloads;
            }
            catch (err) {
                this.$logger.warn(`Unable to apply changes for device ${this.getDeviceDisplayName(device)}. Error is: ${err}, ${helpers_1.stringify(err)}`);
            }
        });
    }
    syncFilesForPlatformSafe(data, filesData, platform, device) {
        return __awaiter(this, void 0, void 0, function* () {
            const deviceId = device && device.id || "";
            try {
                const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform);
                payloads.deviceId = deviceId;
                if (payloads && payloads.files && payloads.files.length) {
                    this.$logger.info(`Start syncing changes for device ${this.getDeviceDisplayName(device)}.`);
                    yield this.$previewSdkService.applyChanges(payloads);
                    this.$logger.info(`Successfully synced '${payloads.files.map(filePayload => filePayload.file.yellow)}' for device ${this.getDeviceDisplayName(device)}.`);
                }
            }
            catch (error) {
                this.$logger.warn(`Unable to apply changes for device ${this.getDeviceDisplayName(device)}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`);
                this.emit(preview_app_constants_1.PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, {
                    error,
                    data,
                    deviceId
                });
            }
        });
    }
}
__decorate([
    decorators_1.performanceLog()
], PreviewAppController.prototype, "handlePrepareReadyEvent", null);
exports.PreviewAppController = PreviewAppController;
$injector.register("previewAppController", PreviewAppController);
