"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 isTextOrBinary = require('istextorbinary');
class PreviewAppLiveSyncService {
    constructor($fs, $errors, $hooksService, $logger, $platformService, $platformsData, $projectDataService, $previewSdkService, $previewAppPluginsService, $projectFilesManager, $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.$projectFilesProvider = $projectFilesProvider;
        this.excludedFileExtensions = [".ts", ".sass", ".scss", ".less"];
        this.excludedFiles = [".DS_Store"];
    }
    initialize(data) {
        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.");
            }
            const filesToSyncMap = {};
            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* () {
                    promise = this.syncFilesForPlatformSafe(data, platform, filesToSyncMap[platform]);
                    yield promise;
                }));
                filesToSyncMap[platform] = [];
            });
            yield this.$hooksService.executeBeforeHooks("preview-sync", {
                hookArgs: {
                    projectData: this.$projectDataService.getProjectData(data.projectDir),
                    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);
            return payloads;
        }));
    }
    syncFiles(data, files) {
        return __awaiter(this, void 0, void 0, function* () {
            this.showWarningsForNativeFiles(files);
            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, files);
            }
        });
    }
    stopLiveSync() {
        return __awaiter(this, void 0, void 0, function* () {
            this.$previewSdkService.stop();
        });
    }
    syncFilesForPlatformSafe(data, platform, files) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info(`Start syncing changes for platform ${platform}.`);
            try {
                const { appFilesUpdaterOptions, env, projectDir } = data;
                const projectData = this.$projectDataService.getProjectData(projectDir);
                const platformData = this.$platformsData.getPlatformData(platform, projectData);
                yield this.preparePlatform(platform, appFilesUpdaterOptions, env, projectData);
                let result = null;
                if (files && files.length) {
                    result = yield this.applyChanges(platformData, projectData, files);
                }
                else {
                    result = yield this.getFilesPayload(platformData, projectData);
                }
                this.$logger.info(`Successfully synced changes for platform ${platform}.`);
                return result;
            }
            catch (err) {
                this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${JSON.stringify(err, null, 2)}.`);
            }
        });
    }
    applyChanges(platformData, projectData, files) {
        return __awaiter(this, void 0, void 0, function* () {
            const payloads = this.getFilesPayload(platformData, projectData, _(files).uniq().value());
            yield this.$previewSdkService.applyChanges(payloads);
            return payloads;
        });
    }
    getFilesPayload(platformData, projectData, files) {
        const appFolderPath = path.join(projectData.projectDir, constants_1.APP_FOLDER_NAME);
        const platformsAppFolderPath = path.join(platformData.appDestinationDirectoryPath, constants_1.APP_FOLDER_NAME);
        if (files && files.length) {
            files = files.map(file => path.join(platformsAppFolderPath, path.relative(appFolderPath, file)));
        }
        else {
            files = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath);
        }
        const filesToTransfer = files
            .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 payloads = filesToTransfer
            .map(file => {
            const projectFileInfo = this.$projectFilesProvider.getProjectFileInfo(file, platformData.normalizedPlatformName, null);
            const relativePath = path.relative(platformsAppFolderPath, file);
            const filePayload = {
                event: preview_app_constants_1.PreviewSdkEventNames.CHANGE_EVENT_NAME,
                file: path.join(path.dirname(relativePath), projectFileInfo.onDeviceFileName),
                binary: isTextOrBinary.isBinarySync(file),
                fileContents: ""
            };
            if (filePayload.binary) {
                const bitmap = this.$fs.readFile(file);
                const base64 = Buffer.from(bitmap).toString('base64');
                filePayload.fileContents = base64;
            }
            else {
                filePayload.fileContents = this.$fs.readText(path.join(path.dirname(projectFileInfo.filePath), projectFileInfo.onDeviceFileName));
            }
            return filePayload;
        });
        return { files: payloads, platform: platformData.normalizedPlatformName.toLowerCase() };
    }
    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.`));
    }
}
exports.PreviewAppLiveSyncService = PreviewAppLiveSyncService;
$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService);
