import { LlamaGrammar } from "../../LlamaGrammar.js";
import { LlamaText } from "../../../utils/LlamaText.js";
import { validateObjectAgainstGbnfSchema } from "../../../utils/gbnfJson/utils/validateObjectAgainstGbnfSchema.js";
import { GbnfGrammarGenerator } from "../../../utils/gbnfJson/GbnfGrammarGenerator.js";
import { getGbnfJsonTerminalForGbnfJsonSchema } from "../../../utils/gbnfJson/utils/getGbnfJsonTerminalForGbnfJsonSchema.js";
import { GbnfGrammar } from "../../../utils/gbnfJson/terminals/GbnfGrammar.js";
import { GbnfOr } from "../../../utils/gbnfJson/terminals/GbnfOr.js";
import { GbnfVerbatimText } from "../../../utils/gbnfJson/terminals/GbnfVerbatimText.js";
export class FunctionCallGrammar extends LlamaGrammar {
    _functions;
    _chatWrapper;
    constructor(llama, functions, chatWrapper, initialFunctionCallEngaged) {
        const grammar = getGbnfGrammarForFunctionCalls(functions, chatWrapper, initialFunctionCallEngaged);
        super({
            llama,
            grammar,
            stopGenerationTriggers: [LlamaText(chatWrapper.settings.functions.call.suffix, "\n".repeat(4))],
            trimWhitespaceSuffix: true
        });
        this._functions = functions;
        this._chatWrapper = chatWrapper;
        this._validateFunctions();
    }
    parseFunctionCall(callText) {
        if (this._chatWrapper.settings.functions.call.optionalPrefixSpace &&
            !callText.startsWith(this._chatWrapper.settings.functions.call.prefix) && callText[0] === " ")
            callText = callText.slice(1);
        if (!callText.startsWith(this._chatWrapper.settings.functions.call.prefix))
            throw new LlamaFunctionCallValidationError(`Expected function call to start with function call prefix from "${this._chatWrapper.wrapperName}" chat wrapper`, this._functions, this._chatWrapper, callText);
        const paramsPrefixIndex = callText.indexOf(this._chatWrapper.settings.functions.call.paramsPrefix, this._chatWrapper.settings.functions.call.prefix.length);
        if (paramsPrefixIndex < 0)
            throw new LlamaFunctionCallValidationError(`Expected function call to contain params prefix from "${this._chatWrapper.wrapperName}" chat wrapper`, this._functions, this._chatWrapper, callText);
        const functionName = callText.slice(this._chatWrapper.settings.functions.call.prefix.length, paramsPrefixIndex);
        if (!Object.hasOwn(this._functions, functionName))
            throw new LlamaFunctionCallValidationError(`Function name "${functionName}" is not in the supplied functions object`, this._functions, this._chatWrapper, callText);
        const functionSchema = this._functions[functionName];
        const callSuffix = this._chatWrapper.settings.functions.call.suffix;
        const callSuffixIndex = (callText + "\n".repeat(4)).lastIndexOf(callSuffix + "\n".repeat(4));
        if (callSuffixIndex < 0 || callSuffixIndex < paramsPrefixIndex + this._chatWrapper.settings.functions.call.paramsPrefix.length)
            throw new LlamaFunctionCallValidationError(`Expected function call to end with function call suffix from "${this._chatWrapper.wrapperName}" chat wrapper`, this._functions, this._chatWrapper, callText);
        const paramsString = callText.slice(paramsPrefixIndex + this._chatWrapper.settings.functions.call.paramsPrefix.length, callSuffixIndex);
        if (functionSchema.params == null && paramsString.trim().length !== 0)
            throw new LlamaFunctionCallValidationError(`Expected function call to not contain params string but got "${paramsString}"`, this._functions, this._chatWrapper, callText);
        else if (functionSchema.params == null)
            return {
                functionName,
                params: undefined,
                raw: callText.slice(0, callSuffixIndex + callSuffix.length)
            };
        const params = JSON.parse(paramsString);
        validateObjectAgainstGbnfSchema(params, functionSchema.params);
        return {
            functionName,
            params: params,
            raw: callText.slice(0, callSuffixIndex + callSuffix.length)
        };
    }
    parseFunctionNameFromPartialCall(callText, { enableInternalBuiltinFunctions = false, initialFunctionCallEngaged = false } = {}) {
        if (this._chatWrapper.settings.functions.call.optionalPrefixSpace &&
            !callText.startsWith(this._chatWrapper.settings.functions.call.prefix) && callText[0] === " ")
            callText = callText.slice(1);
        if (!callText.startsWith(this._chatWrapper.settings.functions.call.prefix))
            throw new LlamaFunctionCallValidationError(`Expected function call to start with function call prefix from "${this._chatWrapper.wrapperName}" chat wrapper`, this._functions, this._chatWrapper, callText);
        const paramsPrefixIndex = callText.indexOf(this._chatWrapper.settings.functions.call.paramsPrefix, this._chatWrapper.settings.functions.call.prefix.length);
        if (paramsPrefixIndex < 0)
            throw new LlamaFunctionCallValidationError(`Expected function call to contain params prefix from "${this._chatWrapper.wrapperName}" chat wrapper`, this._functions, this._chatWrapper, callText);
        const functionName = callText.slice(this._chatWrapper.settings.functions.call.prefix.length, paramsPrefixIndex);
        let foundFunctionName = Object.hasOwn(this._functions, functionName);
        if (!foundFunctionName && enableInternalBuiltinFunctions)
            foundFunctionName ||= Object.hasOwn(this._chatWrapper.getInternalBuiltinFunctions({ initialFunctionCallEngaged }), functionName);
        if (!foundFunctionName)
            throw new LlamaFunctionCallValidationError(`Function name "${functionName}" is not in the supplied functions object`, this._functions, this._chatWrapper, callText);
        return functionName;
    }
    _validateFunctions() {
        for (const functionsName of Object.keys(this._functions)) {
            if (functionsName.includes(" ") || functionsName.includes("\n") || functionsName.includes("\t"))
                throw new Error(`Function name "${functionsName}" contains spaces, new lines or tabs`);
        }
    }
}
function getGbnfGrammarForFunctionCalls(functions, chatWrapper, initialFunctionCallEngaged) {
    const grammarGenerator = new GbnfGrammarGenerator();
    const callGrammars = [];
    function addFunctionCallGrammar(functionName, functionSchema) {
        if (functionSchema.params != null) {
            const paramsTerminal = getGbnfJsonTerminalForGbnfJsonSchema(functionSchema.params, grammarGenerator);
            callGrammars.push(new GbnfGrammar([
                new GbnfVerbatimText(functionName + chatWrapper.settings.functions.call.paramsPrefix).getGrammar(),
                paramsTerminal.resolve(grammarGenerator)
            ]));
        }
        else
            callGrammars.push(new GbnfVerbatimText(functionName + chatWrapper.settings.functions.call.paramsPrefix));
    }
    for (const [functionName, functionSchema] of Object.entries(functions))
        addFunctionCallGrammar(functionName, functionSchema);
    for (const [functionName, functionSchema] of Object.entries(chatWrapper.getInternalBuiltinFunctions({ initialFunctionCallEngaged })))
        addFunctionCallGrammar(functionName, functionSchema);
    const callGrammar = new GbnfOr(callGrammars);
    const rootTerminal = new GbnfGrammar([
        ...(chatWrapper.settings.functions.call.optionalPrefixSpace ? ["[ ]?"] : []),
        new GbnfVerbatimText(chatWrapper.settings.functions.call.prefix).getGrammar(),
        callGrammar.resolve(grammarGenerator),
        new GbnfVerbatimText(chatWrapper.settings.functions.call.suffix).getGrammar()
    ]);
    const rootGrammar = rootTerminal.getGrammar();
    return grammarGenerator.generateGbnfFile(rootGrammar + " [\\n]".repeat(4) + " [\\n]*");
}
export class LlamaFunctionCallValidationError extends Error {
    functions;
    chatWrapper;
    callText;
    constructor(message, functions, chatWrapper, callText) {
        super(message);
        this.functions = functions;
        this.chatWrapper = chatWrapper;
        this.callText = callText;
    }
}
//# sourceMappingURL=FunctionCallGrammar.js.map