"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.Errors = exports.installUncaughtExceptionListener = void 0;
const util = require("util");
const path = require("path");
const _ = require("lodash");
const source_map_1 = require("source-map");
const helpers_1 = require("./helpers");
const decorators_1 = require("./decorators");
const yok_1 = require("./yok");
function Exception() {
}
Exception.prototype = new Error();
function resolveCallStack(error) {
    return __awaiter(this, void 0, void 0, function* () {
        const stackLines = error.stack.split("\n");
        const parsed = _.map(stackLines, (line) => {
            let match = line.match(/^\s*at ([^(]*) \((.*?):([0-9]+):([0-9]+)\)$/);
            if (match) {
                return match;
            }
            match = line.match(/^\s*at (.*?):([0-9]+):([0-9]+)$/);
            if (match) {
                match.splice(1, 0, "<anonymous>");
                return match;
            }
            return line;
        });
        const fs = require("fs");
        const remapped = yield Promise.all(_.map(parsed, (parsedLine) => __awaiter(this, void 0, void 0, function* () {
            if (_.isString(parsedLine)) {
                return parsedLine;
            }
            const functionName = parsedLine[1];
            const fileName = parsedLine[2];
            const line = +parsedLine[3];
            const column = +parsedLine[4];
            const mapFileName = fileName + ".map";
            if (!fs.existsSync(mapFileName)) {
                return parsedLine.input;
            }
            const mapData = JSON.parse(fs.readFileSync(mapFileName).toString());
            return yield source_map_1.SourceMapConsumer.with(mapData, null, (consumer) => {
                const sourcePos = consumer.originalPositionFor({
                    line: line,
                    column: column,
                });
                if (sourcePos && sourcePos.source) {
                    const source = path.join(path.dirname(fileName), sourcePos.source);
                    return util.format("    at %s (%s:%s:%s)", functionName, source, sourcePos.line, sourcePos.column);
                }
                return util.format("    at %s (%s:%s:%s)", functionName, fileName, line, column);
            });
        })));
        let outputMessage = remapped.join("\n");
        if (outputMessage.indexOf(error.message) === -1) {
            outputMessage = outputMessage.replace(/Error/, "Error: " + error.message);
        }
        return outputMessage;
    });
}
function installUncaughtExceptionListener(actionOnException) {
    const handler = (err) => __awaiter(this, void 0, void 0, function* () {
        try {
            let callstack = err.stack;
            if (callstack) {
                try {
                    callstack = yield resolveCallStack(err);
                }
                catch (err) {
                    console.error("Error while resolving callStack:", err);
                }
            }
            console.error(callstack || err.toString());
            yield tryTrackException(err, yok_1.injector);
            if (actionOnException) {
                actionOnException();
            }
        }
        catch (err) {
            process.exit(131);
        }
    });
    process.on("uncaughtException", handler);
    process.on("unhandledRejection", handler);
}
exports.installUncaughtExceptionListener = installUncaughtExceptionListener;
function tryTrackException(error, localInjector) {
    return __awaiter(this, void 0, void 0, function* () {
        let disableAnalytics;
        try {
            disableAnalytics = localInjector.resolve("staticConfig").disableAnalytics;
        }
        catch (err) {
            disableAnalytics = true;
        }
        if (!disableAnalytics) {
            try {
                const analyticsService = localInjector.resolve("analyticsService");
                yield analyticsService.trackException(error, error.message);
            }
            catch (e) {
                console.error("Error while reporting exception: " + e);
            }
        }
    });
}
class Errors {
    constructor($injector) {
        this.$injector = $injector;
        this.printCallStack = false;
    }
    fail(optsOrFormatStr, ...args) {
        const opts = this.getFailOptions(optsOrFormatStr);
        return this.failWithOptions(opts, false, ...args);
    }
    failWithoutHelp(optsOrFormatStr, ...args) {
        return this.fail(optsOrFormatStr, ...args);
    }
    failWithHelp(optsOrFormatStr, ...args) {
        const opts = this.getFailOptions(optsOrFormatStr);
        return this.failWithOptions(opts, true, ...args);
    }
    getFailOptions(optsOrFormatStr) {
        let opts = optsOrFormatStr;
        if (_.isString(opts)) {
            opts = { formatStr: opts };
        }
        return opts;
    }
    failWithOptions(opts, suggestCommandHelp, ...args) {
        const argsArray = args || [];
        const exception = new Exception();
        exception.name = opts.name || "Exception";
        exception.message = util.format.apply(null, [opts.formatStr].concat(argsArray));
        try {
            const $messagesService = this.$injector.resolve("messagesService");
            exception.message = $messagesService.getMessage.apply($messagesService, [opts.formatStr].concat(argsArray));
        }
        catch (err) {
        }
        exception.stack = new Error(exception.message).stack;
        exception.errorCode = opts.errorCode || 127;
        exception.suggestCommandHelp = suggestCommandHelp;
        exception.proxyAuthenticationRequired = !!opts.proxyAuthenticationRequired;
        exception.printOnStdout = opts.printOnStdout;
        this.$injector.resolve("logger").trace(opts.formatStr);
        throw exception;
    }
    beginCommand(action, printCommandHelpSuggestion) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield action();
            }
            catch (ex) {
                const logger = this.$injector.resolve("logger");
                const loggerLevel = logger.getLevel().toUpperCase();
                const printCallStack = this.printCallStack ||
                    loggerLevel === "TRACE" ||
                    loggerLevel === "DEBUG";
                const message = printCallStack
                    ? yield resolveCallStack(ex)
                    : (0, helpers_1.isInteractive)()
                        ? `\x1B[31;1m${ex.message}\x1B[0m`
                        : ex.message;
                if (ex.printOnStdout) {
                    logger.info(message);
                }
                else {
                    logger.error(message);
                }
                if (ex.suggestCommandHelp) {
                    yield printCommandHelpSuggestion();
                }
                yield tryTrackException(ex, this.$injector);
                process.exit(_.isNumber(ex.errorCode) ? ex.errorCode : 127);
            }
        });
    }
    verifyHeap(message) {
        if (global.gc) {
            console.log("verifyHeap: '%s'", message);
            global.gc();
        }
    }
}
__decorate([
    (0, decorators_1.deprecated)("Use `fail` instead.")
], Errors.prototype, "failWithoutHelp", null);
exports.Errors = Errors;
yok_1.injector.register("errors", Errors);
