"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, $errors, $hmrStatusService, $logger, $hooksService, $pluginsService, $prepareController, $previewAppFilesService, $previewAppPluginsService, $previewDevicesService, $previewQrCodeService, $previewSdkService, $prepareDataService, $projectDataService) {
        super();
        this.$analyticsService = $analyticsService;
        this.$errors = $errors;
        this.$hmrStatusService = $hmrStatusService;
        this.$logger = $logger;
        this.$hooksService = $hooksService;
        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.prepareReadyEventHandler = null;
        this.deviceInitializationPromise = {};
        this.promise = Promise.resolve();
    }
    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() {
        return __awaiter(this, void 0, void 0, function* () {
            this.$previewSdkService.stop();
            this.$previewDevicesService.updateConnectedDevices([]);
            if (this.prepareReadyEventHandler) {
                this.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* () {
                try {
                    if (!device) {
                        this.$errors.failWithoutHelp("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();
                    }
                    yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
                    if (!this.prepareReadyEventHandler) {
                        this.prepareReadyEventHandler = (currentPrepareData) => __awaiter(this, void 0, void 0, function* () {
                            yield this.handlePrepareReadyEvent(data, currentPrepareData);
                        });
                        this.$prepareController.on(constants_1.PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler.bind(this));
                    }
                    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 }));
                    yield this.$prepareController.prepare(prepareData);
                    try {
                        const payloads = yield this.getInitialFilesForPlatformSafe(data, device.platform);
                        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* () {
            yield this.promise
                .then(() => __awaiter(this, void 0, void 0, function* () {
                const { hmrData, files, platform } = currentPrepareData;
                const platformHmrData = _.cloneDeep(hmrData);
                this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform);
                yield this.promise;
                if (data.useHotModuleReload && platformHmrData.hash) {
                    const devices = this.$previewDevicesService.getDevicesForPlatform(platform);
                    yield Promise.all(_.map(devices, (previewDevice) => __awaiter(this, void 0, void 0, function* () {
                        const status = yield this.$hmrStatusService.getHmrStatus(previewDevice.id, platformHmrData.hash);
                        if (status === constants_2.HmrConstants.HMR_ERROR_STATUS) {
                            const originalUseHotModuleReload = data.useHotModuleReload;
                            data.useHotModuleReload = false;
                            yield this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id);
                            data.useHotModuleReload = originalUseHotModuleReload;
                        }
                    })));
                }
            }));
        });
    }
    getInitialFilesForPlatformSafe(data, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info(`Start sending initial files for platform ${platform}.`);
            try {
                const payloads = this.$previewAppFilesService.getInitialFilesPayload(data, platform);
                this.$logger.info(`Successfully sent initial files for platform ${platform}.`);
                return payloads;
            }
            catch (err) {
                this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${helpers_1.stringify(err)}`);
            }
        });
    }
    syncFilesForPlatformSafe(data, filesData, platform, deviceId) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform);
                if (payloads && payloads.files && payloads.files.length) {
                    this.$logger.info(`Start syncing changes for platform ${platform}.`);
                    yield this.$previewSdkService.applyChanges(payloads);
                    this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`);
                }
            }
            catch (error) {
                this.$logger.warn(`Unable to apply changes for platform ${platform}. 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);
