"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = exports.Logger = exports.ServerFormatter = exports.BrowserFormatter = exports.Formatter = exports.doLog = exports.field = exports.time = exports.Time = exports.Field = exports.Level = void 0;
/**
 * Numeric log level. A higher number is higher severity.
 */
var Level;
(function (Level) {
    Level[Level["Trace"] = 0] = "Trace";
    Level[Level["Debug"] = 1] = "Debug";
    Level[Level["Info"] = 2] = "Info";
    Level[Level["Warn"] = 3] = "Warn";
    Level[Level["Error"] = 4] = "Error";
})(Level = exports.Level || (exports.Level = {}));
/**
 * A message field.
 */
class Field {
    constructor(identifier, value) {
        this.identifier = identifier;
        this.value = value;
    }
    toJSON() {
        return {
            identifier: this.identifier,
            value: this.value,
        };
    }
}
exports.Field = Field;
/**
 * A time field.
 */
class Time {
    constructor(expected, ms) {
        this.expected = expected;
        this.ms = ms;
    }
}
exports.Time = Time;
/**
 * Log how long something took. Call this before doing the thing then pass it
 * into the logger after finished to log the time it took.
 */
exports.time = (expected) => {
    return new Time(expected, Date.now());
};
/**
 * A field to show with the message.
 */
exports.field = (name, value) => {
    return new Field(name, value);
};
function doLog(level, ...args) {
    switch (level) {
        case Level.Trace:
            return console.trace(...args);
        case Level.Debug:
            return console.debug(...args);
        case Level.Info:
            return console.info(...args);
        case Level.Warn:
            return console.warn(...args);
        case Level.Error:
            return console.error(...args);
    }
}
exports.doLog = doLog;
/**
 * Format and build a *single* log entry at a time.
 */
class Formatter {
    /**
     * formatType is used for the strings returned from style() and reset().
     */
    constructor(formatType = "%s", colors = true) {
        this.formatType = formatType;
        this.colors = colors;
        this.minimumTagWidth = 5;
        this.message = this.newMessage();
    }
    /**
     * Return a new/empty message.
     */
    newMessage() {
        return {
            format: "",
            args: [],
            fields: [],
        };
    }
    /**
     * Add a tag.
     */
    tag(name, color) {
        for (let i = name.length; i < this.minimumTagWidth; ++i) {
            name += " ";
        }
        this.push(name + " ", color);
    }
    push(arg, color, weight) {
        if (Array.isArray(arg) && arg.every((a) => a instanceof Field)) {
            return void this.message.fields.push(...arg);
        }
        if (this.colors && (color || weight)) {
            this.message.format += `${this.formatType}${this.getType(arg)}${this.formatType}`;
            this.message.args.push(this.style(color, weight), arg, this.reset());
        }
        else {
            this.message.format += `${this.getType(arg)}`;
            this.message.args.push(arg);
        }
    }
    /**
     * Write everything out and reset state.
     */
    write(level) {
        const message = this.flush();
        this.doWrite(level, message);
    }
    /**
     * Return current values and reset state.
     */
    flush() {
        const message = this.message;
        this.message = this.newMessage();
        return message;
    }
    /**
     * Get the format string for the value type.
     */
    getType(arg) {
        switch (typeof arg) {
            case "object":
                return "%o";
            case "number":
                return "%d";
            default:
                return "%s";
        }
    }
}
exports.Formatter = Formatter;
/**
 * Display logs in the browser using CSS in the output. Fields are displayed on
 * individual lines within a group.
 */
class BrowserFormatter extends Formatter {
    constructor() {
        super("%c");
    }
    style(color, weight) {
        return (color ? `color: ${color};` : "") + (weight ? `font-weight: ${weight};` : "");
    }
    reset() {
        return this.style("inherit", "normal");
    }
    doWrite(level, message) {
        console.groupCollapsed(message.format, ...message.args);
        message.fields.forEach((field) => {
            this.push(field.identifier, "#3794ff", "bold");
            if (typeof field.value !== "undefined" &&
                field.value.constructor &&
                field.value.constructor.name) {
                this.push(` (${field.value.constructor.name})`);
            }
            this.push(": ");
            this.push(field.value);
            const m = this.flush();
            doLog(level, m.format, ...m.args);
        });
        console.groupEnd();
    }
}
exports.BrowserFormatter = BrowserFormatter;
/**
 * Display logs on the command line using ANSI color codes. Fields are displayed
 * in a single stringified object inline.
 */
