import { encoding_for_model, get_encoding } from 'tiktoken';
const DEFAULT_ENCODING = 'cl100k_base';
const encoderCache = new Map();
let defaultEncoder = null;
function getEncoder(model) {
    if (model) {
        const normalized = model.trim();
        if (encoderCache.has(normalized)) {
            return encoderCache.get(normalized);
        }
        try {
            const encoder = encoding_for_model(normalized);
            encoderCache.set(normalized, encoder);
            return encoder;
        }
        catch {
            // fall back to default encoder
        }
    }
    if (!defaultEncoder) {
        defaultEncoder = get_encoding(DEFAULT_ENCODING);
    }
    return defaultEncoder;
}
export function countRequestTokens(request) {
    const encoder = getEncoder(request.model);
    let total = 0;
    for (const message of request.messages || []) {
        total += countMessageTokens(message, encoder);
    }
    if (Array.isArray(request.tools)) {
        for (const tool of request.tools) {
            total += encodeText(JSON.stringify(tool ?? {}), encoder);
        }
    }
    if (request.parameters) {
        total += encodeText(JSON.stringify(request.parameters), encoder);
    }
    return total;
}
function countMessageTokens(message, encoder) {
    let total = 0;
    total += encodeText(message.role, encoder);
    total += encodeContent(message.content, encoder);
    if (Array.isArray(message.tool_calls)) {
        for (const call of message.tool_calls) {
            total += encodeText(JSON.stringify(call ?? {}), encoder);
        }
    }
    if (message.name) {
        total += encodeText(message.name, encoder);
    }
    if (message.tool_call_id) {
        total += encodeText(message.tool_call_id, encoder);
    }
    return total;
}
function detectMediaKind(record) {
    const typeValue = typeof record.type === 'string' ? record.type.trim().toLowerCase() : '';
    if (typeValue.includes('video')) {
        return 'video';
    }
    if (typeValue.includes('image')) {
        return 'image';
    }
    if (Object.prototype.hasOwnProperty.call(record, 'video_url')) {
        return 'video';
    }
    if (Object.prototype.hasOwnProperty.call(record, 'image_url')) {
        return 'image';
    }
    const dataField = typeof record.data === 'string' ? record.data.trim().toLowerCase() : '';
    if (dataField.startsWith('data:video/')) {
        return 'video';
    }
    if (dataField.startsWith('data:image/')) {
        return 'image';
    }
    return null;
}
function parseStructuredContentString(raw) {
    const trimmed = raw.trim();
    if (!trimmed) {
        return undefined;
    }
    const likelyJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'));
    if (!likelyJson) {
        return undefined;
    }
    try {
        return JSON.parse(trimmed);
    }
    catch {
        return undefined;
    }
}
function encodeContent(content, encoder) {
    if (content === null || content === undefined) {
        return 0;
    }
    if (typeof content === 'string') {
        const structured = parseStructuredContentString(content);
        if (structured !== undefined) {
            return encodeContent(structured, encoder);
        }
        return encodeText(content, encoder);
    }
    if (Array.isArray(content)) {
        let total = 0;
        for (const part of content) {
            if (typeof part === 'string') {
                total += encodeText(part, encoder);
            }
            else if (part && typeof part === 'object') {
                const record = part;
                // Ignore image/video payload blocks in token estimation.
                if (detectMediaKind(record)) {
                    continue;
                }
                if (typeof record.text === 'string') {
                    total += encodeText(record.text, encoder);
                }
                else {
                    total += encodeText(JSON.stringify(record), encoder);
                }
            }
        }
        return total;
    }
    if (typeof content === 'object') {
        const record = content;
        if (detectMediaKind(record)) {
            return 0;
        }
        return encodeText(JSON.stringify(content), encoder);
    }
    return encodeText(String(content), encoder);
}
function encodeText(value, encoder) {
    if (value === null || value === undefined) {
        return 0;
    }
    const text = typeof value === 'string' ? value : String(value);
    if (!text.trim()) {
        return 0;
    }
    return encoder.encode(text).length;
}
