import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
 * ============================================================
 *  src/ui/MessageList.tsx —— 消息历史展示
 * ------------------------------------------------------------
 *  渲染历史消息（system 不显示）。
 *  使用 Ink 5.x 的 <Static> commit 历史到终端 scrollback：
 *  鼠标滚轮可以自然翻阅，性能也更好（已 commit 的行不会再 rerender）。
 *
 *  流式中的 assistant 文本不再在这里"假消息"显示——
 *  streaming 阶段不渲染 live answer，turn 结束后再以正式的
 *  assistant Message 进入 history 并被 <Static> commit。
 *
 *  banner 也走 Static 流（items[0]）。
 *  Ink 5 的 Static 约定要求 Static 是兄弟节点中第一个，否则它上方的 live
 *  兄弟会跟 Static commit 一起被重打到 stdout（用户实测看到 banner 被
 *  反复打印 N 次）。把 banner 作为 Static 的第一个 item，banner 自然
 *  commit 进 scrollback，启动时打印一次，不再反复出现。
 *
 *  v2 新增：
 *    - assistant 分支渲染 reasoning_content / reasoning（💭 dim gray）；
 *      窄终端（rows<28）折叠 2 行 + "▾ N 行 reasoning 折叠"
 *    - tool 分支显示 tool_call_id 末 8 字符作为 [tool result · #abc12345]，
 *      "▾ 完整 N 行（输入 /show xxx 查看）" 提示用户用命令展开
 *    - user 分支前插 turn 头横线 "────── Turn N · 14:32 · 8s · 6 tools ──────"，
 *      turn 边界扫描结果由 useMemo 一次性算出，挂在 StaticItem 上
 *
 *  样式约定：
 *    banner:    青色加粗 minimal-agent + 暗灰色 provider/快捷键提示
 *    user:      整条青色 + "> " 前缀（输入；前置 turn 横线，若 turnMeta 非空）
 *    assistant: reasoning 暗灰带 💭，content 绿色 ⏺ 锚点 + 默认亮色正文（输出），tool_calls 黄色
 *    tool:      暗灰色，折叠（默认只显示前 3 行，末尾提示用 /show 查看）
 * ============================================================
 */
import React from 'react';
import { Box, Static, Text } from 'ink';
/** 中断标记 user message 的固定内容（loop.ts 写入） */
const INTERRUPT_USER_CONTENT = '(用户按下了 ESC/Ctrl+C 中断了任务)';
const MAX_TOOL_PREVIEW_LINES = 3;
/** 窄终端 reasoning 折叠阈值（行）—— 小于此值时 reasoning 只显示首 2 行 */
const NARROW_TERMINAL_ROWS = 28;
/** reasoning 超过此字符数截尾 —— 防撑爆历史 */
const REASONING_MAX_CHARS = 2000;
/**
 * 把 assistant message 的两个字符串型 reasoning 字段拼成单字符串。
 * `reasoning_details` 是结构化数组，本期不展开。两个字段任一非空即返回拼接结果；
 * 都空返回 ''。
 *
 * 导出供单测断言。
 */
export function combineReasoning(message) {
    const parts = [];
    if (typeof message.reasoning_content === 'string' &&
        message.reasoning_content.length > 0) {
        parts.push(message.reasoning_content);
    }
    if (typeof message.reasoning === 'string' && message.reasoning.length > 0) {
        parts.push(message.reasoning);
    }
    return parts.join('\n');
}
/**
 * 扫一遍 history，按 user message 切分 turn，返回每条 message 对应的 turnMeta。
 * 返回数组长度 = history.length，与 history 一一对应：
 *   - user message（非中断）→ 该 turn 的 TurnMeta（turnN 递增，>=1）
 *   - 中断标记 user / assistant / tool / system → null
 *
 * 算法：
 *   1) 先识别"turn 起点"：所有非中断 user message
 *   2) 每个 turn 起点 turnN 从 1 递增
 *   3) turn 结束位置 = 下一个 turn 起点之前（或 history 末尾）
 *   4) durationMs = turn 内最后一条 assistant/tool 的 timestamp - user.timestamp
 *   5) toolsCount = turn 内所有 assistant.tool_calls?.length 累加
 *
 * 导出供单测断言。
 */
export function summarizeTurn(history) {
    const result = new Array(history.length).fill(null);
    // 找出所有 turn 起点的 index
    const turnStartIndices = [];
    for (let i = 0; i < history.length; i++) {
        const m = history[i];
        if (m.role === 'user' && m.content !== INTERRUPT_USER_CONTENT) {
            turnStartIndices.push(i);
        }
    }
    for (let t = 0; t < turnStartIndices.length; t++) {
        const startIdx = turnStartIndices[t];
        const endIdx = t + 1 < turnStartIndices.length
            ? turnStartIndices[t + 1] - 1
            : history.length - 1;
        const startMsg = history[startIdx];
        const userTimestamp = startMsg.timestamp ?? Date.now();
        let lastResponseTs = null;
        let toolsCount = 0;
        for (let j = startIdx + 1; j <= endIdx; j++) {
            const m = history[j];
            if (m.role === 'assistant') {
                toolsCount += m.tool_calls?.length ?? 0;
                if (typeof m.timestamp === 'number')
                    lastResponseTs = m.timestamp;
            }
            else if (m.role === 'tool') {
                if (typeof m.timestamp === 'number')
                    lastResponseTs = m.timestamp;
            }
        }
        const durationMs = lastResponseTs !== null ? Math.max(0, lastResponseTs - userTimestamp) : 0;
        result[startIdx] = {
            turnN: t + 1,
            durationMs,
            toolsCount,
            timestamp: userTimestamp,
        };
    }
    return result;
}
/** 格式化 timestamp 为 HH:MM（本地时区） */
function formatTime(ts) {
    return new Date(ts).toTimeString().slice(0, 5);
}
/** 格式化耗时：<1s / 1.2s / 3m */
function formatDuration(ms) {
    if (ms < 1000)
        return '<1s';
    if (ms < 60_000)
        return `${(ms / 1000).toFixed(1)}s`;
    if (ms < 3_600_000)
        return `${Math.floor(ms / 60_000)}m`;
    return `${Math.floor(ms / 3_600_000)}h`;
}
/**
 * React.memo 包装。
 *
 * props 浅比较够用：
 *  - history 引用变化才需要重算 items（useMemo 依赖正确）
 *  - compactGeneration 数字
 *  - bannerText / terminalRows 都是基础类型 / 启动后稳定
 *
 * App.tsx 中用 useDeferredValue(chat.history) 把 history 推后到低优先级队列，
 * 配合此 memo，streaming 期间高频 setStreamingText 不再带着 MessageList
 * 重算 items + Static reconcile。
 */
export const MessageList = React.memo(function MessageList({ history, compactGeneration, bannerText, terminalRows, }) {
    const items = React.useMemo(() => {
        const turnMetas = summarizeTurn(history);
        return [
            ...(bannerText
                ? [
                    {
                        kind: 'banner',
                        text: bannerText,
                        key: `banner-${bannerText.slice(0, 10)}`,
                    },
                ]
                : []),
            ...history.map((m, idx) => ({
                kind: 'message',
                msg: m,
                turnMeta: turnMetas[idx],
                // 纯派生 key：有 id 用 id，没有就用 idx 兜底。绝不在 render 期 mutate 消息对象
                // （<Static> 已 commit 的行永不重绘，key 仅在 append 对账时用，idx 已足够稳定）。
                key: m.id ?? `message-${idx}`,
            })),
        ];
    }, [bannerText, history]);
    // 窄终端判定（terminalRows 缺失时默认不折叠，保留完整 reasoning）
    const foldReasoning = typeof terminalRows === 'number' && terminalRows < NARROW_TERMINAL_ROWS;
    return (_jsx(Static, { items: items, children: (item) => {
            if (item.kind === 'banner') {
                return _jsx(BannerRow, { text: item.text }, item.key);
            }
            return (_jsx(MessageRow, { message: item.msg, turnMeta: item.turnMeta, foldReasoning: foldReasoning }, item.key));
        } }, compactGeneration));
});
function BannerRow({ text }) {
    // text 形如 "minimal-agent  · provider/model · /new ... · Ctrl+C 中断"
    // 拆出第一个 token "minimal-agent" 走加粗青色，剩下走暗灰。
    const splitAt = text.indexOf('  ·');
    const head = splitAt >= 0 ? text.slice(0, splitAt) : text;
    const tail = splitAt >= 0 ? text.slice(splitAt) : '';
    return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: head }), tail.length > 0 && _jsx(Text, { color: "gray", children: tail })] }));
}
function MessageRow({ message, turnMeta, foldReasoning, }) {
    switch (message.role) {
        case 'system': {
            // v2.x：ephemeral system message 是 UI 临时提示（slash 命令输出等），
            // 用暗灰单行风格显示；正经 system prompt（_ephemeral=undefined）依然 hide。
            const ephemeral = message._ephemeral === true;
            if (!ephemeral)
                return null;
            const lines = message.content.split('\n');
            return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: "gray", dimColor: true, children: line }, `eph-${i}`))) }) }));
        }
        case 'user': {
            const headerLine = turnMeta !== null
                ? `────── Turn ${turnMeta.turnN} · ${formatTime(turnMeta.timestamp)} · ${formatDuration(turnMeta.durationMs)} · ${turnMeta.toolsCount} tools ──────`
                : null;
            return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [headerLine !== null && (_jsx(Text, { color: "gray", dimColor: true, children: headerLine })), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: '> ' }), _jsx(Text, { color: "cyan", children: message.content })] })] }));
        }
        case 'assistant': {
            // assistant 可能只有 tool_calls 没有 content（content 为 null）
            const text = message.content ?? '';
            const tcCount = message.tool_calls?.length ?? 0;
            // reasoning 拼接
            let reasoningText = combineReasoning(message);
            let truncatedCharsHint = '';
            if (reasoningText.length > REASONING_MAX_CHARS) {
                const omitted = reasoningText.length - REASONING_MAX_CHARS;
                reasoningText = reasoningText.slice(0, REASONING_MAX_CHARS);
                truncatedCharsHint = `...(${omitted} 字省略)`;
            }
            const reasoningLines = reasoningText.length > 0 ? reasoningText.split('\n') : [];
            const visibleReasoningLines = foldReasoning
                ? reasoningLines.slice(0, 2)
                : reasoningLines;
            const hiddenReasoningCount = reasoningLines.length - visibleReasoningLines.length;
            return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [visibleReasoningLines.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [visibleReasoningLines.map((line, i) => (_jsx(Text, { color: "gray", dimColor: true, children: i === 0 ? `💭 ${line}` : `   ${line}` }, `r-${i}`))), hiddenReasoningCount > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: `   ▾ ${hiddenReasoningCount} 行 reasoning 折叠（终端高度 < ${NARROW_TERMINAL_ROWS}）` })), truncatedCharsHint.length > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: `   ${truncatedCharsHint}` }))] })), text.length > 0 &&
                        text.split('\n').map((line, i) => i === 0 ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", bold: true, children: '⏺ ' }), _jsx(Text, { children: line.length > 0 ? line : ' ' })] }, `a-${i}`)) : (_jsx(Text, { children: `  ${line}` }, `a-${i}`))), tcCount > 0 && (_jsx(Text, { color: "yellow", dimColor: true, children: `  └─ 调用了 ${tcCount} 个工具：${(message.tool_calls ?? [])
                            .map((tc) => tc.function.name)
                            .join(', ')}` }))] }));
        }
        case 'tool': {
            const lines = message.content.split('\n');
            const preview = lines.slice(0, MAX_TOOL_PREVIEW_LINES).join('\n');
            const hidden = Math.max(0, lines.length - MAX_TOOL_PREVIEW_LINES);
            const shortId = message.tool_call_id.slice(-8);
            return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", dimColor: true, children: `[tool result · #${shortId}]` }), _jsx(Text, { color: "gray", children: preview }), hidden > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: `... ▾ 完整 ${lines.length} 行（输入 /show ${shortId} 查看）` }))] }) }));
        }
    }
}
