"use strict";
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 preview_app_constants_1 = require("./preview-app-constants");
const constants_1 = require("../../../constants");
const constants_2 = require("../../../common/constants");
const isTextOrBinary = require('istextorbinary');
class PreviewAppLiveSyncService {
    constructor($fs, $errors, $hooksService, $logger, $platformService, $platformsData, $projectDataService, $previewSdkService, $previewAppPluginsService, $projectFilesManager, $hmrStatusService, $projectFilesProvider) {
        this.$fs = $fs;
        this.$errors = $errors;
        this.$hooksService = $hooksService;
        this.$logger = $logger;
        this.$platformService = $platformService;
        this.$platformsData = $platformsData;
        this.$projectDataService = $projectDataService;
        this.$previewSdkService = $previewSdkService;
        this.$previewAppPluginsService = $previewAppPluginsService;
        this.$projectFilesManager = $projectFilesManager;
        this.$hmrStatusService = $hmrStatusService;
        this.$projectFilesProvider = $projectFilesProvider;
        this.excludedFileExtensions = [".ts", ".sass", ".scss", ".less"];
        this.excludedFiles = [".DS_Store"];
        this.deviceInitializationPromise = {};
    }
    initialize(data) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$previewSdkService.initialize((device) => __awaiter(this, void 0, void 0, function* () {
                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.initializePreviewForDevice(data, device);
                try {
                    const payloads = yield this.deviceInitializationPromise[device.id];
                    return payloads;
                }
                finally {
                    this.deviceInitializationPromise[device.id] = null;
                }
            }));
        });
    }
    initializePreviewForDevice(data, device) {
        return __awaiter(this, void 0, void 0, function* () {
            const filesToSyncMap = {};
            const hmrData = {};
            let promise = Promise.resolve(null);
            const startSyncFilesTimeout = (platform) => __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 filesToSync = _.cloneDeep(filesToSyncMap[platform]);
                    promise = this.syncFilesForPlatformSafe(data, platform, { filesToSync, skipPrepare: true, useHotModuleReload: data.appFilesUpdaterOptions.useHotModuleReload });
                    yield promise;
                    if (data.appFilesUpdaterOptions.useHotModuleReload && platformHmrData.hash) {
                        const devices = _.filter(this.$previewSdkService.connectedDevices, { platform: platform.toLowerCase() });
                        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) {
                                yield this.syncFilesForPlatformSafe(data, platform, { filesToSync: platformHmrData.fallbackFiles, useHotModuleReload: false, deviceId: previewDevice.id });
                            }
                        })));
                    }
                }));
                filesToSyncMap[platform] = [];
            });
            yield this.$hooksService.executeBeforeHooks("preview-sync", {
                hookArgs: {
                    projectData: this.$projectDataService.getProjectData(data.projectDir),
                    hmrData,
                    config: {
                        env: data.env,
                        platform: device.platform,
                        appFilesUpdaterOptions: data.appFilesUpdaterOptions,
                    },
                    externals: this.$previewAppPluginsService.getExternalPlugins(device),
                    filesToSyncMap,
                    startSyncFilesTimeout: startSyncFilesTimeout.bind(this)
                }
            });
            yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
            const payloads = yield this.syncFilesForPlatformSafe(data, device.platform, { isInitialSync: true, useHotModuleReload: data.appFilesUpdaterOptions.useHotModuleReload });
            return payloads;
        });
    }
    syncFiles(data, filesToSync, filesToRemove) {
        return __awaiter(this, void 0, void 0, function* () {
            this.showWarningsForNativeFiles(filesToSync);
            for (const device of this.$previewSdkService.connectedDevices) {
                yield this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
            }
            const platforms = _(this.$previewSdkService.connectedDevices)
                .map(device => device.platform)
                .uniq()
                .value();
            for (const platform of platforms) {
                yield this.syncFilesForPlatformSafe(data, platform, { filesToSync, filesToRemove, useHotModuleReload: data.appFilesUpdaterOptions.useHotModuleReload });
            }
        });
    }
    stopLiveSync() {
        return __awaiter(this, void 0, void 0, function* () {
            this.$previewSdkService.stop();
        });
    }
    syncFilesForPlatformSafe(data, platform, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info(`Start syncing changes for platform ${platform}.`);
            opts = opts || {};
            let payloads = null;
            try {
                const { appFilesUpdaterOptions, env, projectDir } = data;
                const projectData = this.$projectDataService.getProjectData(projectDir);
                const platformData = this.$platformsData.getPlatformData(platform, projectData);
                if (!opts.skipPrepare) {
                    yield this.preparePlatform(platform, appFilesUpdaterOptions, env, projectData);
                }
                if (opts.isInitialSync) {
                    const platformsAppFolderPath = path.join(platformData.appDestinationDirectoryPath, constants_1.APP_FOLDER_NAME);
                    opts.filesToSync = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath);
                    payloads = this.getFilesPayload(platformData, projectData, opts);
                    this.$logger.info(`Successfully synced changes for platform ${platform}.`);
                }
                else {
                    opts.filesToSync = _.map(opts.filesToSync, file => this.$projectFilesProvider.mapFilePath(file, platformData.normalizedPlatformName, projectData));
                    payloads = this.getFilesPayload(platformData, projectData, opts);
                    yield this.$previewSdkService.applyChanges(payloads);
                    this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`);
                }
                return payloads;
            }
            catch (err) {
                this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${JSON.stringify(err, null, 2)}.`);
            }
        });
    }
    getFilesPayload(platformData, projectData, opts) {
        const { filesToSync, filesToRemove, deviceId } = opts;
        const filesToTransfer = filesToSync
            .filter(file => file.indexOf(constants_1.TNS_MODULES_FOLDER_NAME) === -1)
            .filter(file => file.indexOf(constants_1.APP_RESOURCES_FOLDER_NAME) === -1)
            .filter(file => !_.includes(this.excludedFiles, path.basename(file)))
            .filter(file => !_.includes(this.excludedFileExtensions, path.extname(file)));
        this.$logger.trace(`Transferring ${filesToTransfer.join("\n")}.`);
        const payloadsToSync = filesToTransfer.map(file => this.createFilePayload(file, platformData, projectData, preview_app_constants_1.PreviewSdkEventNames.CHANGE_EVENT_NAME));
        const payloadsToRemove = _.map(filesToRemove, file => this.createFilePayload(file, platformData, projectData, preview_app_constants_1.PreviewSdkEventNames.UNLINK_EVENT_NAME));
        const payloads = payloadsToSync.concat(payloadsToRemove);
        const hmrMode = opts.useHotModuleReload ? 1 : 0;
        return { files: payloads, platform: platformData.normalizedPlatformName.toLowerCase(), hmrMode, deviceId };
    }
    preparePlatform(platform, appFilesUpdaterOptions, env, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const nativePrepare = { skipNativePrepare: true };
            const config = {};
            const platformTemplate = null;
            const prepareInfo = {
                platform,
                appFilesUpdaterOptions,
                env,
                projectData,
                nativePrepare,
                config,
                platformTemplate,
                skipCopyTnsModules: true,
                skipCopyAppResourcesFiles: true
            };
            yield this.$platformService.preparePlatform(prepareInfo);
        });
    }
    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.`));
    }
    createFilePayload(file, platformData, projectData, event) {
        const projectFileInfo = this.$projectFilesProvider.getProjectFileInfo(file, platformData.normalizedPlatformName, null);
        const binary = isTextOrBinary.isBinarySync(file);
        let fileContents = "";
        let filePath = "";
        if (event === preview_app_constants_1.PreviewSdkEventNames.CHANGE_EVENT_NAME) {
            const relativePath = path.relative(path.join(platformData.appDestinationDirectoryPath, constants_1.APP_FOLDER_NAME), file);
            filePath = path.join(path.dirname(relativePath), projectFileInfo.onDeviceFileName);
            if (binary) {
                const bitmap = this.$fs.readFile(file);
                const base64 = Buffer.from(bitmap).toString('base64');
                fileContents = base64;
            }
            else {
                fileContents = this.$fs.readText(path.join(path.dirname(projectFileInfo.filePath), projectFileInfo.onDeviceFileName));
            }
        }
        else if (event === preview_app_constants_1.PreviewSdkEventNames.UNLINK_EVENT_NAME) {
            filePath = path.relative(path.join(projectData.projectDir, constants_1.APP_FOLDER_NAME), file);
        }
        const filePayload = {
            event,
            file: filePath,
            binary,
            fileContents
        };
        return filePayload;
    }
}
exports.PreviewAppLiveSyncService = PreviewAppLiveSyncService;
$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService);
