import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
 * ============================================================
 *  src/ui/LiveArea.tsx —— 输入框上方的本轮过程区（有界多行流式 live 区）
 * ------------------------------------------------------------
 *  结构（自上而下）：
 *    ┌ 状态行（恒 1 行）：动画 spinner + 阶段标签 + 已耗时 (+ plugin 进度)
 *    └ 内容区（0..N 行，有界）：随阶段切换——
 *        thinking       → reasoningPreview 尾部最近 N 行（gray dim，首行 💭）
 *        streaming      → streamingText   尾部最近 N 行（默认亮色 = 答案是主角）
 *        tool_executing → runningTools 逐行人话描述（gray dim）
 *        compacting/idle→ 无内容行
 *
 *  ★ 为什么这样能既"多行打字机 + 流式答案"又**不刷屏**（详见 liveViewport.ts 头部）：
 *    - INV-1：每行都用 wrapToDisplayLines / clampWidthStart 按 CJK/emoji=2 钳到
 *      ≤ cols - LIVE_RIGHT_MARGIN → 终端永不自动折行 → ink eraseLines 每帧精确。
 *    - INV-2：内容区取**尾部最近 N 行**（contentMaxLines 自适应终端高度），保证
 *      live 区总高度 < 终端行数 → 不触发 ink 的 outputHeight>=rows 全屏重写。
 *    完整思维链/答案由 <Static>（MessageList）write-once 呈现，不进 live 区。
 *
 *  ★ 动画 spinner + 已耗时：非 idle 时挂一个 ~150ms tick 强制 re-render，保证
 *    "无 token 到达的纯等待期"spinner 仍在跳、秒数仍在走 → 消除"卡住"观感。
 *    流式内容本身走 useChat 的 100ms 节流；ink 内部 32ms throttle 合并，频率安全。
 * ============================================================
 */
import React, { useEffect, useRef, useState } from 'react';
import { Box, Text } from 'ink';
import { LIVE_RIGHT_MARGIN, clampWidthStart, wrapToDisplayLines, } from './liveViewport.js';
const STAGE_LABELS = {
    thinking: '思考中',
    streaming: '回答中',
    tool_executing: '调用工具',
    compacting: '压缩历史',
};
/** 动画 spinner 帧（charWidth 上界按 2 计；终端实际多为 1，算宽无害）。 */
const SPINNER_FRAMES = ['✶', '✸', '✹', '✺', '✹', '✷'];
/** spinner 推进 + 强制 re-render 间隔（ms）。~7Hz，远低于 ink 31Hz throttle，Windows 安全。 */
const SPINNER_INTERVAL_MS = 150;
/** 内容区行数硬顶（大终端也不铺满，保持清爽）。 */
const CONTENT_HARD_CAP = 8;
/** 终端高度里预留给 status(1) + 输入框(3) + StatusLine(1) + error/interrupt + 安全余量。 */
const RESERVED_ROWS = 9;
/** reasoning 行 💭 前缀 / 续行缩进的预留宽度（💭=2 + 空格；上界 4 更稳）。 */
const REASONING_PREFIX_RESERVE = 4;
/** 工具 argsPreview fallback 截断长度。 */
const TOOL_ARGS_MAX = 60;
/** 内容区最多显示几行：自适应终端高度，留足 status/input 余量（满足 INV-2）。 */
export function contentMaxLines(terminalRows) {
    return Math.max(2, Math.min(CONTENT_HARD_CAP, terminalRows - RESERVED_ROWS));
}
/** 取数组尾部最近 n 个元素。 */
function lastN(arr, n) {
    return arr.length <= n ? arr : arr.slice(arr.length - n);
}
function toolDesc(t) {
    if (t.argsFriendly && t.argsFriendly.length > 0)
        return t.argsFriendly;
    const a = t.argsPreview.length > TOOL_ARGS_MAX
        ? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
        : t.argsPreview;
    return `${t.toolName}(${a})`;
}
/**
 * 纯函数：把一帧的 props（+ 动画 frame / 已耗时秒数）算成 LiveView。
 * idle 返回 null。导出供单测断言 INV-1（每行宽）/ INV-2（行数）/ 颜色层次。
 */
export function buildLiveView(props) {
    const { progressStage, reasoningPreview, streamingText, runningTools, pluginProgress, terminalCols, terminalRows, frame, elapsedSec, } = props;
    if (progressStage === 'idle')
        return null;
    const stage = progressStage;
    const maxWidth = Math.max(8, terminalCols - LIVE_RIGHT_MARGIN);
    const maxLines = contentMaxLines(terminalRows);
    // ---- 状态行 ----
    let status = `${frame} ${STAGE_LABELS[stage]} (${elapsedSec}s)`;
    if (pluginProgress) {
        status += `  ·  [plugin:${pluginProgress.pluginId}] ${pluginProgress.current}/${pluginProgress.max ?? '?'}${pluginProgress.message ? ` ${pluginProgress.message}` : ''}`;
    }
    status = clampWidthStart(status, maxWidth);
    // ---- 内容区 ----
    let contentLines = [];
    let contentDim = true;
    if (stage === 'thinking' && reasoningPreview.length > 0) {
        const wrapped = wrapToDisplayLines(reasoningPreview, maxWidth - REASONING_PREFIX_RESERVE);
        contentLines = lastN(wrapped, maxLines).map((line, i) => i === 0 ? `💭 ${line}` : `   ${line}`);
        contentDim = true;
    }
    else if (stage === 'streaming' && streamingText.length > 0) {
        contentLines = lastN(wrapToDisplayLines(streamingText, maxWidth), maxLines);
        contentDim = false; // 答案 = 主角，默认亮色
    }
    else if (stage === 'tool_executing' && runningTools.length > 0) {
        contentLines = lastN(runningTools, maxLines).map((t) => clampWidthStart(`  ${toolDesc(t)}`, maxWidth));
        contentDim = true;
    }
    return {
        statusLine: status,
        statusColor: stage === 'compacting' ? 'magenta' : 'cyan',
        contentLines,
        contentDim,
    };
}
export const LiveArea = React.memo(function LiveArea(props) {
    const { progressStage } = props;
    // 计时起点：idle→非idle 那一刻记下，idle 清零。在 render 中惰性初始化 ref（幂等，安全）。
    const startRef = useRef(null);
    if (progressStage === 'idle') {
        startRef.current = null;
    }
    else if (startRef.current === null) {
        startRef.current = Date.now();
    }
    // 非 idle 时 ~150ms tick：强制 re-render，让 spinner/秒数在纯等待期也持续动。
    const [, setTick] = useState(0);
    useEffect(() => {
        if (progressStage === 'idle')
            return;
        const timer = setInterval(() => setTick((t) => (t + 1) % 1_000_000), SPINNER_INTERVAL_MS);
        return () => clearInterval(timer);
    }, [progressStage]);
    if (progressStage === 'idle')
        return null;
    const now = Date.now();
    const elapsedSec = startRef.current
        ? Math.max(0, Math.floor((now - startRef.current) / 1000))
        : 0;
    const frame = SPINNER_FRAMES[Math.floor(now / SPINNER_INTERVAL_MS) % SPINNER_FRAMES.length];
    const view = buildLiveView({ ...props, frame, elapsedSec });
    if (!view)
        return null;
    return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: view.statusColor, bold: true, wrap: "truncate", children: view.statusLine }), view.contentLines.map((line, i) => (_jsx(Text, { color: view.contentDim ? 'gray' : undefined, dimColor: view.contentDim, wrap: "truncate", children: line.length > 0 ? line : ' ' }, `live-${i}`)))] }));
});
