"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadLlamaModelInfo = exports.initLlama = exports.loadModel = exports.toggleNativeLog = exports.MTMD_DEFAULT_MEDIA_MARKER = void 0;
exports.addNativeLogListener = addNativeLogListener;
const binding_1 = require("./binding");
__exportStar(require("./binding"), exports);
exports.MTMD_DEFAULT_MEDIA_MARKER = '<__media__>';
const mods = {};
const logListeners = [];
const logCallback = (level, text) => {
    logListeners.forEach((listener) => listener(level, text));
};
let logEnabled = false;
const refreshNativeLogSetup = () => {
    Object.entries(mods).forEach(([, mod]) => {
        mod.LlamaContext.toggleNativeLog(logEnabled, logCallback);
    });
};
const toggleNativeLog = (enable) => __awaiter(void 0, void 0, void 0, function* () {
    logEnabled = enable;
    refreshNativeLogSetup();
});
exports.toggleNativeLog = toggleNativeLog;
function addNativeLogListener(listener) {
    logListeners.push(listener);
    return {
        remove: () => {
            logListeners.splice(logListeners.indexOf(listener), 1);
        },
    };
}
const getJsonSchema = (responseFormat) => {
    var _a;
    if ((responseFormat === null || responseFormat === void 0 ? void 0 : responseFormat.type) === 'json_schema') {
        return (_a = responseFormat.json_schema) === null || _a === void 0 ? void 0 : _a.schema;
    }
    if ((responseFormat === null || responseFormat === void 0 ? void 0 : responseFormat.type) === 'json_object') {
        return responseFormat.schema || {};
    }
    return null;
};
class LlamaContextWrapper {
    constructor(nativeCtx) {
        this.ctx = nativeCtx;
    }
    getSystemInfo() {
        return this.ctx.getSystemInfo();
    }
    getModelInfo() {
        return this.ctx.getModelInfo();
    }
    isJinjaSupported() {
        const { minja } = this.ctx.getModelInfo().chatTemplates;
        return !!(minja === null || minja === void 0 ? void 0 : minja.toolUse) || !!(minja === null || minja === void 0 ? void 0 : minja.default);
    }
    isLlamaChatSupported() {
        return !!this.ctx.getModelInfo().chatTemplates.llamaChat;
    }
    _formatMediaChat(messages) {
        if (!messages)
            return {
                messages,
                has_media: false,
            };
        const mediaPaths = [];
        return {
            messages: messages.map((msg) => {
                if (Array.isArray(msg.content)) {
                    const content = msg.content.map((part) => {
                        var _a;
                        // Handle multimodal content
                        if (part.type === 'image_url') {
                            let path = ((_a = part.image_url) === null || _a === void 0 ? void 0 : _a.url) || '';
                            mediaPaths.push(path);
                            return {
                                type: 'text',
                                text: exports.MTMD_DEFAULT_MEDIA_MARKER,
                            };
                        }
                        else if (part.type === 'input_audio') {
                            const { input_audio: audio } = part;
                            if (!audio)
                                throw new Error('input_audio is required');
                            const { format } = audio;
                            if (format != 'wav' && format != 'mp3') {
                                throw new Error(`Unsupported audio format: ${format}`);
                            }
                            if (audio.url) {
                                const path = audio.url.replace(/file:\/\//, '');
                                mediaPaths.push(path);
                            }
                            else if (audio.data) {
                                mediaPaths.push(audio.data);
                            }
                            return {
                                type: 'text',
                                text: exports.MTMD_DEFAULT_MEDIA_MARKER,
                            };
                        }
                        return part;
                    });
                    return Object.assign(Object.assign({}, msg), { content });
                }
                return msg;
            }),
            has_media: mediaPaths.length > 0,
            media_paths: mediaPaths,
        };
    }
    getFormattedChat(messages, template, params) {
        const { messages: chat, has_media, media_paths, } = this._formatMediaChat(messages);
        const useJinja = this.isJinjaSupported() && (params === null || params === void 0 ? void 0 : params.jinja);
        let tmpl;
        if (template)
            tmpl = template; // Force replace if provided
        const jsonSchema = getJsonSchema(params === null || params === void 0 ? void 0 : params.response_format);
        const result = this.ctx.getFormattedChat(chat, tmpl, {
            jinja: useJinja,
            json_schema: jsonSchema,
            tools: params === null || params === void 0 ? void 0 : params.tools,
            parallel_tool_calls: params === null || params === void 0 ? void 0 : params.parallel_tool_calls,
            tool_choice: params === null || params === void 0 ? void 0 : params.tool_choice,
        });
        if (!useJinja) {
            return {
                type: 'llama-chat',
                prompt: result,
                has_media,
                media_paths,
            };
        }
        const jinjaResult = result;
        jinjaResult.type = 'jinja';
        jinjaResult.has_media = has_media;
        jinjaResult.media_paths = media_paths;
        return jinjaResult;
    }
    completion(options, callback) {
        const { messages, media_paths = options.media_paths } = this._formatMediaChat(options.messages);
        return this.ctx.completion(Object.assign(Object.assign({}, options), { messages, media_paths: options.media_paths || media_paths }), callback || (() => { }));
    }
    stopCompletion() {
        return this.ctx.stopCompletion();
    }
    tokenize(text, { media_paths } = {}) {
        return this.ctx.tokenize(text, media_paths);
    }
    detokenize(tokens) {
        return this.ctx.detokenize(tokens);
    }
    embedding(text) {
        return this.ctx.embedding(text);
    }
    saveSession(path) {
        return this.ctx.saveSession(path);
    }
    loadSession(path) {
        return this.ctx.loadSession(path);
    }
    release() {
        return this.ctx.release();
    }
    applyLoraAdapters(adapters) {
        return this.ctx.applyLoraAdapters(adapters);
    }
    removeLoraAdapters(adapters) {
        return this.ctx.removeLoraAdapters(adapters);
    }
    getLoadedLoraAdapters() {
        return this.ctx.getLoadedLoraAdapters();
    }
    initMultimodal(options) {
        return this.ctx.initMultimodal(options);
    }
    isMultimodalEnabled() {
        return this.ctx.isMultimodalEnabled();
    }
    releaseMultimodal() {
        return this.ctx.releaseMultimodal();
    }
    getMultimodalSupport() {
        return this.ctx.getMultimodalSupport();
    }
    initVocoder(options) {
        return this.ctx.initVocoder(options);
    }
    releaseVocoder() {
        return this.ctx.releaseVocoder();
    }
    isVocoderEnabled() {
        return this.ctx.isVocoderEnabled();
    }
    getFormattedAudioCompletion(speaker, text) {
        return this.ctx.getFormattedAudioCompletion(speaker, text);
    }
    getAudioCompletionGuideTokens(text) {
        return this.ctx.getAudioCompletionGuideTokens(text);
    }
    decodeAudioTokens(tokens) {
        return this.ctx.decodeAudioTokens(tokens);
    }
}
const loadModel = (options) => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b;
    const variant = (_a = options.lib_variant) !== null && _a !== void 0 ? _a : 'default';
    (_b = mods[variant]) !== null && _b !== void 0 ? _b : (mods[variant] = yield (0, binding_1.loadModule)(options.lib_variant));
    refreshNativeLogSetup();
    const nativeCtx = new mods[variant].LlamaContext(options);
    return new LlamaContextWrapper(nativeCtx);
});
exports.loadModel = loadModel;
exports.initLlama = binding_1.loadModule;
const modelInfoSkip = [
    // Large fields
    'tokenizer.ggml.tokens',
    'tokenizer.ggml.token_type',
    'tokenizer.ggml.merges',
    'tokenizer.ggml.scores',
];
const loadLlamaModelInfo = (path) => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const variant = 'default';
    (_a = mods[variant]) !== null && _a !== void 0 ? _a : (mods[variant] = yield (0, binding_1.loadModule)(variant));
    refreshNativeLogSetup();
    return mods[variant].LlamaContext.loadModelInfo(path, modelInfoSkip);
});
exports.loadLlamaModelInfo = loadLlamaModelInfo;
