import { Spacer, Text, matchesKey as tuiMatchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
import { SessionManager } from "../../../core/session-manager.js";
import { CustomEditor } from "./custom-editor.js";
import { LiveChatEntriesController, renderChatMessageEntry, } from "./chat-message-renderer.js";
import { ChatTranscriptComponent, ScrollableComponentViewport, } from "./chat-transcript.js";
import { UsageMeterComponent, FooterComponent } from "./footer.js";
import { WorkingStatusComponent } from "./working-status.js";
import { combineQueuedMessagesForEditor, openExternalEditorForText, pasteClipboardImageToEditor, } from "../chat-input-actions.js";
import { pickWhimsicalWorkingMessage } from "../whimsical-messages.js";
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const ANIMATION_FRAME_MS = 80;
const STREAMING_RENDER_THROTTLE_MS = 80;
const STREAMING_TEXT_TAIL_LINES = 240;
const STREAMING_TEXT_TAIL_CHARS = 16_000;
export class ChatSessionHost {
    constructor(opts) {
        this.focused = true;
        this.inputBuffer = "";
        this.transcript = [];
        this.statusMessage = "";
        this.isBashMode = false;
        this.localBashRunning = false;
        this.sdkBusy = false;
        this.pendingSteeringMessages = [];
        this.pendingFollowUpMessages = [];
        this.compactionQueuedMessages = [];
        this.compacting = false;
        this.bodyViewport = new ScrollableComponentViewport();
        this.optimisticUserSignatureCounts = new Map();
        this.style = opts.style;
        this.commands = opts.commands ?? {};
        this.requestRender = opts.requestRender;
        this.getAgentSession = opts.getAgentSession;
        this.isStreamingOverride = opts.isStreaming;
        this.isPaused = opts.isPaused;
        this.isDisabled = opts.isDisabled;
        this.isBashRunningOverride = opts.isBashRunning;
        this.showWarning = opts.showWarning;
        this.showStatus = opts.showStatus;
        this.actions = opts.actions;
        this.getActionKeyDisplay = opts.getActionKeyDisplay;
        this.getMarkdownTheme = opts.getMarkdownTheme;
        this.getChatRenderSettings = opts.getChatRenderSettings;
        this.getCwd = opts.getCwd;
        this.footerData = opts.footerData;
        this.renderExtraEntry = opts.renderExtraEntry;
        this.tui = opts.tui;
        this.liveChat = new LiveChatEntriesController(this.transcript);
        this.editor = this.createEditor(opts.tui, opts.keybindings, opts.editorTheme, opts.editorFactory);
        this.syncAnimationTick();
    }
    appendMessages(messages) {
        this.liveChat.appendMessages(messages);
    }
    loadSessionFile(sessionFile) {
        if (this.transcript.length > 0 || sessionFile === undefined)
            return;
        let messages;
        try {
            messages = SessionManager.open(sessionFile).buildSessionContext()
                .messages;
        }
        catch {
            return;
        }
        this.liveChat.appendMessages(messages);
    }
    appendExtraEntry(entry) {
        this.transcript.push(entry);
    }
    entries() {
        return this.transcript;
    }
    incrementOptimisticUserSignature(signature) {
        this.optimisticUserSignatureCounts.set(signature, (this.optimisticUserSignatureCounts.get(signature) ?? 0) + 1);
    }
    decrementOptimisticUserSignature(signature) {
        const count = this.optimisticUserSignatureCounts.get(signature) ?? 0;
        if (count <= 1)
            this.optimisticUserSignatureCounts.delete(signature);
        else
            this.optimisticUserSignatureCounts.set(signature, count - 1);
    }
    applyAgentEvent(event) {
        const type = String(event.type ?? "");
        if (type === "message_start") {
            const message = event.message;
            if (isUserMessageLike(message)) {
                const signature = userMessageSignature(extractMessageText(message.content));
                const count = this.optimisticUserSignatureCounts.get(signature) ?? 0;
                if (count > 0) {
                    this.decrementOptimisticUserSignature(signature);
                    return false;
                }
            }
        }
        if (isSharedLiveChatEvent(type)) {
            const changed = this.liveChat.applyEvent(event);
            const toolCallEvent = assistantToolCallEvent(event);
            const changedByToolCall = toolCallEvent !== undefined
                ? this.liveChat.applyEvent(toolCallEvent)
                : false;
            this.afterEvent(changed || changedByToolCall);
            return changed || changedByToolCall;
        }
        let changed = false;
        switch (type) {
            case "agent_start":
                this.sdkBusy = true;
                this.liveChat.clearPendingTools();
                this.statusMessage = "";
                changed = true;
                break;
            case "agent_end":
                this.sdkBusy = false;
                this.workingMessage = undefined;
                this.liveChat.clearPendingTools();
                this.statusMessage = "";
                changed = true;
                break;
            case "turn_start":
                this.workingMessage = pickWhimsicalWorkingMessage();
                changed = true;
                break;
            case "turn_end":
                this.workingMessage = undefined;
                changed = true;
                break;
            case "queue_update": {
                const queue = event;
                this.pendingSteeringMessages = Array.isArray(queue.steering)
                    ? queue.steering.filter((item) => typeof item === "string")
                    : [];
                this.pendingFollowUpMessages = Array.isArray(queue.followUp)
                    ? queue.followUp.filter((item) => typeof item === "string")
                    : [];
                changed = true;
                break;
            }
            case "tool_call":
            case "tool_use":
                changed = this.liveChat.applyEvent(legacyToolStartEvent(event));
                break;
            case "tool_result":
                changed = this.liveChat.applyEvent(legacyToolResultEvent(event));
                break;
            case "thinking_delta":
            case "thinking":
                changed = this.liveChat.applyEvent(legacyThinkingEvent(event));
                break;
            case "compaction_start":
                this.compacting = true;
                this.sdkBusy = true;
                this.statusMessage = "compacting context…";
                changed = true;
                break;
            case "compaction_end": {
                const compaction = event;
                this.compacting = false;
                this.sdkBusy = false;
                this.statusMessage = compaction.errorMessage ?? "";
                if (!compaction.aborted && !compaction.errorMessage && this.compactionQueuedMessages.length > 0) {
                    void this.flushCompactionQueue();
                }
                changed = true;
                break;
            }
            case "auto_retry_start":
                this.sdkBusy = true;
                this.statusMessage = "retrying…";
                changed = true;
                break;
            case "auto_retry_end": {
                const retry = event;
                this.statusMessage = "";
                if (!retry.success) {
                    this.sdkBusy = false;
                    this.workingMessage = undefined;
                }
                changed = true;
                break;
            }
            default:
                changed = false;
        }
        this.afterEvent(changed);
        return changed;
    }
    render(width) {
        return this.renderBody(width, 1);
    }
    invalidate() { }
    renderBody(width, budget) {
        const components = [];
        if (this.transcript.length > 0) {
            components.push(new ChatTranscriptComponent(this.transcript, (entry) => this.renderEntry(entry)));
        }
        if (this.statusMessage) {
            components.push(new Spacer(1));
            components.push(new Text(this.style.dim(this.statusMessage), 2, 0));
        }
        this.bodyViewport.setVisibleRows(budget);
        this.bodyViewport.setComponents(components);
        return this.bodyViewport.render(width);
    }
    renderPendingMessages(width) {
        if (this.pendingSteeringMessages.length === 0 &&
            this.pendingFollowUpMessages.length === 0 &&
            this.compactionQueuedMessages.length === 0) {
            return [];
        }
        const lines = [this.style.blank(width)];
        for (const message of this.pendingSteeringMessages) {
            lines.push(...this.pendingMessageLine(width, "Steering", message));
        }
        for (const message of this.pendingFollowUpMessages) {
            lines.push(...this.pendingMessageLine(width, "Follow-up", message));
        }
        for (const message of this.compactionQueuedMessages) {
            lines.push(...this.pendingMessageLine(width, "Queued", message));
        }
        const hint = this.getActionKeyDisplay?.("app.message.dequeue") ?? "alt+up";
        lines.push(...new Text(this.style.dim(`↳ ${hint} to edit all queued messages`), 1, 0).render(width));
        return lines;
    }
    renderWorkingStatus(width) {
        if (!this.isStreaming())
            return [];
        const message = this.workingMessage ?? "Working...";
        return new WorkingStatusComponent({
            spinner: spinnerFrame(),
            message,
            spinnerColor: (text) => this.style.accentBold(text),
            messageColor: (text) => this.style.textMuted(text),
        }).render(width);
    }
    renderUsage(width) {
        const agentSession = this.getAgentSession?.();
        if (!agentSession)
            return [];
        return new UsageMeterComponent(agentSession).render(width);
    }
    renderEditor(width) {
        const disabled = this.isDisabled?.() === true;
        const agentSession = this.getAgentSession?.();
        const ruleHex = this.style.editorRuleColor(disabled, agentSession, {
            isBashMode: this.isBashMode,
        });
        if (!disabled && this.editor) {
            setEditorFocused(this.editor, this.focused);
            setEditorPlaceholder(this.editor, undefined);
            setEditorBorderColor(this.editor, (text) => this.style.rule(ruleHex, text));
            return this.editor.render(width);
        }
        if (this.editor)
            setEditorFocused(this.editor, false);
        const rule = this.style.rule(ruleHex, "─".repeat(width));
        const available = Math.max(1, width - 3);
        const value = this.inputBuffer
            ? this.style.text(truncateToWidth(this.inputBuffer, available)) + this.style.cursor()
            : disabled
                ? ""
                : this.style.cursor();
        const left = this.style.accentBold("❯") + " " + value;
        const gap = Math.max(0, width - visibleWidth(stripAnsi(left)));
        const body = left + " ".repeat(gap);
        return [rule, body, rule];
    }
    renderFooter(width) {
        const agentSession = this.getAgentSession?.();
        if (agentSession && this.footerData) {
            return new FooterComponent(agentSession, this.footerData).render(width);
        }
        return [];
    }
    handleScrollInput(data) {
        return this.bodyViewport.handleInput(data);
    }
    handleInput(data) {
        if (this.handleScrollInput(data))
            return true;
        if (matchesKey(data, "alt+up")) {
            this.restoreQueuedMessagesToEditor();
            return true;
        }
        if (matchesKey(data, "ctrl+f")) {
            void this.submit("followUp");
            return true;
        }
        if (matchesKey(data, "escape")) {
            if (this.compacting) {
                void this.abortCompaction();
                return true;
            }
            if (this.isStreaming()) {
                void this.interrupt();
                return true;
            }
            if (this.isBashRunning()) {
                void this.abortBash();
                return true;
            }
            if (this.isBashMode) {
                this.setEditorText("");
                this.notifyStatus("Bash mode cleared");
                return true;
            }
        }
        if (this.editor) {
            this.editor.handleInput(data);
            return true;
        }
        if (matchesKey(data, "enter")) {
            void this.submit("auto");
            return true;
        }
        if (matchesKey(data, "backspace")) {
            this.setEditorText(this.inputBuffer.slice(0, -1));
            return true;
        }
        if (data.length === 1 && data >= " " && data <= "~") {
            this.setEditorText(`${this.inputBuffer}${data}`);
            return true;
        }
        return false;
    }
    async interrupt() {
        try {
            this.restoreQueuedMessagesToEditor();
            this.sdkBusy = false;
            this.workingMessage = undefined;
            await this.commands.interrupt?.();
        }
        catch (err) {
            this.statusMessage = errorMessage(err);
        }
        finally {
            this.syncAnimationTick();
            this.requestRender?.();
        }
    }
    async submit(mode = "auto", submittedText) {
        const text = (submittedText ?? this.inputBuffer).trim();
        if (!text)
            return;
        if (text.startsWith("/") && this.commands.handleSlashCommand) {
            const handled = await this.commands.handleSlashCommand(text);
            if (handled) {
                this.setEditorText("");
                this.requestRender?.();
                return;
            }
        }
        if (this.compacting) {
            this.setEditorText("");
            this.compactionQueuedMessages = [...this.compactionQueuedMessages, text];
            this.notifyStatus("Queued message until compaction completes");
            return;
        }
        const bash = parseBashInput(text);
        if (bash?.command) {
            if (this.isBashRunning()) {
                this.notifyWarning("A bash command is already running. esc cancel first.");
                this.setEditorText(text);
                return;
            }
            this.setEditorText("");
            await this.runBashCommand(bash.command, bash.excludeFromContext);
            return;
        }
        this.setEditorText("");
        const isPaused = this.isPaused?.() === true;
        const isStreaming = this.isStreaming();
        const shouldAppendOptimisticUser = mode === "auto" && !isStreaming;
        const optimisticSignature = shouldAppendOptimisticUser
            ? userMessageSignature(text)
            : undefined;
        if (optimisticSignature !== undefined) {
            this.liveChat.appendUserText(text);
            this.bodyViewport.scrollToBottom();
            this.incrementOptimisticUserSignature(optimisticSignature);
        }
        this.requestRender?.();
        try {
            if (isPaused) {
                this.sdkBusy = true;
                this.statusMessage = "resuming…";
                this.syncAnimationTick();
                this.requestRender?.();
                await this.requiredCommand("resume")(text);
                this.sdkBusy = false;
                this.statusMessage = "";
                this.syncAnimationTick();
                return;
            }
            if (mode === "followUp" && isStreaming) {
                await this.queueFollowUp(text);
                return;
            }
            if (isStreaming) {
                await this.queueSteer(text);
            }
            else {
                this.sdkBusy = true;
                this.syncAnimationTick();
                await this.commands.ensureAttached?.();
                await this.requiredCommand("prompt")(text);
                this.sdkBusy = false;
                this.syncAnimationTick();
            }
        }
        catch (err) {
            if (optimisticSignature !== undefined) {
                this.decrementOptimisticUserSignature(optimisticSignature);
            }
            this.sdkBusy = false;
            this.statusMessage = errorMessage(err);
            this.syncAnimationTick();
            this.requestRender?.();
        }
    }
    isStreaming() {
        return this.sdkBusy || this.isStreamingOverride?.() === true;
    }
    isBashRunning() {
        return this.localBashRunning || this.isBashRunningOverride?.() === true;
    }
    isEditingBashCommand() {
        return this.isBashMode;
    }
    hasInputText() {
        return this.inputBuffer.length > 0;
    }
    hasAnimationTick() {
        return this.animationTimer !== undefined;
    }
    bodyScrollFromBottom() {
        return this.bodyViewport.getScrollFromBottom();
    }
    bodyMaxScroll() {
        return this.bodyViewport.getMaxScroll();
    }
    inputText() {
        return this.inputBuffer;
    }
    statusText() {
        return this.statusMessage;
    }
    scrollToBottom() {
        this.bodyViewport.scrollToBottom();
    }
    syncAnimationTick() {
        const shouldAnimate = this.isStreaming() || (this.sdkBusy && this.liveChat.pendingToolIds().length > 0);
        if (shouldAnimate && !this.animationTimer) {
            this.animationTimer = setInterval(() => {
                this.requestRender?.();
            }, ANIMATION_FRAME_MS);
            this.animationTimer.unref?.();
            return;
        }
        if (!shouldAnimate && this.animationTimer) {
            clearInterval(this.animationTimer);
            this.animationTimer = undefined;
        }
    }
    dispose() {
        if (this.animationTimer) {
            clearInterval(this.animationTimer);
            this.animationTimer = undefined;
        }
        if (this.renderThrottleTimer) {
            clearTimeout(this.renderThrottleTimer);
            this.renderThrottleTimer = undefined;
        }
        this.editor = undefined;
    }
    createEditor(tui, keybindings, editorTheme, editorFactory) {
        if (!tui || !keybindings)
            return undefined;
        const editor = this.createInheritedEditor(tui, editorTheme, keybindings, editorFactory) ??
            new CustomEditor(tui, editorTheme, keybindings, { paddingX: 0, autocompleteMaxVisible: 5 });
        editor.onChange = (text) => {
            this.inputBuffer = text;
            this.isBashMode = text.trimStart().startsWith("!");
        };
        editor.onSubmit = (text) => {
            void this.submit("auto", text);
        };
        const actionEditor = editor;
        actionEditor.onAction?.("app.message.followUp", () => {
            void this.submit("followUp");
        });
        actionEditor.onAction?.("app.message.dequeue", () => {
            this.restoreQueuedMessagesToEditor();
        });
        actionEditor.onAction?.("app.editor.external", () => {
            this.openExternalEditor();
        });
        if (this.actions) {
            for (const [action, handler] of Object.entries(this.actions)) {
                actionEditor.onAction?.(action, () => {
                    void handler();
                });
            }
        }
        const previousPasteImage = actionEditor.onPasteImage;
        actionEditor.onPasteImage = () => {
            previousPasteImage?.();
            void pasteClipboardImageToEditor(this.editorAccess(), () => this.requestRender?.(), { showWarning: (message) => this.notifyWarning(message) });
        };
        const previousEscape = actionEditor.onEscape;
        actionEditor.onEscape = () => {
            if (this.compacting) {
                void this.abortCompaction();
                return;
            }
            if (this.isStreaming()) {
                void this.interrupt();
                return;
            }
            if (this.isBashRunning()) {
                void this.abortBash();
                return;
            }
            if (this.isBashMode) {
                this.setEditorText("");
                this.notifyStatus("Bash mode cleared");
                return;
            }
            previousEscape?.();
        };
        return editor;
    }
    createInheritedEditor(tui, editorTheme, keybindings, editorFactory) {
        if (!editorFactory)
            return undefined;
        try {
            return editorFactory(tui, editorTheme, keybindings);
        }
        catch {
            return undefined;
        }
    }
    editorAccess() {
        return {
            insertTextAtCursor: (text) => {
                if (this.editor?.insertTextAtCursor) {
                    this.editor.insertTextAtCursor(text);
                    return;
                }
                this.setEditorText(`${this.inputBuffer}${text}`);
            },
            getText: () => this.inputBuffer,
            setText: (text) => this.setEditorText(text),
        };
    }
    openExternalEditor() {
        if (!this.editor)
            return;
        const host = this.tuiHost();
        if (!host)
            return;
        const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
        const updated = openExternalEditorForText(currentText, host, {
            showWarning: (message) => this.notifyWarning(message),
        });
        if (updated !== undefined)
            this.setEditorText(updated);
    }
    tuiHost() {
        return this.tui;
    }
    setEditorText(text) {
        this.inputBuffer = text;
        this.isBashMode = text.trimStart().startsWith("!");
        this.editor?.setText(text);
    }
    renderEntry(entry) {
        if (isChatMessageEntry(entry)) {
            return renderChatMessageEntry(this.streamingWindowedEntry(entry), this.chatMessageRenderOptions());
        }
        if (!this.renderExtraEntry) {
            return new Text("", 0, 0);
        }
        return this.renderExtraEntry(entry);
    }
    streamingWindowedEntry(entry) {
        if (!this.isStreaming() || this.bodyViewport.getScrollFromBottom() !== 0) {
            return entry;
        }
        if (entry.kind !== "assistant")
            return entry;
        const content = entry.message.content.map((item) => {
            if (item.type === "text")
                return { ...item, text: tailStreamingText(item.text) };
            if (item.type === "thinking")
                return { ...item, thinking: tailStreamingText(item.thinking) };
            return item;
        });
        return { ...entry, message: { ...entry.message, content } };
    }
    chatMessageRenderOptions() {
        const inherited = this.getChatRenderSettings?.();
        return {
            ...inherited,
            ui: this.toolTui(),
            cwd: this.getCwd?.() ?? this.getAgentSession?.()?.sessionManager.getCwd() ?? process.cwd(),
            markdownTheme: inherited?.markdownTheme ?? this.getMarkdownTheme?.(),
            showImages: inherited?.showImages ?? true,
        };
    }
    toolTui() {
        return { requestRender: () => this.requestRender?.() };
    }
    pendingMessageLine(width, label, message) {
        const text = `${label}: ${message}`;
        return new Text(this.style.dim(truncateToWidth(text, Math.max(1, width - 2))), 1, 0).render(width);
    }
    async abortCompaction() {
        try {
            await this.commands.abortCompaction?.();
            this.compacting = false;
            this.sdkBusy = false;
            this.notifyStatus("Compaction cancelled");
        }
        catch (err) {
            this.notifyWarning(errorMessage(err));
        }
        finally {
            this.syncAnimationTick();
            this.requestRender?.();
        }
    }
    async abortBash() {
        try {
            await this.commands.abortBash?.();
            this.localBashRunning = false;
            this.notifyStatus("Bash command cancelled");
        }
        catch (err) {
            this.notifyWarning(errorMessage(err));
        }
        finally {
            this.requestRender?.();
        }
    }
    async runBashCommand(command, excludeFromContext) {
        const runBash = this.commands.runBash;
        if (!runBash) {
            this.notifyWarning("no bash command configured for this chat session");
            return;
        }
        const bashMessage = {
            role: "bashExecution",
            command,
            output: "",
            exitCode: undefined,
            cancelled: false,
            truncated: false,
            timestamp: Date.now(),
            ...(excludeFromContext ? { excludeFromContext: true } : {}),
        };
        const bashEntry = {
            role: "tool",
            kind: "bashExecution",
            message: bashMessage,
            isPartial: true,
        };
        this.transcript.push(bashEntry);
        this.localBashRunning = true;
        this.bodyViewport.scrollToBottom();
        this.requestRender?.();
        try {
            const result = await runBash({
                command,
                excludeFromContext,
                onChunk: (chunk) => {
                    bashMessage.output += chunk;
                    this.requestRender?.();
                },
            });
            bashMessage.output = result.output;
            bashMessage.exitCode = result.exitCode;
            bashMessage.cancelled = result.cancelled;
            bashMessage.truncated = result.truncated;
            if (result.fullOutputPath !== undefined) {
                bashMessage.fullOutputPath = result.fullOutputPath;
            }
        }
        catch (err) {
            bashMessage.output = errorMessage(err);
            bashMessage.exitCode = undefined;
            bashMessage.cancelled = false;
            bashMessage.truncated = false;
        }
        finally {
            bashEntry.isPartial = false;
            this.localBashRunning = false;
            this.requestRender?.();
        }
    }
    async flushCompactionQueue() {
        const queued = [...this.compactionQueuedMessages];
        this.compactionQueuedMessages = [];
        if (queued.length === 0)
            return;
        let nextIndex = 0;
        try {
            const first = queued[0];
            if (first !== undefined) {
                await this.requiredCommand("prompt")(first);
                nextIndex = 1;
            }
            for (; nextIndex < queued.length; nextIndex++) {
                await this.queueFollowUp(queued[nextIndex]);
            }
        }
        catch (err) {
            this.compactionQueuedMessages = [
                ...queued.slice(nextIndex),
                ...this.compactionQueuedMessages,
            ];
            this.notifyWarning(errorMessage(err));
            this.requestRender?.();
        }
    }
    async queueSteer(text) {
        const agentSession = this.getAgentSession?.();
        if (agentSession?.isStreaming) {
            await agentSession.prompt(text, { streamingBehavior: "steer" });
            return;
        }
        await this.requiredCommand("steer")(text);
    }
    async queueFollowUp(text) {
        const agentSession = this.getAgentSession?.();
        if (agentSession?.isStreaming) {
            await agentSession.prompt(text, { streamingBehavior: "followUp" });
            return;
        }
        await this.requiredCommand("followUp")(text);
    }
    restoreQueuedMessagesToEditor() {
        const queuedMessages = [
            ...this.pendingSteeringMessages,
            ...this.pendingFollowUpMessages,
            ...this.compactionQueuedMessages,
        ];
        if (queuedMessages.length === 0) {
            this.notifyStatus("No queued messages to restore");
            return false;
        }
        const restoredText = combineQueuedMessagesForEditor(queuedMessages, this.inputBuffer);
        this.pendingSteeringMessages = [];
        this.pendingFollowUpMessages = [];
        this.compactionQueuedMessages = [];
        this.setEditorText(restoredText);
        this.getAgentSession?.()?.clearQueue();
        this.notifyStatus(`Restored ${queuedMessages.length} queued message${queuedMessages.length === 1 ? "" : "s"} to editor`);
        this.requestRender?.();
        return true;
    }
    notifyWarning(message) {
        this.statusMessage = message;
        this.showWarning?.(message);
        this.requestRender?.();
    }
    notifyStatus(message) {
        this.statusMessage = message;
        this.showStatus?.(message);
        this.requestRender?.();
    }
    requiredCommand(name) {
        switch (name) {
            case "prompt":
                return async (text) => {
                    if (!this.commands.prompt)
                        throw new Error("no prompt command configured for this chat session");
                    await this.commands.prompt(text ?? "");
                };
            case "steer":
                return async (text) => {
                    if (!this.commands.steer)
                        throw new Error("no steer command configured for this chat session");
                    await this.commands.steer(text ?? "");
                };
            case "followUp":
                return async (text) => {
                    if (!this.commands.followUp)
                        throw new Error("no followUp command configured for this chat session");
                    await this.commands.followUp(text ?? "");
                };
            case "resume":
                return async (text) => {
                    if (!this.commands.resume)
                        throw new Error("no resume command configured for this chat session");
                    await this.commands.resume(text);
                };
        }
    }
    afterEvent(changed) {
        this.syncAnimationTick();
        if (!changed)
            return;
        this.requestEventRender();
    }
    requestEventRender() {
        if (!this.isStreaming()) {
            this.requestRender?.();
            return;
        }
        if (this.renderThrottleTimer)
            return;
        this.renderThrottleTimer = setTimeout(() => {
            this.renderThrottleTimer = undefined;
            this.requestRender?.();
        }, STREAMING_RENDER_THROTTLE_MS);
        this.renderThrottleTimer.unref?.();
    }
}
function setEditorPlaceholder(editor, placeholder) {
    const candidate = editor;
    candidate.setPlaceholder?.(placeholder);
}
function setEditorBorderColor(editor, borderColor) {
    const candidate = editor;
    if (candidate.borderColor !== undefined)
        candidate.borderColor = borderColor;
}
function setEditorFocused(editor, focused) {
    const candidate = editor;
    if ("focused" in candidate)
        candidate.focused = focused;
}
function matchesKey(data, key) {
    if (key === "enter" && (data === "\r" || data === "\n"))
        return true;
    if (key === "backspace" && (data === "\x7f" || data === "\b"))
        return true;
    if (key === "escape" && data === "\x1b")
        return true;
    if (key === "ctrl+f" && data === "\x06")
        return true;
    return tuiMatchesKey(data, key);
}
function parseBashInput(text) {
    if (!text.startsWith("!"))
        return undefined;
    const excludeFromContext = text.startsWith("!!");
    const command = text.slice(excludeFromContext ? 2 : 1).trim();
    return { command, excludeFromContext };
}
function isSharedLiveChatEvent(type) {
    return (type === "message_start" ||
        type === "message_update" ||
        type === "message_end" ||
        type === "tool_execution_start" ||
        type === "tool_execution_update" ||
        type === "tool_execution_end");
}
function isChatMessageEntry(entry) {
    if (!("role" in entry) || !("kind" in entry))
        return false;
    const candidate = entry;
    switch (candidate.kind) {
        case "assistant":
            return candidate.role === "assistant" && candidate.message !== undefined;
        case "tool":
            return candidate.role === "tool" && "toolName" in candidate && "toolCallId" in candidate && "args" in candidate;
        case "bashExecution":
            return candidate.role === "tool" && candidate.message !== undefined;
        case "user":
            return candidate.role === "user" && typeof candidate.text === "string";
        case "custom":
            return candidate.role === "custom" && candidate.message !== undefined;
        case "branchSummary":
        case "compactionSummary":
            return candidate.role === "summary" && candidate.message !== undefined;
        case "system":
            return candidate.role === "system" && candidate.message !== undefined;
        default:
            return false;
    }
}
function isMessageLike(message) {
    return message !== null && typeof message === "object" && "role" in message;
}
function isUserMessageLike(message) {
    return isMessageLike(message) && message.role === "user";
}
function userMessageSignature(text) {
    return text.trim();
}
function assistantToolCallEvent(event) {
    const assistantEvent = event.assistantMessageEvent;
    const streamType = String(assistantEvent?.type ?? "");
    if (!streamType.startsWith("toolcall_"))
        return undefined;
    const explicit = toolCallPayload(assistantEvent?.toolCall);
    if (explicit)
        return explicit;
    const contentIndex = typeof assistantEvent?.contentIndex === "number" ? assistantEvent.contentIndex : undefined;
    if (contentIndex === undefined)
        return undefined;
    const partial = assistantEvent?.partial;
    if (!isMessageLike(partial) || partial.role !== "assistant")
        return undefined;
    const content = partial.content;
    if (!Array.isArray(content))
        return undefined;
    return toolCallPayload(content[contentIndex]);
}
function toolCallPayload(value) {
    if (value === null || typeof value !== "object")
        return undefined;
    const candidate = value;
    if (candidate.type !== "toolCall")
        return undefined;
    if (typeof candidate.id !== "string" || typeof candidate.name !== "string")
        return undefined;
    return {
        type: "tool_execution_start",
        toolCallId: candidate.id,
        toolName: candidate.name,
        args: candidate.arguments ?? {},
    };
}
function legacyToolStartEvent(event) {
    const payload = event;
    const toolName = typeof payload.name === "string" ? payload.name : "tool";
    const toolCallId = typeof payload.toolCallId === "string" ? payload.toolCallId : `live-${toolName}`;
    return {
        type: "tool_execution_start",
        toolCallId,
        toolName,
        args: payload.input ?? payload.args ?? {},
    };
}
function legacyToolResultEvent(event) {
    const payload = event;
    const toolName = typeof payload.name === "string" ? payload.name : "tool";
    const toolCallId = typeof payload.toolCallId === "string" ? payload.toolCallId : `live-${toolName}`;
    const output = payload.output;
    return {
        type: "tool_execution_end",
        toolCallId,
        toolName,
        result: output !== null && typeof output === "object" && "content" in output
            ? output
            : { content: typeof output === "string" ? [{ type: "text", text: output }] : [] },
        isError: payload.isError === true,
    };
}
function legacyThinkingEvent(event) {
    const delta = String(event.delta ??
        event.text ??
        "");
    return {
        type: "message_update",
        assistantMessageEvent: { type: "thinking_delta", delta },
        message: { role: "assistant", content: [] },
    };
}
function extractMessageText(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;
        }
        const obj = item;
        if (typeof obj.text === "string")
            parts.push(obj.text);
        else if (obj.type === "text" && typeof obj.text === "string")
            parts.push(obj.text);
    }
    return parts.join("");
}
function tailStreamingText(text) {
    if (text.length <= STREAMING_TEXT_TAIL_CHARS &&
        text.split("\n").length <= STREAMING_TEXT_TAIL_LINES) {
        return text;
    }
    const byChars = text.slice(-STREAMING_TEXT_TAIL_CHARS);
    const lines = byChars.split("\n");
    const tail = lines.length > STREAMING_TEXT_TAIL_LINES
        ? lines.slice(-STREAMING_TEXT_TAIL_LINES).join("\n")
        : byChars;
    return `[earlier streaming output hidden while attached]\n\n${tail.trimStart()}`;
}
function spinnerFrame() {
    const idx = Math.floor(Date.now() / 80) % SPINNER_FRAMES.length;
    return SPINNER_FRAMES[idx];
}
function stripAnsi(s) {
    return s.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
//# sourceMappingURL=chat-session-host.js.map