"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.RunController = void 0;
const constants_1 = require("../common/constants");
const constants_2 = require("../constants");
const decorators_1 = require("../common/decorators");
const events_1 = require("events");
const util = require("util");
const _ = require("lodash");
const yok_1 = require("../common/yok");
class RunController extends events_1.EventEmitter {
    constructor($analyticsService, $buildController, $debugController, $deviceInstallAppService, $devicesService, $errors, $injector, $hmrStatusService, $hooksService, $liveSyncServiceResolver, $liveSyncProcessDataService, $logger, $mobileHelper, $platformsDataService, $pluginsService, $prepareController, $prepareDataService, $prepareNativePlatformService, $projectChangesService, $projectDataService) {
        super();
        this.$analyticsService = $analyticsService;
        this.$buildController = $buildController;
        this.$debugController = $debugController;
        this.$deviceInstallAppService = $deviceInstallAppService;
        this.$devicesService = $devicesService;
        this.$errors = $errors;
        this.$injector = $injector;
        this.$hmrStatusService = $hmrStatusService;
        this.$hooksService = $hooksService;
        this.$liveSyncServiceResolver = $liveSyncServiceResolver;
        this.$liveSyncProcessDataService = $liveSyncProcessDataService;
        this.$logger = $logger;
        this.$mobileHelper = $mobileHelper;
        this.$platformsDataService = $platformsDataService;
        this.$pluginsService = $pluginsService;
        this.$prepareController = $prepareController;
        this.$prepareDataService = $prepareDataService;
        this.$prepareNativePlatformService = $prepareNativePlatformService;
        this.$projectChangesService = $projectChangesService;
        this.$projectDataService = $projectDataService;
        this.prepareReadyEventHandler = null;
    }
    run(runData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { liveSyncInfo, deviceDescriptors } = runData;
            const { projectDir } = liveSyncInfo;
            const projectData = this.$projectDataService.getProjectData(projectDir);
            yield this.initializeSetup(projectData);
            const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors);
            const newPlatforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors);
            const oldPlatforms = this.$liveSyncProcessDataService.getPlatforms(projectDir);
            const platforms = _.uniq(_.concat(newPlatforms, oldPlatforms));
            this.$liveSyncProcessDataService.persistData(projectDir, deviceDescriptors, platforms);
            const shouldStartWatcher = !liveSyncInfo.skipWatcher &&
                this.$liveSyncProcessDataService.hasDeviceDescriptors(projectDir);
            if (shouldStartWatcher && liveSyncInfo.useHotModuleReload) {
                this.$hmrStatusService.attachToHmrStatusEvent();
            }
            if (!this.prepareReadyEventHandler) {
                const handler = (data) => __awaiter(this, void 0, void 0, function* () {
                    if (data.hasNativeChanges) {
                        const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData);
                        const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, data.platform, Object.assign(Object.assign({}, liveSyncInfo), { watch: !liveSyncInfo.skipWatcher }));
                        const changesInfo = yield this.$projectChangesService.checkForChanges(platformData, projectData, prepareData);
                        if (changesInfo.hasChanges) {
                            yield this.syncChangedDataOnDevices(data, projectData, liveSyncInfo);
                        }
                    }
                    else {
                        yield this.syncChangedDataOnDevices(data, projectData, liveSyncInfo);
                    }
                });
                this.prepareReadyEventHandler = handler.bind(this);
                this.$prepareController.on(constants_2.PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler);
            }
            yield this.syncInitialDataOnDevices(projectData, liveSyncInfo, deviceDescriptorsForInitialSync);
            this.attachDeviceLostHandler();
        });
    }
    stop(data) {
        return __awaiter(this, void 0, void 0, function* () {
            const { projectDir, deviceIdentifiers, stopOptions } = data;
            const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir);
            if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) {
                // In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it),
                // so we cannot await it as this will cause infinite loop.
                const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions;
                const deviceIdentifiersToRemove = deviceIdentifiers && deviceIdentifiers.length
                    ? deviceIdentifiers
                    : _.map(liveSyncProcessInfo.deviceDescriptors, (d) => d.identifier);
                const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, (descriptor) => _.includes(deviceIdentifiersToRemove, descriptor.identifier)).map((descriptor) => descriptor.identifier);
                // Handle the case when no more devices left for any of the persisted platforms
                for (let i = 0; i < liveSyncProcessInfo.platforms.length; i++) {
                    const platform = liveSyncProcessInfo.platforms[i];
                    const devices = this.$devicesService.getDevicesForPlatform(platform);
                    if (!devices || !devices.length) {
                        yield this.$prepareController.stopWatchers(projectDir, platform);
                    }
                }
                // In case deviceIdentifiers are not passed, we should stop the whole LiveSync.
                if (!deviceIdentifiers ||
                    !deviceIdentifiers.length ||
                    !liveSyncProcessInfo.deviceDescriptors ||
                    !liveSyncProcessInfo.deviceDescriptors.length) {
                    if (liveSyncProcessInfo.timer) {
                        clearTimeout(liveSyncProcessInfo.timer);
                    }
                    for (let k = 0; k < liveSyncProcessInfo.platforms.length; k++) {
                        yield this.$prepareController.stopWatchers(projectDir, liveSyncProcessInfo.platforms[k]);
                    }
                    liveSyncProcessInfo.isStopped = true;
                    if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) {
                        yield liveSyncProcessInfo.actionsChain;
                    }
                    liveSyncProcessInfo.deviceDescriptors = [];
                    if (this.prepareReadyEventHandler) {
                        this.$prepareController.removeListener(constants_2.PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler);
                        this.prepareReadyEventHandler = null;
                    }
                    const projectData = this.$projectDataService.getProjectData(projectDir);
                    yield this.$hooksService.executeAfterHooks("watch", {
                        hookArgs: {
                            projectData,
                        },
                    });
                }
                else if (liveSyncProcessInfo.currentSyncAction &&
                    shouldAwaitPendingOperation) {
                    yield liveSyncProcessInfo.currentSyncAction;
                }
                // Emit RunOnDevice stopped when we've really stopped.
                _.each(removedDeviceIdentifiers, (deviceIdentifier) => {
                    this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceStopped, {
                        projectDir,
                        deviceIdentifier,
                        keepProcessAlive: stopOptions === null || stopOptions === void 0 ? void 0 : stopOptions.keepProcessAlive,
                    });
                });
                if (stopOptions === null || stopOptions === void 0 ? void 0 : stopOptions.keepProcessAlive) {
                    this.removeAllListeners(constants_2.RunOnDeviceEvents.runOnDeviceStopped);
                }
            }
        });
    }
    getDeviceDescriptors(data) {
        return this.$liveSyncProcessDataService.getDeviceDescriptors(data.projectDir);
    }
    refreshApplication(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, fullSyncAction) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = deviceDescriptor.debuggingEnabled
                ? yield this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor)
                : yield this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, undefined, fullSyncAction);
            const device = liveSyncResultInfo.deviceAppData.device;
            this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceExecuted, {
                projectDir: projectData.projectDir,
                deviceIdentifier: device.deviceInfo.identifier,
                applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
                syncedFiles: liveSyncResultInfo.modifiedFilesData.map((m) => m.getLocalPath()),
                isFullSync: liveSyncResultInfo.isFullSync,
            });
            return result;
        });
    }
    refreshApplicationWithDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor) {
        return __awaiter(this, void 0, void 0, function* () {
            const debugOptions = deviceDescriptor.debugOptions || {};
            liveSyncResultInfo.waitForDebugger = !!debugOptions.debugBrk;
            liveSyncResultInfo.forceRefreshWithSocket = true;
            const refreshInfo = yield this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, {
                shouldSkipEmitLiveSyncNotification: true,
                shouldCheckDeveloperDiscImage: true,
            });
            // we do not stop the application when debugBrk is false, so we need to attach, instead of launch
            // if we try to send the launch request, the debugger port will not be printed and the command will timeout
            debugOptions.start = !debugOptions.debugBrk;
            debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart;
            yield this.$debugController.enableDebuggingCoreWithoutWaitingCurrentAction(projectData.projectDir, deviceDescriptor.identifier, debugOptions);
            return refreshInfo;
        });
    }
    refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings, fullSyncAction) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = { didRestart: false };
            const platform = liveSyncResultInfo.deviceAppData.platform;
            const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()];
            const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platform);
            try {
                const isFullSync = filesChangeEventData &&
                    (filesChangeEventData.hasNativeChanges ||
                        !filesChangeEventData.hasOnlyHotUpdateFiles);
                let shouldRestart = isFullSync;
                if (!shouldRestart) {
                    shouldRestart = yield platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo);
                }
                if (!shouldRestart) {
                    shouldRestart = !(yield platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo));
                }
                if (!isFullSync && shouldRestart && fullSyncAction) {
                    this.$logger.trace(`Syncing all files as the current app state does not support hot updates.`);
                    liveSyncResultInfo.didRecover = true;
                    yield fullSyncAction();
                }
                if (shouldRestart) {
                    this.emit(constants_2.DEBUGGER_DETACHED_EVENT_NAME, {
                        deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
                    });
                    yield platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo);
                    result.didRestart = true;
                }
            }
            catch (err) {
                this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`);
                const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`;
                this.$logger.warn(msg);
                const device = liveSyncResultInfo.deviceAppData.device;
                const deviceIdentifier = device.deviceInfo.identifier;
                if (!settings || !settings.shouldSkipEmitLiveSyncNotification) {
                    this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceNotification, {
                        projectDir: projectData.projectDir,
                        deviceIdentifier: device.deviceInfo.identifier,
                        applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
                        notification: msg,
                    });
                }
                if (settings &&
                    settings.shouldCheckDeveloperDiscImage &&
                    (err.message || err) === "Could not find developer disk image") {
                    const attachDebuggerOptions = {
                        platform: device.deviceInfo.platform,
                        isEmulator: device.isEmulator,
                        projectDir: projectData.projectDir,
                        deviceIdentifier,
                        debugOptions: deviceDescriptor.debugOptions,
                        outputPath: deviceDescriptor.buildData.outputPath,
                    };
                    this.emit(constants_2.USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions);
                }
            }
            return result;
        });
    }
    getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors) {
        const currentRunData = this.$liveSyncProcessDataService.getPersistedData(projectDir);
        const isAlreadyLiveSyncing = currentRunData && !currentRunData.isStopped;
        // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D.
        const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing
            ? _.differenceBy(deviceDescriptors, currentRunData.deviceDescriptors, "identifier")
            : deviceDescriptors;
        return deviceDescriptorsForInitialSync;
    }
    initializeSetup(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                yield this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
            }
            catch (err) {
                this.$logger.trace(err);
                this.$errors.fail(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`);
            }
        });
    }
    attachDeviceLostHandler() {
        this.$devicesService.on(constants_1.DeviceDiscoveryEventNames.DEVICE_LOST, (device) => __awaiter(this, void 0, void 0, function* () {
            this.$logger.trace(`Received ${constants_1.DeviceDiscoveryEventNames.DEVICE_LOST} event in LiveSync service for ${device.deviceInfo.identifier}. Will stop LiveSync operation for this device.`);
            for (const projectDir in this.$liveSyncProcessDataService.getAllPersistedData()) {
                try {
                    const deviceDescriptors = this.getDeviceDescriptors({ projectDir });
                    if (_.find(deviceDescriptors, (d) => d.identifier === device.deviceInfo.identifier)) {
                        yield this.stop({
                            projectDir,
                            deviceIdentifiers: [device.deviceInfo.identifier],
                        });
                    }
                }
                catch (err) {
                    this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err);
                }
            }
        }));
    }
    syncInitialDataOnDevices(projectData, liveSyncInfo, deviceDescriptors) {
        return __awaiter(this, void 0, void 0, function* () {
            const rebuiltInformation = {};
            const deviceAction = (device) => __awaiter(this, void 0, void 0, function* () {
                const deviceDescriptor = _.find(deviceDescriptors, (dd) => dd.identifier === device.deviceInfo.identifier);
                const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, Object.assign(Object.assign(Object.assign({}, liveSyncInfo), deviceDescriptor.buildData), { nativePrepare: {
                        skipNativePrepare: !!deviceDescriptor.skipNativePrepare,
                    }, watch: !liveSyncInfo.skipWatcher }));
                const prepareResultData = yield this.$prepareController.prepare(prepareData);
                const buildData = Object.assign(Object.assign({}, deviceDescriptor.buildData), { buildForDevice: !device.isEmulator });
                const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData);
                try {
                    let packageFilePath = null;
                    // Case where we have three devices attached, a change that requires build is found,
                    // we'll rebuild the app only for the first device, but we should install new package on all three devices.
                    if (rebuiltInformation[platformData.platformNameLowerCase] &&
                        (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) ||
                            rebuiltInformation[platformData.platformNameLowerCase]
                                .isEmulator === device.isEmulator)) {
                        packageFilePath =
                            rebuiltInformation[platformData.platformNameLowerCase]
                                .packageFilePath;
                        yield this.$deviceInstallAppService.installOnDevice(device, buildData, packageFilePath);
                    }
                    else {
                        const shouldBuild = prepareResultData.hasNativeChanges ||
                            buildData.nativePrepare.forceRebuildNativeApp ||
                            (yield this.$buildController.shouldBuild(buildData));
                        if (shouldBuild) {
                            packageFilePath = yield deviceDescriptor.buildAction();
                            rebuiltInformation[platformData.platformNameLowerCase] = {
                                isEmulator: device.isEmulator,
                                platform: platformData.platformNameLowerCase,
                                packageFilePath,
                            };
                        }
                        else {
                            yield this.$analyticsService.trackEventActionInGoogleAnalytics({
                                action: "LiveSync" /* TrackActionNames.LiveSync */,
                                device,
                                projectDir: projectData.projectDir,
                            });
                        }
                        yield this.$deviceInstallAppService.installOnDeviceIfNeeded(device, buildData, packageFilePath);
                    }
                    const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase);
                    const { force, useHotModuleReload, skipWatcher } = liveSyncInfo;
                    const liveSyncResultInfo = yield platformLiveSyncService.fullSync({
                        force,
                        useHotModuleReload,
                        projectData,
                        device,
                        watch: !skipWatcher,
                        liveSyncDeviceData: deviceDescriptor,
                    });
                    yield this.refreshApplication(projectData, liveSyncResultInfo, null, deviceDescriptor);
                    this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
                    this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceStarted, {
                        projectDir: projectData.projectDir,
                        deviceIdentifier: device.deviceInfo.identifier,
                        applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
                    });
                }
                catch (err) {
                    this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`);
                    this.$logger.trace(err);
                    this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceError, {
                        projectDir: projectData.projectDir,
                        deviceIdentifier: device.deviceInfo.identifier,
                        applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
                        error: err,
                    });
                    yield this.stop({
                        projectDir: projectData.projectDir,
                        deviceIdentifiers: [device.deviceInfo.identifier],
                        stopOptions: { shouldAwaitAllActions: false },
                    });
                }
            });
            yield this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device) => _.some(deviceDescriptors, (deviceDescriptor) => deviceDescriptor.identifier === device.deviceInfo.identifier)));
        });
    }
    syncChangedDataOnDevices(data, projectData, liveSyncInfo) {
        return __awaiter(this, void 0, void 0, function* () {
            const successfullySyncedMessageFormat = `Successfully synced application %s on device %s.`;
            const rebuiltInformation = {};
            const deviceAction = (device) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectData.projectDir);
                const deviceDescriptor = _.find(deviceDescriptors, (dd) => dd.identifier === device.deviceInfo.identifier);
                const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData);
                const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, Object.assign(Object.assign(Object.assign({}, liveSyncInfo), deviceDescriptor.buildData), { nativePrepare: {
                        skipNativePrepare: !!deviceDescriptor.skipNativePrepare,
                    }, watch: !liveSyncInfo.skipWatcher }));
                try {
                    const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform);
                    const allAppFiles = ((_b = (_a = data.hmrData) === null || _a === void 0 ? void 0 : _a.fallbackFiles) === null || _b === void 0 ? void 0 : _b.length)
                        ? data.hmrData.fallbackFiles
                        : data.files;
                    const filesToSync = data.hasOnlyHotUpdateFiles
                        ? data.files
                        : allAppFiles;
                    const watchInfo = {
                        liveSyncDeviceData: deviceDescriptor,
                        projectData,
                        // todo: remove stale files once everything is stable
                        // currently, watcher fires multiple times & may clean up unsynced files
                        // filesToRemove: data.staleFiles ?? [],
                        filesToRemove: [],
                        filesToSync,
                        hmrData: data.hmrData,
                        useHotModuleReload: liveSyncInfo.useHotModuleReload,
                        force: liveSyncInfo.force,
                        connectTimeout: 1000,
                    };
                    const deviceAppData = yield platformLiveSyncService.getAppData(_.merge({ device, watch: true }, watchInfo));
                    if (data.hasNativeChanges) {
                        const rebuiltInfo = rebuiltInformation[platformData.platformNameLowerCase] &&
                            (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) ||
                                rebuiltInformation[platformData.platformNameLowerCase]
                                    .isEmulator === device.isEmulator);
                        if (!rebuiltInfo) {
                            yield this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
                            yield deviceDescriptor.buildAction();
                            rebuiltInformation[platformData.platformNameLowerCase] = {
                                isEmulator: device.isEmulator,
                                platform: platformData.platformNameLowerCase,
                                packageFilePath: null,
                            };
                        }
                        yield this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, rebuiltInformation[platformData.platformNameLowerCase]
                            .packageFilePath);
                        yield platformLiveSyncService.syncAfterInstall(device, watchInfo);
                        yield this.refreshApplication(projectData, {
                            deviceAppData,
                            modifiedFilesData: [],
                            isFullSync: false,
                            useHotModuleReload: liveSyncInfo.useHotModuleReload,
                        }, data, deviceDescriptor);
                        this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier));
                    }
                    else {
                        const isInHMRMode = liveSyncInfo.useHotModuleReload &&
                            data.hmrData &&
                            data.hmrData.hash;
                        if (isInHMRMode) {
                            this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
                        }
                        const watchAction = () => __awaiter(this, void 0, void 0, function* () {
                            const liveSyncResultInfo = yield platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
                            const fullSyncAction = () => __awaiter(this, void 0, void 0, function* () {
                                watchInfo.filesToSync = allAppFiles;
                                const fullLiveSyncResultInfo = yield platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
                                // IMPORTANT: keep the same instance as we rely on side effects
                                _.assign(liveSyncResultInfo, fullLiveSyncResultInfo);
                            });
                            yield this.$hooksService.executeBeforeHooks("watchAction", {
                                hookArgs: {
                                    liveSyncResultInfo,
                                    filesToSync,
                                    allAppFiles,
                                    isInHMRMode,
                                    filesChangedEvent: data,
                                },
                            });
                            yield this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor, fullSyncAction);
                            if (!liveSyncResultInfo.didRecover && isInHMRMode) {
                                const status = yield this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
                                // the timeout is assumed OK as the app could be blocked on a breakpoint
                                if (status === constants_1.HmrConstants.HMR_ERROR_STATUS) {
                                    yield fullSyncAction();
                                    liveSyncResultInfo.isFullSync = true;
                                    yield this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor);
                                }
                            }
                            yield this.$hooksService.executeAfterHooks("watchAction", {
                                liveSyncResultInfo,
                                filesToSync,
                                allAppFiles,
                                filesChangedEvent: data,
                                isInHMRMode,
                            });
                            this.$logger.info(util.format(successfullySyncedMessageFormat, deviceAppData.appIdentifier, device.deviceInfo.identifier));
                        });
                        if (liveSyncInfo.useHotModuleReload) {
                            try {
                                this.$logger.trace("Try executing watch action without any preparation of files.");
                                yield watchAction();
                                this.$logger.trace("Successfully executed watch action without any preparation of files.");
                                return;
                            }
                            catch (err) {
                                this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`);
                            }
                        }
                        yield this.$deviceInstallAppService.installOnDeviceIfNeeded(device, deviceDescriptor.buildData);
                        watchInfo.connectTimeout = null;
                        yield watchAction();
                    }
                }
                catch (err) {
                    this.$logger.warn(`Unable to apply changes for device: ${device.deviceInfo.identifier}. Error is: ${err && err.message}.`);
                    this.emitCore(constants_2.RunOnDeviceEvents.runOnDeviceError, {
                        projectDir: projectData.projectDir,
                        deviceIdentifier: device.deviceInfo.identifier,
                        applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
                        error: err,
                    });
                    yield this.stop({
                        projectDir: projectData.projectDir,
                        deviceIdentifiers: [device.deviceInfo.identifier],
                        stopOptions: { shouldAwaitAllActions: false },
                    });
                }
            });
            yield this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device) => {
                const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectData.projectDir);
                return (data.platform.toLowerCase() ===
                    device.deviceInfo.platform.toLowerCase() &&
                    liveSyncProcessInfo &&
                    _.some(liveSyncProcessInfo.deviceDescriptors, (deviceDescriptor) => deviceDescriptor.identifier === device.deviceInfo.identifier));
            }));
        });
    }
    addActionToChain(projectDir, action) {
        return __awaiter(this, void 0, void 0, function* () {
            const liveSyncInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir);
            if (liveSyncInfo) {
                liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(() => __awaiter(this, void 0, void 0, function* () {
                    if (!liveSyncInfo.isStopped) {
                        liveSyncInfo.currentSyncAction = action();
                        const res = yield liveSyncInfo.currentSyncAction;
                        return res;
                    }
                }));
                const result = yield liveSyncInfo.actionsChain;
                return result;
            }
        });
    }
    emitCore(event, data) {
        this.$logger.trace(`Will emit event ${event} with data`, data);
        this.emit(event, data);
    }
}
__decorate([
    (0, decorators_1.performanceLog)()
], RunController.prototype, "refreshApplicationWithoutDebug", null);
__decorate([
    (0, decorators_1.cache)()
], RunController.prototype, "attachDeviceLostHandler", null);
exports.RunController = RunController;
yok_1.injector.register("runController", RunController);
//# sourceMappingURL=run-controller.js.map