import { _isToolCall } from "../tools/utils.js";
import { AIMessage, AIMessageChunk } from "./ai.js";
import { isBaseMessage, _isMessageFieldWithRole, } from "./base.js";
import { ChatMessage, ChatMessageChunk, } from "./chat.js";
import { FunctionMessage, FunctionMessageChunk, } from "./function.js";
import { HumanMessage, HumanMessageChunk } from "./human.js";
import { SystemMessage, SystemMessageChunk } from "./system.js";
import { ToolMessage, } from "./tool.js";
function _coerceToolCall(toolCall) {
    if (_isToolCall(toolCall)) {
        return toolCall;
    }
    else if (typeof toolCall.id === "string" &&
        toolCall.type === "function" &&
        typeof toolCall.function === "object" &&
        toolCall.function !== null &&
        "arguments" in toolCall.function &&
        typeof toolCall.function.arguments === "string" &&
        "name" in toolCall.function &&
        typeof toolCall.function.name === "string") {
        // Handle OpenAI tool call format
        return {
            id: toolCall.id,
            args: JSON.parse(toolCall.function.arguments),
            name: toolCall.function.name,
            type: "tool_call",
        };
    }
    else {
        // TODO: Throw an error?
        return toolCall;
    }
}
function _constructMessageFromParams(params) {
    const { type, ...rest } = params;
    if (type === "human" || type === "user") {
        return new HumanMessage(rest);
    }
    else if (type === "ai" || type === "assistant") {
        const { tool_calls: rawToolCalls, ...other } = rest;
        if (!Array.isArray(rawToolCalls)) {
            return new AIMessage(rest);
        }
        const tool_calls = rawToolCalls.map(_coerceToolCall);
        return new AIMessage({ ...other, tool_calls });
    }
    else if (type === "system") {
        return new SystemMessage(rest);
    }
    else if (type === "tool" && "tool_call_id" in rest) {
        return new ToolMessage({
            ...rest,
            content: rest.content,
            tool_call_id: rest.tool_call_id,
            name: rest.name,
        });
    }
    else {
        throw new Error(`Unable to coerce message from array: only human, AI, or system message coercion is currently supported.`);
    }
}
export function coerceMessageLikeToMessage(messageLike) {
    if (typeof messageLike === "string") {
        return new HumanMessage(messageLike);
    }
    else if (isBaseMessage(messageLike)) {
        return messageLike;
    }
    if (Array.isArray(messageLike)) {
        const [type, content] = messageLike;
        return _constructMessageFromParams({ type, content });
    }
    else if (_isMessageFieldWithRole(messageLike)) {
        const { role: type, ...rest } = messageLike;
        return _constructMessageFromParams({ ...rest, type });
    }
    else {
        return _constructMessageFromParams(messageLike);
    }
}
/**
 * This function is used by memory classes to get a string representation
 * of the chat message history, based on the message content and role.
 */
export function getBufferString(messages, humanPrefix = "Human", aiPrefix = "AI") {
    const string_messages = [];
    for (const m of messages) {
        let role;
        if (m._getType() === "human") {
            role = humanPrefix;
        }
        else if (m._getType() === "ai") {
            role = aiPrefix;
        }
        else if (m._getType() === "system") {
            role = "System";
        }
        else if (m._getType() === "function") {
            role = "Function";
        }
        else if (m._getType() === "tool") {
            role = "Tool";
        }
        else if (m._getType() === "generic") {
            role = m.role;
        }
        else {
            throw new Error(`Got unsupported message type: ${m._getType()}`);
        }
        const nameStr = m.name ? `${m.name}, ` : "";
        const readableContent = typeof m.content === "string"
            ? m.content
            : JSON.stringify(m.content, null, 2);
        string_messages.push(`${role}: ${nameStr}${readableContent}`);
    }
    return string_messages.join("\n");
}
/**
 * Maps messages from an older format (V1) to the current `StoredMessage`
 * format. If the message is already in the `StoredMessage` format, it is
 * returned as is. Otherwise, it transforms the V1 message into a
 * `StoredMessage`. This function is important for maintaining
 * compatibility with older message formats.
 */
function mapV1MessageToStoredMessage(message) {
    // TODO: Remove this mapper when we deprecate the old message format.
    if (message.data !== undefined) {
        return message;
    }
    else {
        const v1Message = message;
        return {
            type: v1Message.type,
            data: {
                content: v1Message.text,
                role: v1Message.role,
                name: undefined,
                tool_call_id: undefined,
            },
        };
    }
}
export function mapStoredMessageToChatMessage(message) {
    const storedMessage = mapV1MessageToStoredMessage(message);
    switch (storedMessage.type) {
        case "human":
            return new HumanMessage(storedMessage.data);
        case "ai":
            return new AIMessage(storedMessage.data);
        case "system":
            return new SystemMessage(storedMessage.data);
        case "function":
            if (storedMessage.data.name === undefined) {
                throw new Error("Name must be defined for function messages");
            }
            return new FunctionMessage(storedMessage.data);
        case "tool":
            if (storedMessage.data.tool_call_id === undefined) {
                throw new Error("Tool call ID must be defined for tool messages");
            }
            return new ToolMessage(storedMessage.data);
        case "generic": {
            if (storedMessage.data.role === undefined) {
                throw new Error("Role must be defined for chat messages");
            }
            return new ChatMessage(storedMessage.data);
        }
        default:
            throw new Error(`Got unexpected type: ${storedMessage.type}`);
    }
}
/**
 * Transforms an array of `StoredMessage` instances into an array of
 * `BaseMessage` instances. It uses the `mapV1MessageToStoredMessage`
 * function to ensure all messages are in the `StoredMessage` format, then
 * creates new instances of the appropriate `BaseMessage` subclass based
 * on the type of each message. This function is used to prepare stored
 * messages for use in a chat context.
 */
export function mapStoredMessagesToChatMessages(messages) {
    return messages.map(mapStoredMessageToChatMessage);
}
/**
 * Transforms an array of `BaseMessage` instances into an array of
 * `StoredMessage` instances. It does this by calling the `toDict` method
 * on each `BaseMessage`, which returns a `StoredMessage`. This function
 * is used to prepare chat messages for storage.
 */
export function mapChatMessagesToStoredMessages(messages) {
    return messages.map((message) => message.toDict());
}
export function convertToChunk(message) {
    const type = message._getType();
    if (type === "human") {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return new HumanMessageChunk({ ...message });
    }
    else if (type === "ai") {
        let aiChunkFields = {
            ...message,
        };
        if ("tool_calls" in aiChunkFields) {
            aiChunkFields = {
                ...aiChunkFields,
                tool_call_chunks: aiChunkFields.tool_calls?.map((tc) => ({
                    ...tc,
                    type: "tool_call_chunk",
                    index: undefined,
                    args: JSON.stringify(tc.args),
                })),
            };
        }
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return new AIMessageChunk({ ...aiChunkFields });
    }
    else if (type === "system") {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return new SystemMessageChunk({ ...message });
    }
    else if (type === "function") {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return new FunctionMessageChunk({ ...message });
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
    }
    else if (ChatMessage.isInstance(message)) {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        return new ChatMessageChunk({ ...message });
    }
    else {
        throw new Error("Unknown message type.");
    }
}
