"use strict";
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.LogSourceMapService = void 0;
const path = require("path");
const util = require("util");
const _ = require("lodash");
const sourcemap = require("source-map");
const sourceMapConverter = require("convert-source-map");
const semver = require("semver");
const helpers_1 = require("../common/helpers");
const constants_1 = require("../constants");
const yok_1 = require("../common/yok");
class LogSourceMapService {
    get $platformsDataService() {
        return this.$injector.resolve("platformsDataService");
    }
    constructor($fs, $projectDataService, $injector, $devicePlatformsConstants, $logger) {
        this.$fs = $fs;
        this.$projectDataService = $projectDataService;
        this.$injector = $injector;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$logger = $logger;
        this.cache = {};
        this.originalFilesLocationCache = {};
        this.getProjectData = _.memoize(this.$projectDataService.getProjectData.bind(this.$projectDataService));
        this.getRuntimeVersion = _.memoize(this.getRuntimeVersionCore, (...args) => args.join(LogSourceMapService.MEMOIZE_FUNCTION_RANDOM_KEY_FOR_JOIN));
    }
    setSourceMapConsumerForFile(filePath) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (!this.$fs.getFsStats(filePath).isDirectory()) {
                    const mapFile = filePath + ".map";
                    let sourceMapRaw;
                    const source = this.$fs.readText(filePath);
                    if (this.$fs.exists(mapFile)) {
                        sourceMapRaw = sourceMapConverter.fromMapFileSource(source, (filename) => {
                            return this.$fs.readText(path.join(path.dirname(filePath), filename));
                        });
                    }
                    else {
                        sourceMapRaw = sourceMapConverter.fromSource(source);
                    }
                    let smc = null;
                    if (sourceMapRaw && sourceMapRaw.sourcemap) {
                        const sourceMap = sourceMapRaw.sourcemap;
                        smc = yield sourcemap.SourceMapConsumer.with(sourceMap, null, (c) => {
                            return c;
                        });
                    }
                    this.cache[filePath] = smc;
                }
            }
            catch (err) {
                this.$logger.trace(`Unable to set sourceMapConsumer for file ${filePath}. Error is: ${err}`);
            }
        });
    }
    replaceWithOriginalFileLocations(platform, messageData, loggingOptions) {
        if (!messageData || !loggingOptions || !loggingOptions.projectDir) {
            return messageData;
        }
        const projectData = this.getProjectData(loggingOptions.projectDir);
        const lines = messageData.split("\n");
        const isAndroid = platform.toLowerCase() ===
            this.$devicePlatformsConstants.Android.toLowerCase();
        const parserFunction = isAndroid
            ? this.parseAndroidLog.bind(this, projectData)
            : this.parseIosLog.bind(this);
        let outputData = "";
        lines.forEach((rawLine) => {
            const parsedLine = parserFunction(rawLine);
            const originalLocation = this.getOriginalFileLocation(platform, parsedLine, projectData);
            if (originalLocation && originalLocation.sourceFile) {
                const runtimeVersion = this.getRuntimeVersion(loggingOptions.projectDir, platform);
                const { sourceFile, line, column } = originalLocation;
                if (semver.valid(runtimeVersion) &&
                    semver.gte(semver.coerce(runtimeVersion), "6.1.0")) {
                    const lastIndexOfFile = rawLine.lastIndexOf(LogSourceMapService.FILE_PREFIX);
                    const firstPart = rawLine.substr(0, lastIndexOfFile);
                    outputData +=
                        firstPart +
                            rawLine
                                .substring(lastIndexOfFile)
                                .replace(/file:\/\/\/.+?:\d+:\d+/, `${LogSourceMapService.FILE_PREFIX_REPLACEMENT}${sourceFile}:${line}:${column}`) +
                            "\n";
                }
                else {
                    outputData = `${outputData}${parsedLine.messagePrefix}${LogSourceMapService.FILE_PREFIX_REPLACEMENT}${sourceFile}:${line}:${column}${parsedLine.messageSuffix}\n`;
                }
            }
            else if (rawLine !== "") {
                outputData = `${outputData}${rawLine}\n`;
            }
        });
        return outputData;
    }
    getRuntimeVersionCore(projectDir, platform) {
        let runtimeVersion = null;
        try {
            const projectData = this.getProjectData(projectDir);
            const platformData = this.$platformsDataService.getPlatformData(platform, projectData);
            const runtimeVersionData = this.$projectDataService.getRuntimePackage(projectData.projectDir, platformData.platformNameLowerCase);
            runtimeVersion = runtimeVersionData && runtimeVersionData.version;
        }
        catch (err) {
            this.$logger.trace(`Unable to get runtime version for project directory: ${projectDir} and platform ${platform}. Error is: `, err);
        }
        return runtimeVersion;
    }
    getOriginalFileLocation(platform, parsedLine, projectData) {
        const fileLocation = path.join(this.getFilesLocation(platform, projectData), constants_1.APP_FOLDER_NAME);
        if (parsedLine && parsedLine.filePath) {
            const sourceMapFile = path.join(fileLocation, parsedLine.filePath);
            const smc = this.cache[sourceMapFile];
            if (smc) {
                const originalPosition = smc.originalPositionFor({
                    line: parsedLine.line,
                    column: parsedLine.column,
                });
                let sourceFile = originalPosition.source &&
                    originalPosition.source.replace("webpack:///", "");
                if (sourceFile) {
                    const appPath = projectData.getAppDirectoryRelativePath();
                    if (!_.startsWith(sourceFile, constants_1.NODE_MODULES_FOLDER_NAME) &&
                        !_.startsWith(sourceFile, appPath + "/")) {
                        sourceFile = path.join(appPath, sourceFile);
                    }
                    sourceFile = (0, helpers_1.stringReplaceAll)(sourceFile, "/", path.sep);
                    if (!this.originalFilesLocationCache[sourceFile]) {
                        const { dir, ext, name } = path.parse(sourceFile);
                        const platformSpecificName = `${name}.${platform.toLowerCase()}`;
                        const platformSpecificFile = path.format({
                            dir,
                            ext,
                            name: platformSpecificName,
                        });
                        if (this.$fs.exists(path.join(projectData.projectDir, platformSpecificFile))) {
                            this.originalFilesLocationCache[sourceFile] =
                                platformSpecificFile;
                        }
                        else {
                            this.originalFilesLocationCache[sourceFile] = sourceFile;
                        }
                    }
                    return {
                        sourceFile: this.originalFilesLocationCache[sourceFile],
                        line: originalPosition.line,
                        column: originalPosition.column,
                    };
                }
            }
        }
    }
    parseAndroidLog(projectData, rawMessage) {
        const fileIndex = rawMessage.lastIndexOf(LogSourceMapService.FILE_PREFIX);
        const deviceProjectPath = util.format(constants_1.ANDROID_DEVICE_APP_ROOT_TEMPLATE, projectData.projectIdentifiers.android);
        let separator = ",";
        let messageSuffix = "";
        let parts, filePath, line, column, messagePrefix;
        if (fileIndex >= 0) {
            const fileSubstring = rawMessage.substring(fileIndex + LogSourceMapService.FILE_PREFIX.length);
            parts = fileSubstring.split(separator);
            if (parts.length >= 3) {
                parts[0] = parts[0].replace("'", "");
                parts[1] = parts[1].replace(" line: ", "");
                parts[2] = parts[2].replace(" column: ", "");
            }
            else {
                separator = ":";
                parts = fileSubstring.split(separator);
            }
            if (parts.length >= 3) {
                const devicePath = `${deviceProjectPath}/${constants_1.APP_FOLDER_NAME}/`;
                filePath = path.relative(devicePath, `${"/"}${parts[0]}`);
                line = parseInt(parts[1]);
                column = parseInt(parts[2]);
                messagePrefix = rawMessage.substring(0, fileIndex);
                for (let i = 3; i < parts.length; i++) {
                    messageSuffix += `${parts[i]}${i === parts.length - 1 ? "" : separator}`;
                }
                messagePrefix = _.trimEnd(messagePrefix, "(");
            }
        }
        return { filePath, line, column, messagePrefix, messageSuffix };
    }
    parseIosLog(rawMessage) {
        const fileIndex = rawMessage.lastIndexOf(LogSourceMapService.FILE_PREFIX);
        let messageSuffix = "";
        let parts, filePath, line, column, messagePrefix;
        if (fileIndex >= 0) {
            const fileSubstring = rawMessage.substring(fileIndex + LogSourceMapService.FILE_PREFIX.length);
            parts = fileSubstring.split(":");
            if (parts && parts.length >= 3) {
                filePath = parts[0];
                if (_.startsWith(filePath, constants_1.APP_FOLDER_NAME)) {
                    filePath = path.relative(constants_1.APP_FOLDER_NAME, parts[0]);
                }
                line = parseInt(parts[1]);
                column = parseInt(parts[2]);
                messagePrefix = rawMessage.substring(0, fileIndex);
                for (let i = 3; i < parts.length; i++) {
                    messageSuffix += `${parts[i]}${i === parts.length - 1 ? "" : ":"}`;
                }
            }
        }
        return { filePath, line, column, messagePrefix, messageSuffix };
    }
    getFilesLocation(platform, projectData) {
        try {
            const platformsData = this.$platformsDataService.getPlatformData(platform.toLowerCase(), projectData);
            return platformsData.appDestinationDirectoryPath;
        }
        catch (err) {
            return "";
        }
    }
}
LogSourceMapService.FILE_PREFIX = "file:///";
LogSourceMapService.FILE_PREFIX_REPLACEMENT = "file: ";
LogSourceMapService.MEMOIZE_FUNCTION_RANDOM_KEY_FOR_JOIN = "__some_random_value__";
exports.LogSourceMapService = LogSourceMapService;
yok_1.injector.register("logSourceMapService", LogSourceMapService);
