import { format } from 'node:util';
import { dimmed, raw } from './style.js';

/**
 * Log level constants
 */
export const LOG_LEVEL = {
    NONE: 0,
    ERROR: 1,
    WARN: 2,
    INFO: 3,
    TRACE: 4,
    DEBUG: 5
};

/**
 * Map of log levels to style functions
 * that should be applied to logged message
 */
const STYLE = {
    [LOG_LEVEL.TRACE]: dimmed,
    [LOG_LEVEL.DEBUG]: dimmed
};

/**
 * Write message to trace stream and
 * conditionally to output stream, based
 * on the configuration of the logger.
 *
 * For non-TTY output streams the styling
 * will be removed, otherwise conditionally added.
 *
 * @param {Object} opts - logger options
 * @param {Array.<*>} args - values to format and write
 */
const write = (opts, args = []) => {
    let msg = format(...args);
    if (opts.to?.isTTY) {
        if (opts.colors) {
            msg = STYLE[opts.as]?.(msg) ?? msg;
        } else {
            msg = raw(msg);
        }
    } else {
        msg = raw(msg);
    }
    opts.trace?.write?.(JSON.stringify({
        t: Date.now(),
        l: opts.as,
        msg
    }) + '\n');
    (opts.level >= opts.as) && opts.to.write(msg + '\n');
};

/**
 * Create new instance of logger
 *
 * @param {Object} opts
 * @param {stream.Writable} opts.stdout - output stream for information messages
 * @param {stream.Writable} opts.stderr - output stream for error messages
 * @param {stream.Writable} [opts.trace] - output stream for tracing
 * @param {number | string} [opts.level] - log level
 * @param {boolean | string} [opts.colors] - use formatting characters to colorize output messages
 */
export const logger = (opts) => ({
    error: (...args) => write({as: LOG_LEVEL.ERROR, to: opts.stderr, ...opts }, args),
    warn: (...args) => write({as: LOG_LEVEL.WARN, to: opts.stderr, ...opts }, args),
    log: (...args) => write({as: LOG_LEVEL.INFO, to: opts.stderr, ...opts }, args),
    info: (...args) => write({as: LOG_LEVEL.INFO, to: opts.stdout, ...opts }, args),
    trace: (...args) => write({as: LOG_LEVEL.TRACE, to: opts.stderr, ...opts }, args),
    debug: (...args) => write({as: LOG_LEVEL.DEBUG, to: opts.stderr, ...opts }, args)
});
