import { Container, Text } from "@earendil-works/pi-tui";
import { parseSkillBlock } from "../../../core/agent-session.js";
import { getMarkdownTheme, theme } from "../theme/theme.js";
import { AssistantMessageComponent } from "./assistant-message.js";
import { BashExecutionComponent } from "./bash-execution.js";
import { BranchSummaryMessageComponent } from "./branch-summary-message.js";
import { CompactionSummaryMessageComponent } from "./compaction-summary-message.js";
import { CustomMessageComponent } from "./custom-message.js";
import { SkillInvocationMessageComponent } from "./skill-invocation-message.js";
import { ToolExecutionComponent } from "./tool-execution.js";
import { UserMessageComponent } from "./user-message.js";
export function chatEntriesFromAgentMessages(messages) {
    const entries = [];
    const pendingTools = new Map();
    for (const message of messages) {
        switch (message.role) {
            case "assistant": {
                entries.push({ role: "assistant", kind: "assistant", message });
                for (const content of message.content) {
                    if (content.type !== "toolCall")
                        continue;
                    const toolEntry = {
                        role: "tool",
                        kind: "tool",
                        toolName: content.name,
                        toolCallId: content.id,
                        args: content.arguments,
                        isPartial: true,
                    };
                    entries.push(toolEntry);
                    pendingTools.set(content.id, toolEntry);
                }
                if (message.stopReason === "aborted" || message.stopReason === "error") {
                    const errorText = message.stopReason === "aborted"
                        ? message.errorMessage || "Operation aborted"
                        : message.errorMessage || "Unknown error";
                    for (const toolEntry of pendingTools.values()) {
                        toolEntry.result = {
                            role: "toolResult",
                            toolCallId: toolEntry.toolCallId,
                            toolName: toolEntry.toolName,
                            content: [{ type: "text", text: errorText }],
                            isError: true,
                            timestamp: message.timestamp,
                        };
                        toolEntry.isPartial = false;
                    }
                    pendingTools.clear();
                }
                break;
            }
            case "toolResult": {
                const toolEntry = pendingTools.get(message.toolCallId);
                if (toolEntry) {
                    toolEntry.result = message;
                    toolEntry.isPartial = false;
                    pendingTools.delete(message.toolCallId);
                }
                else {
                    entries.push({
                        role: "tool",
                        kind: "tool",
                        toolName: message.toolName,
                        toolCallId: message.toolCallId,
                        args: {},
                        result: message,
                        isPartial: false,
                    });
                }
                break;
            }
            case "user": {
                const text = getMessageText(message);
                if (text)
                    entries.push({ role: "user", kind: "user", text });
                break;
            }
            case "bashExecution":
                entries.push({ role: "tool", kind: "bashExecution", message });
                break;
            case "custom":
                if (message.display)
                    entries.push({ role: "custom", kind: "custom", message });
                break;
            case "branchSummary":
                entries.push({ role: "summary", kind: "branchSummary", message });
                break;
            case "compactionSummary":
                entries.push({ role: "summary", kind: "compactionSummary", message });
                break;
            default: {
                const role = message.role;
                entries.push({ role: "system", kind: "system", text: role });
                break;
            }
        }
    }
    return entries;
}
export class LiveChatEntriesController {
    constructor(entries) {
        this.entries = entries;
        this.pendingToolIndexes = new Map();
    }
    appendMessages(messages) {
        this.entries.push(...chatEntriesFromAgentMessages(messages));
        this.reindexPendingTools();
    }
    appendUserText(text) {
        this.entries.push({ role: "user", kind: "user", text });
    }
    applyEvent(event) {
        const type = String(event.type ?? "");
        switch (type) {
            case "message_start":
                return this.handleMessageStart(event.message);
            case "message_update":
                return this.handleMessageUpdate(event);
            case "message_end":
                return this.handleMessageEnd(event.message);
            case "tool_execution_start":
                return this.upsertToolEntry({
                    toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : undefined,
                    toolName: typeof event.toolName === "string" ? event.toolName : "tool",
                    args: event.args,
                    isPartial: true,
                });
            case "tool_execution_update": {
                const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
                if (!toolCallId)
                    return false;
                return this.updateToolResult(toolCallId, event.partialResult, true, false);
            }
            case "tool_execution_end": {
                const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
                if (!toolCallId)
                    return false;
                return this.updateToolResult(toolCallId, event.result, false, event.isError === true);
            }
            default:
                return false;
        }
    }
    pendingToolIds() {
        return [...this.pendingToolIndexes.keys()];
    }
    clearPendingTools() {
        this.pendingToolIndexes.clear();
    }
    handleMessageStart(message) {
        if (!isAgentMessageLike(message))
            return false;
        if (message.role === "assistant") {
            this.streamingAssistantIndex = undefined;
            return this.updateAssistantMessage(message);
        }
        const entries = chatEntriesFromAgentMessages([message]);
        if (entries.length === 0)
            return false;
        this.entries.push(...entries);
        this.reindexPendingTools();
        return true;
    }
    handleMessageUpdate(event) {
        const message = event.message;
        let changed = false;
        const snapshotHasPayload = isAgentMessageLike(message) &&
            message.role === "assistant" &&
            assistantContentHasRenderablePayload(message.content);
        if (isAgentMessageLike(message) && message.role === "assistant" && snapshotHasPayload) {
            changed = this.updateAssistantMessage(message) || changed;
        }
        const assistantEvent = event.assistantMessageEvent;
        const streamType = String(assistantEvent?.type ?? "");
        const delta = typeof assistantEvent?.delta === "string" ? assistantEvent.delta : "";
        if (!changed && streamType === "text_delta" && delta) {
            changed = this.appendAssistantTextDelta(delta);
        }
        else if (!changed && streamType === "thinking_delta" && delta) {
            changed = this.appendAssistantThinkingDelta(delta);
        }
        return changed;
    }
    handleMessageEnd(message) {
        if (!isAgentMessageLike(message) || message.role !== "assistant")
            return false;
        const changed = this.updateAssistantMessage(message);
        const stopReason = typeof message.stopReason === "string" ? message.stopReason : "";
        if (stopReason === "aborted" || stopReason === "error") {
            const errorText = typeof message.errorMessage === "string" && message.errorMessage
                ? message.errorMessage
                : stopReason === "aborted"
                    ? "Operation aborted"
                    : "Unknown error";
            for (const toolCallId of this.pendingToolIds()) {
                this.updateToolResult(toolCallId, { content: [{ type: "text", text: errorText }] }, false, true);
            }
            this.clearPendingTools();
        }
        this.streamingAssistantIndex = undefined;
        return changed || true;
    }
    updateAssistantMessage(message) {
        if (this.streamingAssistantIndex !== undefined && this.isAssistantEntry(this.entries[this.streamingAssistantIndex])) {
            this.entries[this.streamingAssistantIndex] = {
                ...this.entries[this.streamingAssistantIndex],
                message,
            };
        }
        else {
            this.entries.push({ role: "assistant", kind: "assistant", message });
            this.streamingAssistantIndex = this.entries.length - 1;
        }
        for (const content of message.content) {
            if (content.type !== "toolCall")
                continue;
            this.upsertToolEntry({
                toolCallId: content.id,
                toolName: content.name,
                args: content.arguments,
                isPartial: true,
            });
        }
        return true;
    }
    appendAssistantTextDelta(delta) {
        const current = this.currentStreamingAssistantMessage();
        const content = current ? [...current.content] : [];
        const lastText = [...content].reverse().find((item) => item.type === "text");
        if (lastText && lastText.type === "text")
            lastText.text += delta;
        else
            content.push({ type: "text", text: delta });
        return this.updateAssistantMessage({
            ...(current ?? minimalAssistantMessage()),
            content,
        });
    }
    appendAssistantThinkingDelta(delta) {
        const current = this.currentStreamingAssistantMessage();
        const content = current ? [...current.content] : [];
        const lastThinking = [...content].reverse().find((item) => item.type === "thinking");
        if (lastThinking && lastThinking.type === "thinking")
            lastThinking.thinking += delta;
        else
            content.push({ type: "thinking", thinking: delta });
        return this.updateAssistantMessage({
            ...(current ?? minimalAssistantMessage()),
            content,
        });
    }
    currentStreamingAssistantMessage() {
        const entry = this.streamingAssistantIndex !== undefined ? this.entries[this.streamingAssistantIndex] : undefined;
        return this.isAssistantEntry(entry) ? entry.message : undefined;
    }
    upsertToolEntry(update) {
        const toolCallId = update.toolCallId ?? `live-${update.toolName}`;
        const index = this.pendingToolIndexes.get(toolCallId) ?? this.findToolEntryIndex(toolCallId, update.toolName);
        const previous = index >= 0 ? this.entries[index] : undefined;
        const previousTool = this.isToolEntry(previous) ? previous : undefined;
        const next = {
            role: "tool",
            kind: "tool",
            toolName: previousTool?.toolName ?? update.toolName,
            toolCallId,
            args: update.args ?? previousTool?.args ?? {},
            result: previousTool?.result,
            isPartial: update.isPartial,
        };
        if (index >= 0)
            this.entries[index] = next;
        else
            this.entries.push(next);
        this.pendingToolIndexes.set(toolCallId, index >= 0 ? index : this.entries.length - 1);
        return true;
    }
    updateToolResult(toolCallId, result, isPartial, isError) {
        const index = this.pendingToolIndexes.get(toolCallId) ?? this.findToolEntryIndex(toolCallId);
        if (index < 0)
            return false;
        const entry = this.entries[index];
        if (!this.isToolEntry(entry))
            return false;
        const resultObject = toolResultFromUnknown(result, entry.toolName, toolCallId, isError);
        this.entries[index] = { ...entry, result: resultObject, isPartial };
        if (!isPartial)
            this.pendingToolIndexes.delete(toolCallId);
        return true;
    }
    findToolEntryIndex(toolCallId, toolName) {
        for (let i = this.entries.length - 1; i >= 0; i--) {
            const entry = this.entries[i];
            if (!this.isToolEntry(entry))
                continue;
            if (entry.toolCallId === toolCallId || (toolName && entry.toolName === toolName && entry.isPartial !== false))
                return i;
        }
        return -1;
    }
    reindexPendingTools() {
        this.pendingToolIndexes.clear();
        for (let i = 0; i < this.entries.length; i++) {
            const entry = this.entries[i];
            if (this.isToolEntry(entry) && entry.isPartial !== false)
                this.pendingToolIndexes.set(entry.toolCallId, i);
        }
    }
    isAssistantEntry(entry) {
        return isChatMessageEntry(entry) && entry.kind === "assistant";
    }
    isToolEntry(entry) {
        return isChatMessageEntry(entry) && entry.kind === "tool";
    }
}
function isChatMessageEntry(entry) {
    return entry !== undefined && "kind" in entry;
}
function isAgentMessageLike(message) {
    return message !== null && typeof message === "object" && "role" in message;
}
function assistantContentHasRenderablePayload(content) {
    if (typeof content === "string")
        return content.length > 0;
    if (!Array.isArray(content))
        return false;
    return content.some((item) => {
        if (typeof item === "string")
            return item.length > 0;
        if (item == null || typeof item !== "object")
            return false;
        const obj = item;
        return (obj.type === "text" && typeof obj.text === "string" && obj.text.length > 0) ||
            (obj.type === "thinking" && typeof obj.thinking === "string" && obj.thinking.length > 0) ||
            obj.type === "toolCall";
    });
}
function minimalAssistantMessage() {
    return {
        role: "assistant",
        content: [],
        stopReason: "stop",
    };
}
function toolResultFromUnknown(result, toolName, toolCallId, isError) {
    if (result !== null && typeof result === "object" && "content" in result) {
        const candidate = result;
        const content = Array.isArray(candidate.content) ? candidate.content : [];
        return {
            role: "toolResult",
            toolCallId,
            toolName,
            content: content,
            details: candidate.details,
            isError,
            timestamp: Date.now(),
        };
    }
    return {
        role: "toolResult",
        toolCallId,
        toolName,
        content: typeof result === "string" ? [{ type: "text", text: result }] : [],
        isError,
        timestamp: Date.now(),
    };
}
export function renderChatMessageEntry(entry, options) {
    const messageEntry = entry;
    const markdownTheme = options.markdownTheme ?? getMarkdownTheme();
    switch (messageEntry.kind) {
        case "assistant":
            return new AssistantMessageComponent(messageEntry.message, options.hideThinkingBlock ?? false, markdownTheme, options.hiddenThinkingLabel ?? "Thinking...");
        case "tool": {
            const component = new ToolExecutionComponent(messageEntry.toolName, messageEntry.toolCallId, messageEntry.args, {
                showImages: options.showImages ?? true,
                imageWidthCells: options.imageWidthCells,
            }, options.getToolDefinition?.(messageEntry.toolName), options.ui, options.cwd);
            component.setExpanded(options.toolOutputExpanded ?? false);
            if (messageEntry.result)
                component.updateResult(messageEntry.result, messageEntry.isPartial ?? false);
            return component;
        }
        case "bashExecution": {
            const component = new BashExecutionComponent(messageEntry.message.command, options.ui, messageEntry.message.excludeFromContext);
            if (messageEntry.message.output)
                component.appendOutput(messageEntry.message.output);
            component.setComplete(messageEntry.message.exitCode, messageEntry.message.cancelled, messageEntry.message.truncated
                ? { truncated: true }
                : undefined, messageEntry.message.fullOutputPath);
            return component;
        }
        case "user":
            return userMessageComponent(messageEntry.text, markdownTheme, options.toolOutputExpanded ?? false);
        case "custom": {
            const component = new CustomMessageComponent(messageEntry.message, options.getCustomMessageRenderer?.(messageEntry.message.customType), markdownTheme);
            component.setExpanded(options.toolOutputExpanded ?? false);
            return component;
        }
        case "branchSummary": {
            const component = new BranchSummaryMessageComponent(messageEntry.message, markdownTheme);
            component.setExpanded(options.toolOutputExpanded ?? false);
            return component;
        }
        case "compactionSummary": {
            const component = new CompactionSummaryMessageComponent(messageEntry.message, markdownTheme);
            component.setExpanded(options.toolOutputExpanded ?? false);
            return component;
        }
        case "system":
            return new Text(theme.fg("dim", messageEntry.text), 1, 0);
    }
}
function userMessageComponent(text, markdownTheme, expanded) {
    const skillBlock = parseSkillBlock(text);
    if (!skillBlock)
        return new UserMessageComponent(text, markdownTheme);
    const container = new Container();
    const skillComponent = new SkillInvocationMessageComponent(skillBlock, markdownTheme);
    skillComponent.setExpanded(expanded);
    container.addChild(skillComponent);
    if (skillBlock.userMessage) {
        container.addChild(new UserMessageComponent(skillBlock.userMessage, markdownTheme));
    }
    return container;
}
function getMessageText(message) {
    return messageContentText(message.content).trim();
}
function messageContentText(content) {
    if (typeof content === "string")
        return content;
    if (!Array.isArray(content))
        return "";
    const parts = [];
    for (const item of content) {
        if (item == null)
            continue;
        if (typeof item === "string") {
            parts.push(item);
            continue;
        }
        if (typeof item !== "object")
            continue;
        const text = item.text;
        if (typeof text === "string")
            parts.push(text);
    }
    return parts.join("");
}
//# sourceMappingURL=chat-message-renderer.js.map