"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 constants_1 = require("../../../constants");
const preview_app_constants_1 = require("./preview-app-constants");
const constants_2 = require("../../../common/constants");
const helpers_1 = require("../../../common/helpers");
const events_1 = require("events");
const decorators_1 = require("../../../common/decorators");
class PreviewAppLiveSyncService extends events_1.EventEmitter {
    constructor($errors, $hooksService, $logger, $platformsData, $projectDataService, $previewSdkService, $previewAppFilesService, $previewAppPluginsService, $previewDevicesService, $hmrStatusService) {
        super();
        this.$errors = $errors;
        this.$hooksService = $hooksService;
        this.$logger = $logger;
        this.$platformsData = $platformsData;
        this.$projectDataService = $projectDataService;
        this.$previewSdkService = $previewSdkService;
        this.$previewAppFilesService = $previewAppFilesService;
        this.$previewAppPluginsService = $previewAppPluginsService;
        this.$previewDevicesService = $previewDevicesService;
        this.$hmrStatusService = $hmrStatusService;
        this.deviceInitializationPromise = {};
    }
    initialize(data) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$previewSdkService.initialize((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 this.deviceInitializationPromise[device.id];
                    }
                    this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device);
                    try {
                        const payloads = yield this.deviceInitializationPromise[device.id];
                        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
                    });
                }
            }));
        });
    }
    syncFiles(data, filesToSync, filesToRemove) {
        return __awaiter(this, void 0, void 0, function* () {
            this.showWarningsForNativeFiles(filesToSync);
            const connectedDevices = this.$previewDevicesService.getConnectedDevices();
            for (const device of connectedDevices) {
                yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
            }
            const platforms = _(connectedDevices)
                .map(device => device.platform)
                .uniq()
                .value();
            for (const platform of platforms) {
                yield this.syncFilesForPlatformSafe(data, { filesToSync, filesToRemove }, platform);
            }
        });
    }
    stopLiveSync() {
        return __awaiter(this, void 0, void 0, function* () {
            this.$previewSdkService.stop();
            this.$previewDevicesService.updateConnectedDevices([]);
        });
    }
    getInitialFilesForDevice(data, device) {
        return __awaiter(this, void 0, void 0, function* () {
            const hookArgs = this.getHookArgs(data, device);
            yield this.$hooksService.executeBeforeHooks("preview-sync", { hookArgs });
            yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
            const payloads = yield this.getInitialFilesForPlatformSafe(data, device.platform);
            return payloads;
        });
    }
    getHookArgs(data, device) {
        const filesToSyncMap = {};
        const hmrData = {};
        const promise = Promise.resolve();
        const result = {
            projectData: this.$projectDataService.getProjectData(data.projectDir),
            hmrData,
            config: {
                env: data.env,
                platform: device.platform,
                appFilesUpdaterOptions: {
                    bundle: data.bundle,
                    useHotModuleReload: data.useHotModuleReload,
                    release: false
                },
            },
            externals: this.$previewAppPluginsService.getExternalPlugins(device),
            filesToSyncMap,
            startSyncFilesTimeout: (platform) => __awaiter(this, void 0, void 0, function* () { return yield this.onWebpackCompilationComplete(data, hmrData, filesToSyncMap, promise, platform); })
        };
        return result;
    }
    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,
                    platform,
                    deviceId
                });
            }
        });
    }
    onWebpackCompilationComplete(data, hmrData, filesToSyncMap, promise, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            yield promise
                .then(() => __awaiter(this, void 0, void 0, function* () {
                const currentHmrData = _.cloneDeep(hmrData);
                const platformHmrData = currentHmrData[platform] || {};
                const projectData = this.$projectDataService.getProjectData(data.projectDir);
                const platformData = this.$platformsData.getPlatformData(platform, projectData);
                const clonedFiles = _.cloneDeep(filesToSyncMap[platform]);
                const filesToSync = _.map(clonedFiles, fileToSync => {
                    const result = path.join(platformData.appDestinationDirectoryPath, constants_1.APP_FOLDER_NAME, path.relative(projectData.getAppDirectoryPath(), fileToSync));
                    return result;
                });
                promise = this.syncFilesForPlatformSafe(data, { filesToSync }, platform);
                yield 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;
                        }
                    })));
                }
            }));
            filesToSyncMap[platform] = [];
        });
    }
    showWarningsForNativeFiles(files) {
        _.filter(files, file => file.indexOf(constants_1.APP_RESOURCES_FOLDER_NAME) > -1)
            .forEach(file => this.$logger.warn(`Unable to apply changes from ${constants_1.APP_RESOURCES_FOLDER_NAME} folder. You need to build your application in order to make changes in ${constants_1.APP_RESOURCES_FOLDER_NAME} folder.`));
    }
}
__decorate([
    decorators_1.performanceLog()
], PreviewAppLiveSyncService.prototype, "initialize", null);
__decorate([
    decorators_1.performanceLog()
], PreviewAppLiveSyncService.prototype, "syncFiles", null);
__decorate([
    decorators_1.performanceLog()
], PreviewAppLiveSyncService.prototype, "onWebpackCompilationComplete", null);
exports.PreviewAppLiveSyncService = PreviewAppLiveSyncService;
$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService);
