/**
 * Terminal streaming renderer for Clawx.
 *
 * EXTRACTION NOTE:
 * OpenClaw's streaming (pi-embedded-subscribe.ts, 726 lines) handles:
 * - Thinking/reasoning tag stripping with code-span awareness
 * - Block chunking for channel message size limits
 * - Messaging tool duplicate detection
 * - Compaction retry coordination
 * - Multi-level buffering with partial block state
 * - Channel-aware formatting (markdown vs plain)
 *
 * We DISCARD all of that. For terminal output, we render agent events directly:
 * - Stream text deltas as they arrive
 * - Show tool calls with their names and a spinner
 * - Show tool results
 * - Show errors
 *
 * Clean, simple, terminal-native.
 */
import chalk from "chalk";
// Federation tool categories
const FEDERATION_DISPATCH = new Set(["agent_run", "agent_batch", "agent_send"]);
const FEDERATION_MGMT = new Set([
    "agent_serve", "agent_spawn_local", "agent_list", "agent_remove",
    "agent_network", "agent_status", "agent_result", "agent_cleanup",
    "agent_chat", "agent_chat_direct", "agent_rehydrate_workers",
    "agent_master_status",
]);
const FEDERATION_PEER = new Set([
    "agent_peer_add", "agent_peer_serve", "agent_peer_send",
    "agent_peer_list_workers", "agent_peer_chat",
]);
function getFederationTag(toolName, args) {
    if (FEDERATION_DISPATCH.has(toolName)) {
        const worker = args.worker_id || args.worker || args.agent_id;
        const target = args.target || worker;
        if (target)
            return chalk.magenta(`[dispatch:${target}]`);
        return chalk.magenta("[dispatch:auto]");
    }
    if (FEDERATION_PEER.has(toolName)) {
        const peer = args.peer_name || args.peer || args.peer_id;
        if (peer)
            return chalk.blue(`[peer:${peer}]`);
        return chalk.blue("[peer]");
    }
    if (FEDERATION_MGMT.has(toolName)) {
        return chalk.dim("[federation]");
    }
    return null;
}
export function createStreamRenderer(options) {
    let currentToolName = null;
    let toolCallCount = 0;
    let turnCount = 0;
    let toolStartTime = 0;
    let turnStartTime = 0;
    let agentStartTime = 0;
    const toolTimings = [];
    let federationCalls = 0;
    let peerCalls = 0;
    let dispatchCalls = 0;
    return {
        onEvent(event) {
            switch (event.type) {
                case "agent_start": {
                    agentStartTime = Date.now();
                    const net = options?.getNetworkSnapshot?.();
                    if (net?.serving) {
                        const parts = [];
                        if (net.workers > 0)
                            parts.push(`${net.workers} worker${net.workers !== 1 ? "s" : ""}`);
                        if (net.peers > 0)
                            parts.push(`${net.peers} peer${net.peers !== 1 ? "s" : ""}`);
                        const netInfo = parts.length > 0 ? parts.join(", ") : "no workers";
                        process.stderr.write(chalk.dim(`[network] master on :${net.port || "?"} — ${netInfo}\n`));
                    }
                    break;
                }
                case "turn_start":
                    turnCount++;
                    turnStartTime = Date.now();
                    process.stderr.write(chalk.dim(`\n── Turn ${turnCount} ──\n`));
                    break;
                case "message_start":
                    if (event.message.role === "assistant") {
                        // New assistant response starting
                    }
                    break;
                case "message_update": {
                    const evt = event.assistantMessageEvent;
                    if (evt.type === "text_delta") {
                        process.stdout.write(evt.delta);
                    }
                    else if (evt.type === "thinking_delta") {
                        // Show thinking in dim if present
                        process.stderr.write(chalk.dim(evt.delta));
                    }
                    break;
                }
                case "message_end": {
                    const msg = event.message;
                    if (msg.role === "assistant") {
                        const assistant = msg;
                        // Ensure newline after streamed text
                        const hasText = assistant.content.some((c) => c.type === "text" && c.text.trim().length > 0);
                        if (hasText) {
                            process.stdout.write("\n");
                        }
                        // Show stop reason if not normal
                        if (assistant.stopReason === "error") {
                            console.error(chalk.red(`\n[error] ${assistant.errorMessage || "Unknown error"}`));
                        }
                        else if (assistant.stopReason === "length") {
                            console.error(chalk.yellow("\n[truncated — max tokens reached]"));
                        }
                    }
                    break;
                }
                case "tool_execution_start": {
                    currentToolName = event.toolName;
                    toolCallCount++;
                    toolStartTime = Date.now();
                    const fedTag = getFederationTag(event.toolName, event.args || {});
                    if (fedTag) {
                        federationCalls++;
                        if (FEDERATION_DISPATCH.has(event.toolName))
                            dispatchCalls++;
                        if (FEDERATION_PEER.has(event.toolName))
                            peerCalls++;
                    }
                    const argsPreview = formatToolArgs(event.args);
                    process.stderr.write(chalk.cyan(`\n[tool] ${event.toolName}`) +
                        (fedTag ? ` ${fedTag}` : "") +
                        chalk.gray(` ${argsPreview}\n`));
                    break;
                }
                case "tool_execution_update":
                    // Could show progress, but most tools are fast
                    break;
                case "tool_execution_end": {
                    const elapsed = Date.now() - toolStartTime;
                    toolTimings.push({ name: event.toolName, ms: elapsed });
                    currentToolName = null;
                    const isErr = event.isError;
                    const resultText = extractResultText(event.result);
                    const truncated = resultText.length > 500
                        ? resultText.slice(0, 500) + "... (truncated)"
                        : resultText;
                    const timeStr = chalk.dim(` (${formatDuration(elapsed)})`);
                    if (isErr) {
                        process.stderr.write(chalk.red(`  [error] ${truncated}`) + timeStr + "\n");
                    }
                    else {
                        process.stderr.write(chalk.gray(`  ${truncated}`) + timeStr + "\n");
                    }
                    break;
                }
                case "turn_end": {
                    const turnElapsed = Date.now() - turnStartTime;
                    process.stderr.write(chalk.dim(`── Turn ${turnCount} complete (${formatDuration(turnElapsed)}) ──\n`));
                    break;
                }
                case "agent_end":
                    break;
            }
        },
        finish() {
            const totalElapsed = Date.now() - agentStartTime;
            if (toolCallCount > 0) {
                // Summary line
                process.stderr.write(chalk.dim(`\n�── Summary: ${turnCount} turn${turnCount !== 1 ? "s" : ""}, `) +
                    chalk.dim(`${toolCallCount} tool call${toolCallCount !== 1 ? "s" : ""}, `) +
                    chalk.dim(`${formatDuration(totalElapsed)} total ──\n`));
                // Tool timing breakdown (group by tool name)
                const grouped = new Map();
                for (const t of toolTimings) {
                    const entry = grouped.get(t.name) || { count: 0, totalMs: 0 };
                    entry.count++;
                    entry.totalMs += t.ms;
                    grouped.set(t.name, entry);
                }
                for (const [name, stats] of grouped) {
                    const avg = Math.round(stats.totalMs / stats.count);
                    process.stderr.write(chalk.dim(`  ${name}: ${stats.count}x, ${formatDuration(stats.totalMs)} total`) +
                        (stats.count > 1 ? chalk.dim(` (avg ${formatDuration(avg)})`) : "") +
                        "\n");
                }
                // Federation stats
                if (federationCalls > 0) {
                    let fedLine = chalk.magenta(`  federation: ${federationCalls} call${federationCalls !== 1 ? "s" : ""}`);
                    const parts = [];
                    if (dispatchCalls > 0)
                        parts.push(`${dispatchCalls} dispatched`);
                    if (peerCalls > 0)
                        parts.push(`${peerCalls} peer`);
                    if (parts.length > 0)
                        fedLine += chalk.dim(` (${parts.join(", ")})`);
                    process.stderr.write(fedLine + "\n");
                }
            }
            else if (totalElapsed > 0) {
                process.stderr.write(chalk.dim(`\n[done] ${formatDuration(totalElapsed)}\n`));
            }
        },
    };
}
function formatDuration(ms) {
    if (ms < 1000)
        return `${ms}ms`;
    if (ms < 60000)
        return `${(ms / 1000).toFixed(1)}s`;
    const mins = Math.floor(ms / 60000);
    const secs = Math.round((ms % 60000) / 1000);
    return `${mins}m${secs}s`;
}
function formatToolArgs(args) {
    if (!args || Object.keys(args).length === 0)
        return "";
    const parts = [];
    for (const [key, value] of Object.entries(args)) {
        if (typeof value === "string" && value.length > 80) {
            parts.push(`${key}="${value.slice(0, 80)}..."`);
        }
        else if (typeof value === "string") {
            parts.push(`${key}="${value}"`);
        }
        else {
            parts.push(`${key}=${JSON.stringify(value)}`);
        }
    }
    return parts.join(" ");
}
function extractResultText(result) {
    if (!result || typeof result !== "object")
        return String(result);
    const r = result;
    if (Array.isArray(r.content)) {
        return r.content
            .filter((c) => c.type === "text" && c.text)
            .map((c) => c.text)
            .join("\n");
    }
    return JSON.stringify(result).slice(0, 200);
}
//# sourceMappingURL=streaming.js.map