"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmulatorLogger = exports.Verbosity = void 0;
const clc = require("colorette");
const utils = require("../utils");
const logger_1 = require("../logger");
const types_1 = require("./types");
const utils_1 = require("../utils");
const TYPE_VERBOSITY = {
    DEBUG: 0,
    INFO: 1,
    BULLET: 1,
    SUCCESS: 1,
    USER: 2,
    WARN: 2,
    WARN_ONCE: 2,
    ERROR: 2,
};
var Verbosity;
(function (Verbosity) {
    Verbosity[Verbosity["DEBUG"] = 0] = "DEBUG";
    Verbosity[Verbosity["INFO"] = 1] = "INFO";
    Verbosity[Verbosity["QUIET"] = 2] = "QUIET";
})(Verbosity = exports.Verbosity || (exports.Verbosity = {}));
class EmulatorLogger {
    constructor(name, data = {}) {
        this.name = name;
        this.data = data;
    }
    static forEmulator(emulator) {
        return new EmulatorLogger(emulator, {
            metadata: {
                emulator: {
                    name: emulator,
                },
            },
        });
    }
    static forFunction(functionName, extensionLogInfo) {
        return new EmulatorLogger(types_1.Emulators.FUNCTIONS, {
            metadata: {
                emulator: {
                    name: types_1.Emulators.FUNCTIONS,
                },
                function: {
                    name: functionName,
                },
                extension: extensionLogInfo,
            },
        });
    }
    static forExtension(extensionLogInfo) {
        return new EmulatorLogger(types_1.Emulators.EXTENSIONS, {
            metadata: {
                emulator: {
                    name: types_1.Emulators.EXTENSIONS,
                },
                extension: extensionLogInfo,
            },
        });
    }
    log(type, text, data) {
        if (!data) {
            data = this.data;
        }
        if (EmulatorLogger.shouldSupress(type)) {
            logger_1.logger.debug(`${type}: ${text}`);
            return;
        }
        const mergedData = Object.assign(Object.assign({}, data), { metadata: Object.assign(Object.assign({}, data.metadata), { message: text }) });
        switch (type) {
            case "DEBUG":
                logger_1.logger.debug(text, mergedData);
                break;
            case "INFO":
                logger_1.logger.info(text, mergedData);
                break;
            case "USER":
                logger_1.logger.info(text, mergedData);
                break;
            case "BULLET":
                utils.logBullet(text, "info", mergedData);
                break;
            case "WARN":
                utils.logWarning(text, "warn", mergedData);
                break;
            case "WARN_ONCE":
                if (!EmulatorLogger.warnOnceCache.has(text)) {
                    utils.logWarning(text, "warn", mergedData);
                    EmulatorLogger.warnOnceCache.add(text);
                }
                break;
            case "SUCCESS":
                utils.logSuccess(text, "info", mergedData);
                break;
            case "ERROR":
                utils.logBullet(text, "error", mergedData);
                break;
        }
    }
    handleRuntimeLog(log, ignore = []) {
        if (ignore.includes(log.level)) {
            return;
        }
        switch (log.level) {
            case "SYSTEM":
                this.handleSystemLog(log);
                break;
            case "USER":
                this.log("USER", `${clc.blackBright("> ")} ${log.text}`, Object.assign({ user: (0, utils_1.tryParse)(log.text) }, this.data));
                break;
            case "DEBUG":
                if (log.data && Object.keys(log.data).length > 0) {
                    this.log("DEBUG", `[${log.type}] ${log.text} ${JSON.stringify(log.data)}`);
                }
                else {
                    this.log("DEBUG", `[${log.type}] ${log.text}`);
                }
                break;
            case "INFO":
                this.logLabeled("BULLET", "functions", log.text);
                break;
            case "WARN":
                this.logLabeled("WARN", "functions", log.text);
                break;
            case "WARN_ONCE":
                this.logLabeled("WARN_ONCE", "functions", log.text);
                break;
            case "FATAL":
                this.logLabeled("WARN", "functions", log.text);
                break;
            default:
                this.log("INFO", `${log.level}: ${log.text}`);
                break;
        }
    }
    handleSystemLog(systemLog) {
        switch (systemLog.type) {
            case "runtime-status":
                if (systemLog.text === "killed") {
                    this.log("WARN", `Your function was killed because it raised an unhandled error.`);
                }
                break;
            case "googleapis-network-access":
                this.log("WARN", `Google API requested!\n   - URL: "${systemLog.data.href}"\n   - Be careful, this may be a production service.`);
                break;
            case "unidentified-network-access":
                this.log("WARN", `External network resource requested!\n   - URL: "${systemLog.data.href}"\n - Be careful, this may be a production service.`);
                break;
            case "functions-config-missing-value":
                this.log("WARN_ONCE", `It looks like you're trying to access functions.config().${systemLog.data.key} but there is no value there. You can learn more about setting up config here: https://firebase.google.com/docs/functions/local-emulator`);
                break;
            case "non-default-admin-app-used":
                this.log("WARN", `Non-default "firebase-admin" instance created!\n   ` +
                    `- This instance will *not* be mocked and will access production resources.`);
                break;
            case "missing-module":
                this.log("WARN", `The Cloud Functions emulator requires the module "${systemLog.data.name}" to be installed as a ${systemLog.data.isDev ? "development dependency" : "dependency"}. To fix this, run "npm install ${systemLog.data.isDev ? "--save-dev" : "--save"} ${systemLog.data.name}" in your functions directory.`);
                break;
            case "uninstalled-module":
                this.log("WARN", `The Cloud Functions emulator requires the module "${systemLog.data.name}" to be installed. This package is in your package.json, but it's not available. \
You probably need to run "npm install" in your functions directory.`);
                break;
            case "out-of-date-module":
                this.log("WARN", `The Cloud Functions emulator requires the module "${systemLog.data.name}" to be version >${systemLog.data.minVersion} so your version is too old. \
You can probably fix this by running "npm install ${systemLog.data.name}@latest" in your functions directory.`);
                break;
            case "missing-package-json":
                this.log("WARN", `The Cloud Functions directory you specified does not have a "package.json" file, so we can't load it.`);
                break;
            case "function-code-resolution-failed":
                this.log("WARN", systemLog.data.error);
                const helper = ["We were unable to load your functions code. (see above)"];
                if (systemLog.data.isPotentially.wrong_directory) {
                    helper.push(`   - There is no "package.json" file in your functions directory.`);
                }
                if (systemLog.data.isPotentially.typescript) {
                    helper.push("   - It appears your code is written in Typescript, which must be compiled before emulation.");
                }
                if (systemLog.data.isPotentially.uncompiled) {
                    helper.push(`   - You may be able to run "npm run build" in your functions directory to resolve this.`);
                }
                utils.logWarning(helper.join("\n"), "warn", this.data);
                break;
            case "function-runtimeconfig-json-invalid":
                this.log("WARN", "Found .runtimeconfig.json but the JSON format is invalid.");
                break;
            default:
        }
    }
    logLabeled(type, labelOrText, text) {
        let label = labelOrText;
        if (text === undefined) {
            text = label;
            label = this.name;
        }
        if (EmulatorLogger.shouldSupress(type)) {
            logger_1.logger.debug(`[${label}] ${text}`);
            return;
        }
        const mergedData = Object.assign(Object.assign({}, this.data), { metadata: Object.assign(Object.assign({}, this.data.metadata), { message: text }) });
        switch (type) {
            case "DEBUG":
                logger_1.logger.debug(`[${label}] ${text}`);
                break;
            case "BULLET":
                utils.logLabeledBullet(label, text, "info", mergedData);
                break;
            case "SUCCESS":
                utils.logLabeledSuccess(label, text, "info", mergedData);
                break;
            case "WARN":
                utils.logLabeledWarning(label, text, "warn", mergedData);
                break;
            case "WARN_ONCE":
                if (!EmulatorLogger.warnOnceCache.has(text)) {
                    utils.logLabeledWarning(label, text, "warn", mergedData);
                    EmulatorLogger.warnOnceCache.add(text);
                }
                break;
            case "ERROR":
                utils.logLabeledError(label, text, "error", mergedData);
                break;
        }
    }
    static shouldSupress(type) {
        const typeVerbosity = TYPE_VERBOSITY[type];
        return EmulatorLogger.verbosity > typeVerbosity;
    }
}
exports.EmulatorLogger = EmulatorLogger;
EmulatorLogger.verbosity = Verbosity.DEBUG;
EmulatorLogger.warnOnceCache = new Set();