class ServerFormatter extends Formatter {
    constructor() {
        super("%s", !!process.stdout.isTTY);
    }
    style(color, weight) {
        return (weight === "bold" ? "\u001B[1m" : "") + (color ? this.hex(color) : "");
    }
    reset() {
        return "\u001B[0m";
    }
    hex(hex) {
        const [r, g, b] = this.hexToRgb(hex);
        return `\u001B[38;2;${r};${g};${b}m`;
    }
    hexToRgb(hex) {
        const integer = parseInt(hex.substr(1), 16);
        return [(integer >> 16) & 0xff, (integer >> 8) & 0xff, integer & 0xff];
    }
    doWrite(level, message) {
        if (message.fields.length === 0) {
            return doLog(level, "[%s] " + message.format, new Date().toISOString(), ...message.args);
        }
        const obj = {};
        message.fields.forEach((field) => (obj[field.identifier] = field.value));
        doLog(level, "[%s] " + message.format + " %s%s%s", new Date().toISOString(), ...message.args, this.style("#8c8c8c"), JSON.stringify(obj), this.reset());
    }
}
exports.ServerFormatter = ServerFormatter;
class Logger {
    constructor(_formatter, name, defaultFields, extenders = []) {
        this._formatter = _formatter;
        this.name = name;
        this.defaultFields = defaultFields;
        this.extenders = extenders;
        this.level = Level.Info;
        this.muted = false;
        if (name) {
            this.nameColor = this.hashStringToColor(name);
        }
        if (typeof process !== "undefined" && typeof process.env !== "undefined") {
            switch (process.env.LOG_LEVEL) {
                case "trace":
                    this.level = Level.Trace;
                    break;
                case "debug":
                    this.level = Level.Debug;
                    break;
                case "info":
                    this.level = Level.Info;
                    break;
                case "warn":
                    this.level = Level.Warn;
                    break;
                case "error":
                    this.level = Level.Error;
                    break;
            }
        }
    }
    set formatter(formatter) {
        this._formatter = formatter;
    }
    /**
     * Supresses all output
     */
    mute() {
        this.muted = true;
    }
    extend(extender) {
        this.extenders.push(extender);
    }
    info(message, ...fields) {
        this.handle({
            message,
            fields,
            tagColor: "#008FBF",
            level: Level.Info,
        });
    }
    warn(message, ...fields) {
        this.handle({
            message,
            fields,
            tagColor: "#FF9D00",
            level: Level.Warn,
        });
    }
    trace(message, ...fields) {
        this.handle({
            message,
            fields,
            tagColor: "#888888",
            level: Level.Trace,
        });
    }
    debug(message, ...fields) {
        this.handle({
            message,
            fields,
            tagColor: "#84009E",
            level: Level.Debug,
        });
    }
    error(message, ...fields) {
        this.handle({
            message,
            fields,
            tagColor: "#B00000",
            level: Level.Error,
        });
    }
    /**
     * Returns a sub-logger with a name.
     * Each name is deterministically generated a color.
     */
    named(name, ...fields) {
        const l = new Logger(this._formatter, name, fields, this.extenders);
        if (this.muted) {
            l.mute();
        }
        return l;
    }
    handle(message) {
        if (this.level > message.level || this.muted) {
            return;
        }
        let passedFields = message.fields || [];
        if (typeof message.message === "function") {
            const values = message.message();
            message.message = values.shift();
            passedFields = values;
        }
        const fields = (this.defaultFields
            ? passedFields.filter((f) => !!f).concat(this.defaultFields)
            : passedFields.filter((f) => !!f));
        const now = Date.now();
        let times = [];
        const hasFields = fields && fields.length > 0;
        if (hasFields) {
            times = fields.filter((f) => f.value instanceof Time);
            this._formatter.push(fields);
        }
        this._formatter.tag(Level[message.level].toLowerCase(), message.tagColor);
        if (this.name && this.nameColor) {
            this._formatter.tag(this.name, this.nameColor);
        }
        this._formatter.push(message.message);
        if (times.length > 0) {
            times.forEach((time) => {
                const diff = now - time.value.ms;
                const expPer = diff / time.value.expected;
                const min = 125 * (1 - expPer);
                const max = 125 + min;
                const green = expPer < 1 ? max : min;
                const red = expPer >= 1 ? max : min;
                this._formatter.push(` ${time.identifier}=`, "#3794ff");
                this._formatter.push(`${diff}ms`, this.rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0));
            });
        }
        this._formatter.write(message.level);
        this.extenders.forEach((extender) => {
            extender(Object.assign({ section: this.name }, message));
        });
    }
    /**
     * Hashes a string.
     */
    djb2(str) {
        let hash = 5381;
        for (let i = 0; i < str.length; i++) {
            hash = (hash << 5) + hash + str.charCodeAt(i); /* hash * 33 + c */
        }
        return hash;
    }
    rgbToHex(r, g, b) {
        const integer = ((Math.round(r) & 0xff) << 16) + ((Math.round(g) & 0xff) << 8) + (Math.round(b) & 0xff);
        const str = integer.toString(16);
        return "#" + "000000".substring(str.length) + str;
    }
    /**
     * Generates a deterministic color from a string using hashing.
     */
    hashStringToColor(str) {
        const hash = this.djb2(str);
        return this.rgbToHex((hash & 0xff0000) >> 16, (hash & 0x00ff00) >> 8, hash & 0x0000ff);
    }
}
exports.Logger = Logger;
exports.logger = new Logger(typeof process === "undefined" || typeof process.stdout === "undefined"
    ? new BrowserFormatter()
    : new ServerFormatter());
//# sourceMappingURL=logger.js.map