/** NakoLogger */
import { NakoError, NakoRuntimeError } from './nako_errors.mjs';
import { NakoColors } from './nako_colors.mjs';
/**
 * ログレベル - 数字が高いほど優先度が高い。
 */
export class LogLevel {
    // string to level no
    static fromS(levelStr) {
        let level = LogLevel.trace;
        switch (levelStr) {
            case 'all':
                level = LogLevel.all;
                break;
            case 'trace':
                level = LogLevel.trace;
                break;
            case 'debug':
                level = LogLevel.debug;
                break;
            case 'info':
                level = LogLevel.info;
                break;
            case 'warn':
                level = LogLevel.warn;
                break;
            case 'error':
                level = LogLevel.error;
                break;
            case 'stdout':
                level = LogLevel.stdout;
                break;
            default:
                throw new Error('[NakoLogger] unknown logger level:' + levelStr);
        }
        return level;
    }
    static toString(level) {
        const levels = ['all', 'trace', 'debug', 'info', 'warn', 'error', 'stdout'];
        return levels[level];
    }
}
// level no
LogLevel.all = 0;
LogLevel.trace = 1;
LogLevel.debug = 2;
LogLevel.info = 3;
LogLevel.warn = 4;
LogLevel.error = 5;
LogLevel.stdout = 6;
/**
 * エラー位置を日本語で表示する。
 * たとえば `stringifyPosition({ file: "foo.txt", line: 5 })` は `"foo.txt(6行目):"` を出力する。
 */
function stringifyPosition(p) {
    if (!p) {
        return '';
    }
    return `${p.file || ''}${p.line === undefined ? '' : `(${p.line + 1}行目): `}`;
}
/**
 * コンパイラのログ情報を出力するためのクラス。
 * trace(), debug(), info(), warn(), error() はそれぞれメッセージに `[警告]` などのタグとエラー位置の日本語表現を付けて表示する。
 * error() は引数にエラーオブジェクトを受け取ることもでき、その場合エラーオブジェクトからエラーメッセージとエラー位置が取り出される。
 */
export class NakoLogger {
    constructor() {
        this.listeners = [];
        this.logs = '';
        this.position = '';
    }
    getErrorLogs() {
        return [this.logs.replace(/\s+$/, ''), this.position];
    }
    clear() {
        this.logs = '';
        this.position = '';
    }
    /**
     * sendメソッドで送られた情報を受け取るコールバックを設定する。
     * @param levelStr
     * @param callback
     */
    addListener(levelStr, callback) {
        const level = LogLevel.fromS(levelStr);
        this.listeners.push({ level, callback });
    }
    /**
     * addListenerメソッドで設定したコールバックを取り外す。
     * @param {LogListener} callback
     */
    removeListener(callback) {
        this.listeners = this.listeners.filter((l) => l.callback !== callback);
    }
    /** 本体開発時のデバッグ情報（debugより更に詳細な情報）
     * @param {string} message
     * @param {Position | null} position
     */
    trace(message, position = null) {
        this.sendI(LogLevel.trace, `${NakoColors.color.bold}[デバッグ情報（詳細）]${NakoColors.color.reset}${stringifyPosition(position)}${message}`, position);
    }
    /** 本体開発時のデバッグ情報
     * @param {string} message
     * @param {Position | null} position
     */
    debug(message, position = null) {
        this.sendI(LogLevel.debug, `${NakoColors.color.bold}[デバッグ情報]${NakoColors.color.reset}${stringifyPosition(position)}${message}`, position);
    }
    /** ユーザープログラムのデバッグ情報（あまり重要ではないもの）
     * @param {string} message
     * @param {Position | null} position
     */
    info(message, position = null) {
        this.sendI(LogLevel.info, `${NakoColors.color.bold}${NakoColors.color.blue}[情報]${NakoColors.color.reset}${stringifyPosition(position)}${message}`, position);
    }
    /** ユーザープログラムのデバッグ情報（重要なもの）
     * @param {string} message
     * @param {Position | null} position
     */
    warn(message, position = null) {
        this.sendI(LogLevel.warn, `${NakoColors.color.bold}${NakoColors.color.green}[警告]${NakoColors.color.reset}${stringifyPosition(position)}${message}`, position);
    }
    /** エラーメッセージ
     * @param {string | Error} message
     * @param {Position | null} position
     */
    error(message, position = null) {
        // NakoErrorか判定 (`message instanceof NakoError`では判定できない場合がある)
        if (message instanceof Error && typeof message.type === 'string') {
            // NakoErrorか
            const etype = message.type;
            switch (etype) {
                case 'NakoRuntimeError':
                case 'NakoError':
                    if (message instanceof NakoError) {
                        const e = message;
                        let pos = position;
                        if (pos === null || pos === undefined) {
                            pos = { file: e.file, line: e.line || 0, startOffset: 0, endOffset: 0 };
                        }
                        this.sendI(LogLevel.error, e.message, pos);
                        return;
                    }
            }
        }
        if (message instanceof Error) {
            // 一般のエラーの場合は、messageのみ取得できる。
            message = message.message;
        }
        this.sendI(LogLevel.error, `${NakoColors.color.bold}${NakoColors.color.red}[エラー]${NakoColors.color.reset}${stringifyPosition(position)}${message}`, position);
    }
    /** RuntimeErrorを生成する */
    runtimeError(error, posStr) {
        const e = new NakoRuntimeError(error, posStr);
        return e;
    }
    /** ユーザープログラムのデバッグ情報（すべて)
     * @param {string} message
     * @param {Position | null} position
     */
    stdout(message, position = null) {
        this.sendI(LogLevel.stdout, `${message}`, position);
    }
    /** 指定したlevelのlistenerにメッセージを送る。htmlやbrowserConsoleは無ければnodeConsoleから生成する。 */
    send(levelStr, nodeConsole, position, html = null, browserConsole = null) {
        const i = LogLevel.fromS(levelStr);
        this.sendI(i, nodeConsole, position, html, browserConsole);
    }
    /** 指定したlevelのlistenerにメッセージを送る。htmlやbrowserConsoleは無ければnodeConsoleから生成する。 */
    sendI(level, nodeConsole, position, html = null, browserConsole = null) {
        const makeData = () => {
            // nodeConsoleからnoColor, nodeCondoleなどの形式を生成する。
            const formats = NakoColors.convertColorTextFormat(nodeConsole);
            // ログが複数行から構成される場合は、htmlでの表現にborderを設定する。
            let style = '';
            if (nodeConsole.includes('\n')) {
                style += 'border-top: 1px solid #8080806b; border-bottom: 1px solid #8080806b;';
            }
            // 各イベントリスナーが受け取るデータ
            const data = {
                noColor: formats.noColor,
                nodeConsole: formats.nodeConsole,
                browserConsole: browserConsole || formats.browserConsole,
                html: `<div style="${style}">` + (html || formats.html) + '</div>', // 各行を style: block で表示するために、<div>で囲む。
                level: LogLevel.toString(level),
                position
            };
            return data;
        };
        // エラーならログに追加
        if (level === LogLevel.error) {
            const data = makeData();
            this.logs += data.noColor + '\n';
            if (position && this.position !== null) {
                this.position = `l${position.line}:${position.file}`;
            }
        }
        // 登録したリスナーに通知する
        for (const l of this.listeners) {
            if (l.level <= level) {
                const data = makeData();
                l.callback(data);
            }
        }
    }
}
