import { CombinedAutocompleteProvider, Container, Loader, ProcessTerminal, Text, TUI, } from "@mariozechner/pi-tui";
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
import { loadConfig } from "../config/config.js";
import { buildAgentMainSessionKey, normalizeAgentId, normalizeMainKey, parseAgentSessionKey, } from "../routing/session-key.js";
import { getSlashCommands } from "./commands.js";
import { ChatLog } from "./components/chat-log.js";
import { CustomEditor } from "./components/custom-editor.js";
import { GatewayChatClient } from "./gateway-chat.js";
import { editorTheme, theme } from "./theme/theme.js";
import { createCommandHandlers } from "./tui-command-handlers.js";
import { createEventHandlers } from "./tui-event-handlers.js";
import { formatTokens } from "./tui-formatters.js";
import { createLocalShellRunner } from "./tui-local-shell.js";
import { buildWaitingStatusMessage, defaultWaitingPhrases } from "./tui-waiting.js";
import { createOverlayHandlers } from "./tui-overlays.js";
import { createSessionActions } from "./tui-session-actions.js";
export { resolveFinalAssistantText } from "./tui-formatters.js";
export function createEditorSubmitHandler(params) {
    return (text) => {
        const raw = text;
        const value = raw.trim();
        params.editor.setText("");
        // Keep previous behavior: ignore empty/whitespace-only submissions.
        if (!value)
            return;
        // Bash mode: only if the very first character is '!' and it's not just '!'.
        // IMPORTANT: use the raw (untrimmed) text so leading spaces do NOT trigger.
        // Per requirement: a lone '!' should be treated as a normal message.
        if (raw.startsWith("!") && raw !== "!") {
            params.editor.addToHistory(raw);
            void params.handleBangLine(raw);
            return;
        }
        // Enable built-in editor prompt history navigation (up/down).
        params.editor.addToHistory(value);
        if (value.startsWith("/")) {
            void params.handleCommand(value);
            return;
        }
        void params.sendMessage(value);
    };
}
export async function runTui(opts) {
    const config = loadConfig();
    const initialSessionInput = (opts.session ?? "").trim();
    let sessionScope = (config.session?.scope ?? "per-sender");
    let sessionMainKey = normalizeMainKey(config.session?.mainKey);
    let agentDefaultId = resolveDefaultAgentId(config);
    let currentAgentId = agentDefaultId;
    let agents = [];
    const agentNames = new Map();
    let currentSessionKey = "";
    let initialSessionApplied = false;
    let currentSessionId = null;
    let activeChatRunId = null;
    let historyLoaded = false;
    let isConnected = false;
    let wasDisconnected = false;
    let toolsExpanded = false;
    let showThinking = false;
    const deliverDefault = opts.deliver ?? false;
    const autoMessage = opts.message?.trim();
    let autoMessageSent = false;
    let sessionInfo = {};
    let lastCtrlCAt = 0;
    let activityStatus = "idle";
    let connectionStatus = "connecting";
    let statusTimeout = null;
    let statusTimer = null;
    let statusStartedAt = null;
    let lastActivityStatus = activityStatus;
    const state = {
        get agentDefaultId() {
            return agentDefaultId;
        },
        set agentDefaultId(value) {
            agentDefaultId = value;
        },
        get sessionMainKey() {
            return sessionMainKey;
        },
        set sessionMainKey(value) {
            sessionMainKey = value;
        },
        get sessionScope() {
            return sessionScope;
        },
        set sessionScope(value) {
            sessionScope = value;
        },
        get agents() {
            return agents;
        },
        set agents(value) {
            agents = value;
        },
        get currentAgentId() {
            return currentAgentId;
        },
        set currentAgentId(value) {
            currentAgentId = value;
        },
        get currentSessionKey() {
            return currentSessionKey;
        },
        set currentSessionKey(value) {
            currentSessionKey = value;
        },
        get currentSessionId() {
            return currentSessionId;
        },
        set currentSessionId(value) {
            currentSessionId = value;
        },
        get activeChatRunId() {
            return activeChatRunId;
        },
        set activeChatRunId(value) {
            activeChatRunId = value;
        },
        get historyLoaded() {
            return historyLoaded;
        },
        set historyLoaded(value) {
            historyLoaded = value;
        },
        get sessionInfo() {
            return sessionInfo;
        },
        set sessionInfo(value) {
            sessionInfo = value;
        },
        get initialSessionApplied() {
            return initialSessionApplied;
        },
        set initialSessionApplied(value) {
            initialSessionApplied = value;
        },
        get isConnected() {
            return isConnected;
        },
        set isConnected(value) {
            isConnected = value;
        },
        get autoMessageSent() {
            return autoMessageSent;
        },
        set autoMessageSent(value) {
            autoMessageSent = value;
        },
        get toolsExpanded() {
            return toolsExpanded;
        },
        set toolsExpanded(value) {
            toolsExpanded = value;
        },
        get showThinking() {
            return showThinking;
        },
        set showThinking(value) {
            showThinking = value;
        },
        get connectionStatus() {
            return connectionStatus;
        },
        set connectionStatus(value) {
            connectionStatus = value;
        },
        get activityStatus() {
            return activityStatus;
        },
        set activityStatus(value) {
            activityStatus = value;
        },
        get statusTimeout() {
            return statusTimeout;
        },
        set statusTimeout(value) {
            statusTimeout = value;
        },
        get lastCtrlCAt() {
            return lastCtrlCAt;
        },
        set lastCtrlCAt(value) {
            lastCtrlCAt = value;
        },
    };
    const client = new GatewayChatClient({
        url: opts.url,
        token: opts.token,
        password: opts.password,
    });
    const tui = new TUI(new ProcessTerminal());
    const header = new Text("", 1, 0);
    const statusContainer = new Container();
    const footer = new Text("", 1, 0);
    const chatLog = new ChatLog();
    const editor = new CustomEditor(tui, editorTheme);
    const root = new Container();
    root.addChild(header);
    root.addChild(chatLog);
    root.addChild(statusContainer);
    root.addChild(footer);
    root.addChild(editor);
    const updateAutocompleteProvider = () => {
        editor.setAutocompleteProvider(new CombinedAutocompleteProvider(getSlashCommands({
            cfg: config,
            provider: sessionInfo.modelProvider,
            model: sessionInfo.model,
        }), process.cwd()));
    };
    tui.addChild(root);
    tui.setFocus(editor);
    const formatSessionKey = (key) => {
        if (key === "global" || key === "unknown")
            return key;
        const parsed = parseAgentSessionKey(key);
        return parsed?.rest ?? key;
    };
    const formatAgentLabel = (id) => {
        const name = agentNames.get(id);
        return name ? `${id} (${name})` : id;
    };
    const resolveSessionKey = (raw) => {
        const trimmed = (raw ?? "").trim();
        if (sessionScope === "global")
            return "global";
        if (!trimmed) {
            return buildAgentMainSessionKey({
                agentId: currentAgentId,
                mainKey: sessionMainKey,
            });
        }
        if (trimmed === "global" || trimmed === "unknown")
            return trimmed;
        if (trimmed.startsWith("agent:"))
            return trimmed;
        return `agent:${currentAgentId}:${trimmed}`;
    };
    currentSessionKey = resolveSessionKey(initialSessionInput);
    const updateHeader = () => {
        const sessionLabel = formatSessionKey(currentSessionKey);
        const agentLabel = formatAgentLabel(currentAgentId);
        header.setText(theme.header(`openclaw-cn tui - ${client.connection.url} - agent ${agentLabel} - session ${sessionLabel}`));
    };
    const busyStates = new Set(["sending", "waiting", "streaming", "running"]);
    let statusText = null;
    let statusLoader = null;
    const formatElapsed = (startMs) => {
        const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1000));
        if (totalSeconds < 60)
            return `${totalSeconds}s`;
        const minutes = Math.floor(totalSeconds / 60);
        const seconds = totalSeconds % 60;
        return `${minutes}m ${seconds}s`;
    };
    const ensureStatusText = () => {
        if (statusText)
            return;
        statusContainer.clear();
        statusLoader?.stop();
        statusLoader = null;
        statusText = new Text("", 1, 0);
        statusContainer.addChild(statusText);
    };
    const ensureStatusLoader = () => {
        if (statusLoader)
            return;
        statusContainer.clear();
        statusText = null;
        statusLoader = new Loader(tui, (spinner) => theme.accent(spinner), (text) => theme.bold(theme.accentSoft(text)), "");
        statusContainer.addChild(statusLoader);
    };
    let waitingTick = 0;
    let waitingTimer = null;
    let waitingPhrase = null;
    const updateBusyStatusMessage = () => {
        if (!statusLoader || !statusStartedAt)
            return;
        const elapsed = formatElapsed(statusStartedAt);
        if (activityStatus === "waiting") {
            waitingTick++;
            statusLoader.setMessage(buildWaitingStatusMessage({
                theme,
                tick: waitingTick,
                elapsed,
                connectionStatus,
                phrases: waitingPhrase ? [waitingPhrase] : undefined,
            }));
            return;
        }
        statusLoader.setMessage(`${activityStatus} • ${elapsed} | ${connectionStatus}`);
    };
    const startStatusTimer = () => {
        if (statusTimer)
            return;
        statusTimer = setInterval(() => {
            if (!busyStates.has(activityStatus))
                return;
            updateBusyStatusMessage();
        }, 1000);
    };
    const stopStatusTimer = () => {
        if (!statusTimer)
            return;
        clearInterval(statusTimer);
        statusTimer = null;
    };
    const startWaitingTimer = () => {
        if (waitingTimer)
            return;
        // Pick a phrase once per waiting session.
        if (!waitingPhrase) {
            const idx = Math.floor(Math.random() * defaultWaitingPhrases.length);
            waitingPhrase = defaultWaitingPhrases[idx] ?? defaultWaitingPhrases[0] ?? "waiting";
        }
        waitingTick = 0;
        waitingTimer = setInterval(() => {
            if (activityStatus !== "waiting")
                return;
            updateBusyStatusMessage();
        }, 120);
    };
    const stopWaitingTimer = () => {
        if (!waitingTimer)
            return;
        clearInterval(waitingTimer);
        waitingTimer = null;
        waitingPhrase = null;
    };
    const renderStatus = () => {
        const isBusy = busyStates.has(activityStatus);
        if (isBusy) {
            if (!statusStartedAt || lastActivityStatus !== activityStatus) {
                statusStartedAt = Date.now();
            }
            ensureStatusLoader();
            if (activityStatus === "waiting") {
                stopStatusTimer();
                startWaitingTimer();
            }
            else {
                stopWaitingTimer();
                startStatusTimer();
            }
            updateBusyStatusMessage();
        }
        else {
            statusStartedAt = null;
            stopStatusTimer();
            stopWaitingTimer();
            statusLoader?.stop();
            statusLoader = null;
            ensureStatusText();
            const text = activityStatus ? `${connectionStatus} | ${activityStatus}` : connectionStatus;
            statusText?.setText(theme.dim(text));
        }
        lastActivityStatus = activityStatus;
    };
    const setConnectionStatus = (text, ttlMs) => {
        connectionStatus = text;
        renderStatus();
        if (statusTimeout)
            clearTimeout(statusTimeout);
        if (ttlMs && ttlMs > 0) {
            statusTimeout = setTimeout(() => {
                connectionStatus = isConnected ? "connected" : "disconnected";
                renderStatus();
            }, ttlMs);
        }
    };
    const setActivityStatus = (text) => {
        activityStatus = text;
        renderStatus();
    };
    const updateFooter = () => {
        const sessionKeyLabel = formatSessionKey(currentSessionKey);
        const sessionLabel = sessionInfo.displayName
            ? `${sessionKeyLabel} (${sessionInfo.displayName})`
            : sessionKeyLabel;
        const agentLabel = formatAgentLabel(currentAgentId);
        const modelLabel = sessionInfo.model
            ? sessionInfo.modelProvider
                ? `${sessionInfo.modelProvider}/${sessionInfo.model}`
                : sessionInfo.model
            : "unknown";
        const tokens = formatTokens(sessionInfo.totalTokens ?? null, sessionInfo.contextTokens ?? null);
        const think = sessionInfo.thinkingLevel ?? "off";
        const verbose = sessionInfo.verboseLevel ?? "off";
        const reasoning = sessionInfo.reasoningLevel ?? "off";
        const reasoningLabel = reasoning === "on" ? "reasoning" : reasoning === "stream" ? "reasoning:stream" : null;
        const footerParts = [
            `agent ${agentLabel}`,
            `session ${sessionLabel}`,
            modelLabel,
            think !== "off" ? `think ${think}` : null,
            verbose !== "off" ? `verbose ${verbose}` : null,
            reasoningLabel,
            tokens,
        ].filter(Boolean);
        footer.setText(theme.dim(footerParts.join(" | ")));
    };
    const { openOverlay, closeOverlay } = createOverlayHandlers(tui, editor);
    const initialSessionAgentId = (() => {
        if (!initialSessionInput)
            return null;
        const parsed = parseAgentSessionKey(initialSessionInput);
        return parsed ? normalizeAgentId(parsed.agentId) : null;
    })();
    const sessionActions = createSessionActions({
        client,
        chatLog,
        tui,
        opts,
        state,
        agentNames,
        initialSessionInput,
        initialSessionAgentId,
        resolveSessionKey,
        updateHeader,
        updateFooter,
        updateAutocompleteProvider,
        setActivityStatus,
    });
    const { refreshAgents, refreshSessionInfo, loadHistory, setSession, abortActive } = sessionActions;
    const { handleChatEvent, handleAgentEvent } = createEventHandlers({
        chatLog,
        tui,
        state,
        setActivityStatus,
        refreshSessionInfo,
    });
    const { handleCommand, sendMessage, openModelSelector, openAgentSelector, openSessionSelector } = createCommandHandlers({
        client,
        chatLog,
        tui,
        opts,
        state,
        deliverDefault,
        openOverlay,
        closeOverlay,
        refreshSessionInfo,
        loadHistory,
        setSession,
        refreshAgents,
        abortActive,
        setActivityStatus,
        formatSessionKey,
    });
    const { runLocalShellLine } = createLocalShellRunner({
        chatLog,
        tui,
        openOverlay,
        closeOverlay,
    });
    updateAutocompleteProvider();
    editor.onSubmit = createEditorSubmitHandler({
        editor,
        handleCommand,
        sendMessage,
        handleBangLine: runLocalShellLine,
    });
    editor.onEscape = () => {
        void abortActive();
    };
    editor.onCtrlC = () => {
        const now = Date.now();
        if (editor.getText().trim().length > 0) {
            editor.setText("");
            setActivityStatus("cleared input");
            tui.requestRender();
            return;
        }
        if (now - lastCtrlCAt < 1000) {
            client.stop();
            tui.stop();
            process.exit(0);
        }
        lastCtrlCAt = now;
        setActivityStatus("press ctrl+c again to exit");
        tui.requestRender();
    };
    editor.onCtrlD = () => {
        client.stop();
        tui.stop();
        process.exit(0);
    };
    editor.onCtrlO = () => {
        toolsExpanded = !toolsExpanded;
        chatLog.setToolsExpanded(toolsExpanded);
        setActivityStatus(toolsExpanded ? "tools expanded" : "tools collapsed");
        tui.requestRender();
    };
    editor.onCtrlL = () => {
        void openModelSelector();
    };
    editor.onCtrlG = () => {
        void openAgentSelector();
    };
    editor.onCtrlP = () => {
        void openSessionSelector();
    };
    editor.onCtrlT = () => {
        showThinking = !showThinking;
        void loadHistory();
    };
    client.onEvent = (evt) => {
        if (evt.event === "chat")
            handleChatEvent(evt.payload);
        if (evt.event === "agent")
            handleAgentEvent(evt.payload);
    };
    client.onConnected = () => {
        isConnected = true;
        const reconnected = wasDisconnected;
        wasDisconnected = false;
        setConnectionStatus("connected");
        void (async () => {
            await refreshAgents();
            updateHeader();
            await loadHistory();
            setConnectionStatus(reconnected ? "gateway reconnected" : "gateway connected", 4000);
            tui.requestRender();
            if (!autoMessageSent && autoMessage) {
                autoMessageSent = true;
                await sendMessage(autoMessage);
            }
            updateFooter();
            tui.requestRender();
        })();
    };
    client.onDisconnected = (reason) => {
        isConnected = false;
        wasDisconnected = true;
        historyLoaded = false;
        const reasonLabel = reason?.trim() ? reason.trim() : "closed";
        setConnectionStatus(`gateway disconnected: ${reasonLabel}`, 5000);
        setActivityStatus("idle");
        updateFooter();
        tui.requestRender();
    };
    client.onGap = (info) => {
        setConnectionStatus(`event gap: expected ${info.expected}, got ${info.received}`, 5000);
        tui.requestRender();
    };
    updateHeader();
    setConnectionStatus("connecting");
    updateFooter();
    tui.start();
    client.start();
}
