import chalk from "chalk";
import { getConsoleLogPrefix } from "../utils/getConsoleLogPrefix.js";
import { LlamaLogLevel } from "./types.js";
const LlamaLogLevelToAddonLogLevel = new Map([
    [LlamaLogLevel.disabled, 0],
    [LlamaLogLevel.fatal, 1],
    [LlamaLogLevel.error, 2],
    [LlamaLogLevel.warn, 3],
    [LlamaLogLevel.info, 4],
    [LlamaLogLevel.debug, 5]
]);
const addonLogLevelToLlamaLogLevel = new Map([...LlamaLogLevelToAddonLogLevel.entries()].map(([key, value]) => [value, key]));
const defaultLogLevel = 5;
export class Llama {
    /** @internal */ _bindings;
    /** @internal */ _metal;
    /** @internal */ _cuda;
    /** @internal */ _buildType;
    /** @internal */ _cmakeOptions;
    /** @internal */ _llamaCppRelease;
    /** @internal */ _logger;
    /** @internal */ _logLevel;
    /** @internal */ _pendingLog = null;
    /** @internal */ _pendingLogLevel = null;
    /** @internal */ _logDispatchQueuedMicrotasks = 0;
    /** @internal */ _previousLog = null;
    /** @internal */ _previousLogLevel = null;
    /** @internal */ _nextLogNeedNewLine = false;
    constructor({ bindings, metal, cuda, logLevel, logger, buildType, cmakeOptions, llamaCppRelease }) {
        this._bindings = bindings;
        this._metal = metal;
        this._cuda = cuda;
        this._logLevel = logLevel ?? LlamaLogLevel.debug;
        this._logger = logger;
        this._buildType = buildType;
        this._cmakeOptions = Object.freeze({ ...cmakeOptions });
        this._llamaCppRelease = Object.freeze({
            repo: llamaCppRelease.repo,
            release: llamaCppRelease.release
        });
        this._dispatchPendingLogMicrotask = this._dispatchPendingLogMicrotask.bind(this);
        this._onAddonLog = this._onAddonLog.bind(this);
        this._bindings.setLogger(this._onAddonLog);
        this._bindings.setLoggerLogLevel(LlamaLogLevelToAddonLogLevel.get(this._logLevel) ?? defaultLogLevel);
    }
    get metal() {
        return this._metal;
    }
    get cuda() {
        return this._cuda;
    }
    get logLevel() {
        return this._logLevel;
    }
    set logLevel(value) {
        if (value === this._logLevel)
            return;
        this._bindings.setLoggerLogLevel(LlamaLogLevelToAddonLogLevel.get(value) ?? defaultLogLevel);
        this._logLevel = value;
    }
    get logger() {
        return this._logger;
    }
    set logger(value) {
        this._logger = value;
        if (value !== Llama.defaultConsoleLogger)
            this._nextLogNeedNewLine = false;
    }
    get buildType() {
        return this._buildType;
    }
    get cmakeOptions() {
        return this._cmakeOptions;
    }
    get llamaCppRelease() {
        return this._llamaCppRelease;
    }
    get systemInfo() {
        return this._bindings.systemInfo();
    }
    /** @internal */
    _onAddonLog(level, message) {
        const llamaLogLevel = addonLogLevelToLlamaLogLevel.get(level) ?? LlamaLogLevel.fatal;
        if (this._pendingLog != null && this._pendingLogLevel != null && this._pendingLogLevel != llamaLogLevel) {
            this._callLogger(this._pendingLogLevel, this._pendingLog);
            this._pendingLog = null;
        }
        const sourceMessage = (this._pendingLog ?? "") + message;
        const lastNewLineIndex = sourceMessage.lastIndexOf("\n");
        const currentLog = lastNewLineIndex < 0
            ? sourceMessage
            : sourceMessage.slice(0, lastNewLineIndex);
        const nextLog = lastNewLineIndex < 0
            ? ""
            : sourceMessage.slice(lastNewLineIndex + 1);
        if (currentLog !== "")
            this._callLogger(llamaLogLevel, currentLog);
        if (nextLog !== "") {
            this._pendingLog = nextLog;
            this._pendingLogLevel = llamaLogLevel;
            queueMicrotask(this._dispatchPendingLogMicrotask);
            this._logDispatchQueuedMicrotasks++;
        }
        else
            this._pendingLog = null;
    }
    /** @internal */
    _dispatchPendingLogMicrotask() {
        this._logDispatchQueuedMicrotasks--;
        if (this._logDispatchQueuedMicrotasks !== 0)
            return;
        if (this._pendingLog != null && this._pendingLogLevel != null) {
            this._callLogger(this._pendingLogLevel, this._pendingLog);
            this._pendingLog = null;
        }
    }
    /** @internal */
    _callLogger(level, message) {
        // llama.cpp uses dots to indicate progress, so we don't want to print them as different lines,
        // and instead, append to the same log line
        if (logMessageIsOnlyDots(message) && this._logger === Llama.defaultConsoleLogger) {
            if (logMessageIsOnlyDots(this._previousLog) && level === this._previousLogLevel) {
                process.stdout.write(message);
            }
            else {
                this._nextLogNeedNewLine = true;
                process.stdout.write(prefixAndColorMessage(message, getColorForLogLevel(level)));
            }
        }
        else {
            if (this._nextLogNeedNewLine) {
                process.stdout.write("\n");
                this._nextLogNeedNewLine = false;
            }
            try {
                this._logger(level, message);
            }
            catch (err) {
                // the native addon code calls this function, so there's no use to throw an error here
            }
        }
        this._previousLog = message;
        this._previousLogLevel = level;
    }
    /** @internal */
    static async _create({ bindings, buildType, buildMetadata, logLevel, logger }) {
        return new Llama({
            bindings,
            buildType,
            metal: buildMetadata.buildOptions.computeLayers.metal,
            cuda: buildMetadata.buildOptions.computeLayers.cuda,
            cmakeOptions: buildMetadata.buildOptions.customCmakeOptions,
            llamaCppRelease: {
                repo: buildMetadata.buildOptions.llamaCpp.repo,
                release: buildMetadata.buildOptions.llamaCpp.release
            },
            logLevel,
            logger
        });
    }
    static defaultConsoleLogger(level, message) {
        switch (level) {
            case LlamaLogLevel.disabled:
                break;
            case LlamaLogLevel.fatal:
                // we don't use console.error here because it prints the stack trace
                console.warn(prefixAndColorMessage(message, getColorForLogLevel(level)));
                break;
            case LlamaLogLevel.error:
                // we don't use console.error here because it prints the stack trace
                console.warn(prefixAndColorMessage(message, getColorForLogLevel(level)));
                break;
            case LlamaLogLevel.warn:
                console.warn(prefixAndColorMessage(message, getColorForLogLevel(level)));
                break;
            case LlamaLogLevel.info:
                console.info(prefixAndColorMessage(message, getColorForLogLevel(level)));
                break;
            case LlamaLogLevel.debug:
                console.debug(prefixAndColorMessage(message, getColorForLogLevel(level)));
                break;
            default:
                void (level);
                console.warn(getConsoleLogPrefix() + getColorForLogLevel(LlamaLogLevel.warn)(`Unknown log level: ${level}`));
                console.log(prefixAndColorMessage(message, getColorForLogLevel(level)));
        }
    }
}
function getColorForLogLevel(level) {
    switch (level) {
        case LlamaLogLevel.disabled: return chalk.whiteBright;
        case LlamaLogLevel.fatal: return chalk.redBright;
        case LlamaLogLevel.error: return chalk.red;
        case LlamaLogLevel.warn: return chalk.yellow;
        case LlamaLogLevel.info: return chalk.whiteBright;
        case LlamaLogLevel.debug: return chalk.white;
        default:
            void (level);
            return chalk.whiteBright;
    }
}
function prefixAndColorMessage(message, color) {
    return getConsoleLogPrefix() + (message
        .split("\n")
        .map(line => color(line))
        .join("\n" + getConsoleLogPrefix()));
}
function logMessageIsOnlyDots(message) {
    if (message == null)
        return false;
    for (let i = 0; i < message.length; i++) {
        if (message[i] !== ".")
            return false;
    }
    return true;
}
//# sourceMappingURL=Llama.js.map