/**
 * MaiSaka 聊天流实时监控组件
 *
 * 通过 WebSocket 实时接收 MaiSaka 推理引擎事件，
 * 以时间线形式展示聊天流的推理过程。
 */
import {
  Activity,
  ArrowRight,
  Bot,
  Brain,
  CheckCircle2,
  ChevronDown,
  ChevronRight,
  CircleDot,
  Clock,
  Eraser,
  ExternalLink,
  Gauge,
  MessageSquare,
  PauseCircle,
  Timer,
  Wrench,
  XCircle,
  Zap,
} from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle } from '@/components/ui/card'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'
import { useCallback, useEffect, useRef, useState } from 'react'

import type {
  CycleEndEvent,
  CycleStartEvent,
  MaisakaToolCall,
  MessageIngestedEvent,
  PlannerFinalizedEvent,
  PlannerResponseEvent,
  ReplierResponseEvent,
  TimingGateResultEvent,
  ToolExecutionEvent,
} from '@/lib/maisaka-monitor-client'
import type { SessionInfo, TimelineEntry } from './use-maisaka-monitor'
import { useMaisakaMonitor } from './use-maisaka-monitor'

// ─── 工具函数 ──────────────────────────────────────────────────

function formatMs(ms: number): string {
  if (ms < 1000) return `${Math.round(ms)}ms`
  return `${(ms / 1000).toFixed(2)}s`
}

function formatTimestamp(ts: number): string {
  return new Date(ts * 1000).toLocaleTimeString('zh-CN', {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  })
}

function formatRelativeTime(ts: number): string {
  const diff = Date.now() / 1000 - ts
  if (diff < 10) return '刚刚'
  if (diff < 60) return `${Math.round(diff)}秒前`
  if (diff < 3600) return `${Math.round(diff / 60)}分钟前`
  return `${Math.round(diff / 3600)}小时前`
}

// ─── 会话侧边栏 ──────────────────────────────────────────────

function SessionSidebar({
  sessions,
  selectedSession,
  onSelect,
  collapsed,
}: {
  sessions: Map<string, SessionInfo>
  selectedSession: string | null
  onSelect: (id: string) => void
  collapsed: boolean
}) {
  const sortedSessions = Array.from(sessions.values()).sort(
    (a, b) => b.lastActivity - a.lastActivity,
  )
  const getSessionInitial = (session: SessionInfo) => {
    const name = session.sessionName.trim()
    if (name) return name.slice(0, 1)
    return session.isGroupChat ? '群' : '私'
  }

  if (sortedSessions.length === 0) {
    return (
      <div className={cn(
        'flex flex-col items-center justify-center h-full text-muted-foreground gap-2',
        collapsed ? 'p-2' : 'p-4',
      )}>
        <Bot className="h-8 w-8 opacity-40" />
        <p className="text-sm text-center">等待 MaiSaka 会话…</p>
      </div>
    )
  }

  return (
    <div className={cn('flex flex-col gap-1', collapsed ? 'items-center p-2' : 'p-2')}>
      {sortedSessions.map((session) => (
        <button
          key={session.sessionId}
          onClick={() => onSelect(session.sessionId)}
          title={session.sessionName}
          className={cn(
            'rounded-lg text-left text-sm transition-colors',
            'hover:bg-accent/50',
            collapsed
              ? 'flex h-10 w-10 items-center justify-center p-0'
              : 'flex w-full flex-col items-start gap-0.5 px-2.5 py-2',
            selectedSession === session.sessionId && 'bg-accent text-accent-foreground',
          )}
        >
          <div className={cn('flex w-full items-center', collapsed ? 'justify-center' : 'justify-between gap-2')}>
            <div className={cn('flex min-w-0 items-center gap-2', !collapsed && 'flex-1')}>
              <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-primary/10 text-xs font-semibold text-primary">
                {getSessionInitial(session)}
              </span>
              {false && session.isGroupChat !== undefined && (
                <Badge variant="outline" className="h-4 shrink-0 px-1 text-[10px]">
                  {session.isGroupChat ? '群' : '私'}
                </Badge>
              )}
              {!collapsed && <span className="min-w-0 flex-1 truncate font-medium" title={session.sessionName}>
                {session.sessionName}
              </span>}
            </div>
            {!collapsed && <Badge variant="secondary" className="h-4 shrink-0 px-1 text-[10px]">
              {session.eventCount}
            </Badge>}
          </div>
          {!collapsed && <span className="text-xs text-muted-foreground">
            {formatRelativeTime(session.lastActivity)}
          </span>}
        </button>
      ))}
    </div>
  )
}

// ─── 单条时间线事件渲染 ──────────────────────────────────────

function MessageIngestedCard({ data }: { data: MessageIngestedEvent }) {
  return (
    <div className="flex items-start gap-3">
      <div className="mt-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-blue-500/15 text-blue-500">
        <MessageSquare className="h-3.5 w-3.5" />
      </div>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 mb-1">
          <span className="font-medium text-sm">{data.speaker_name}</span>
          <span className="text-xs text-muted-foreground">{formatTimestamp(data.timestamp)}</span>
        </div>
        <p className="text-sm text-foreground/80 whitespace-pre-wrap wrap-break-word leading-relaxed">
          {data.content || '[空消息]'}
        </p>
      </div>
    </div>
  )
}

function CycleStartCard({ data }: { data: CycleStartEvent }) {
  return (
    <div className="flex items-center gap-3">
      <div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-violet-500/15 text-violet-500">
        <Zap className="h-3.5 w-3.5" />
      </div>
      <div className="flex items-center gap-2 flex-wrap">
        <span className="text-sm font-medium">推理循环 #{data.cycle_id}</span>
        <Badge variant="outline" className="text-[10px]">
          回合 {data.round_index + 1}/{data.max_rounds}
        </Badge>
        <Badge variant="secondary" className="text-[10px]">
          上下文 {data.history_count} 条
        </Badge>
      </div>
    </div>
  )
}

function TimingGateCard({ data }: { data: TimingGateResultEvent }) {
  const actionConfig: Record<string, { label: string; variant: 'default' | 'secondary' | 'destructive'; icon: typeof ArrowRight }> = {
    continue: { label: '继续执行', variant: 'default', icon: ArrowRight },
    wait: { label: '等待', variant: 'secondary', icon: PauseCircle },
    no_reply: { label: '不回复', variant: 'destructive', icon: XCircle },
  }
  const config = actionConfig[data.action] ?? actionConfig.continue
  const Icon = config.icon

  return (
    <div className="flex items-start gap-3">
      <div className="mt-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-amber-500/15 text-amber-500">
        <Timer className="h-3.5 w-3.5" />
      </div>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 mb-1 flex-wrap">
          <span className="text-sm font-medium">反应</span>
          <Badge variant="outline" className="text-[10px]">react</Badge>
          <Badge variant={config.variant} className="text-[10px] gap-0.5">
            <Icon className="h-2.5 w-2.5" />
            {config.label}
          </Badge>
          <span className="text-xs text-muted-foreground">{formatMs(data.duration_ms)}</span>
        </div>
        {data.content && (
          <CollapsibleText text={data.content} maxLines={3} />
        )}
      </div>
    </div>
  )
}

function ToolCallBadges({ toolCalls }: { toolCalls: MaisakaToolCall[] }) {
  if (toolCalls.length <= 0) {
    return null
  }

  return (
    <div className="mt-2 flex flex-wrap gap-1.5">
      {toolCalls.map((tc: MaisakaToolCall, idx: number) => (
        <Badge key={`${tc.id || tc.name}-${idx}`} variant="secondary" className="text-[10px] gap-1">
          <Wrench className="h-2.5 w-2.5" />
          {tc.name}
        </Badge>
      ))}
    </div>
  )
}

function openPromptHtml(uri: string) {
  const normalized = uri.trim()
  if (!normalized) return
  window.open(normalized, '_blank', 'noopener,noreferrer')
}

function PlannerResponseCard({ data }: { data: PlannerResponseEvent }) {
  return (
    <div className="flex items-start gap-3">
      <div className="mt-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-emerald-500/15 text-emerald-500">
        <Brain className="h-3.5 w-3.5" />
      </div>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 mb-1 flex-wrap">
          <span className="text-sm font-medium">规划器思考</span>
          <span className="text-xs text-muted-foreground">{formatMs(data.duration_ms)}</span>
          <Badge variant="outline" className="text-[10px]">
            {data.prompt_tokens}+{data.completion_tokens} tokens
          </Badge>
        </div>
        {data.content && (
          <CollapsibleText text={data.content} maxLines={6} />
        )}
        <ToolCallBadges toolCalls={data.tool_calls} />
      </div>
    </div>
  )
}

function PlannerFinalizedCard({ data }: { data: PlannerFinalizedEvent }) {
  const planner = data.planner
  const promptHtmlUri = planner?.prompt_html_uri?.trim() ?? ''

  return (
    <Card className="border-l-4 border-l-emerald-500/60">
      <CardHeader className="py-3 px-4 space-y-3">
        <div className="flex items-center gap-2 flex-wrap">
          <Brain className="h-4 w-4 text-emerald-500" />
          <CardTitle className="text-sm font-medium">主循环 planner</CardTitle>
          {promptHtmlUri && (
            <Button
              variant="ghost"
              size="sm"
              className="h-6 px-2 text-[10px]"
              onClick={() => openPromptHtml(promptHtmlUri)}
              title="打开 planner HTML 记录"
            >
              <ExternalLink className="mr-1 h-3 w-3" />
              HTML
            </Button>
          )}
          <Badge variant="outline" className="text-xs font-normal ml-auto">
            {formatMs(planner?.duration_ms ?? 0)}
          </Badge>
          {data.request && (
            <Badge variant="secondary" className="text-[10px]">
              上下文 {data.request.selected_history_count} 条 / 可用工具 {data.request.tool_count}
            </Badge>
          )}
          {planner && (planner.prompt_tokens > 0 || planner.completion_tokens > 0) && (
            <Badge variant="outline" className="text-[10px]">
              {planner.prompt_tokens}+{planner.completion_tokens} tokens
            </Badge>
          )}
        </div>

        {planner?.content ? (
          <CollapsibleText text={planner.content} maxLines={6} className="text-foreground/90" />
        ) : (
          <p className="text-sm text-muted-foreground">planner 本轮没有文本内容</p>
        )}

      </CardHeader>
    </Card>
  )
}

function PlannerToolCallsBlock({ data }: { data: PlannerFinalizedEvent }) {
  const toolCalls = data.planner?.tool_calls ?? []
  const tools = data.tools ?? []
  const displayTools = tools.length > 0
    ? tools
    : toolCalls.map((toolCall) => ({
        tool_call_id: toolCall.id,
        tool_name: toolCall.name,
        tool_args: toolCall.arguments ?? {},
        success: true,
        duration_ms: 0,
        summary: '',
      }))

  if (displayTools.length <= 0) {
    return null
  }

  return (
    <Card className="border-l-4 border-l-teal-500/60">
      <CardHeader className="py-3 px-4 space-y-2">
        <div className="flex items-center gap-2">
          <Wrench className="h-4 w-4 text-teal-500" />
          <CardTitle className="text-sm font-medium">Planner 工具调用</CardTitle>
          <Badge variant="secondary" className="ml-auto text-[10px]">
            {displayTools.length} 个
          </Badge>
        </div>
        <div className="space-y-2">
          {displayTools.map((tool, idx) => (
            <div
              key={`${tool.tool_call_id || tool.tool_name}-${idx}`}
              className="rounded-md border bg-muted/40 px-2.5 py-2 text-xs"
            >
              <div className="flex items-center gap-2">
                <span className="font-mono font-medium">{tool.tool_name || 'unknown'}</span>
                {tool.success
                  ? <CheckCircle2 className="h-3.5 w-3.5 text-teal-500" />
                  : <XCircle className="h-3.5 w-3.5 text-red-500" />
                }
                {tool.duration_ms > 0 && (
                  <span className="text-muted-foreground">{formatMs(tool.duration_ms)}</span>
                )}
              </div>
              {Object.keys(tool.tool_args ?? {}).length > 0 && (
                <pre className="mt-1 whitespace-pre-wrap break-all rounded bg-background/70 px-2 py-1 text-[11px] text-muted-foreground">
                  {JSON.stringify(tool.tool_args, null, 2)}
                </pre>
              )}
              {tool.summary && (
                <p className="mt-1 text-muted-foreground whitespace-pre-wrap break-words">{tool.summary}</p>
              )}
            </div>
          ))}
        </div>
      </CardHeader>
    </Card>
  )
}

function ToolExecutionCard({ data }: { data: ToolExecutionEvent }) {
  return (
    <div className="flex items-start gap-3">
      <div className={cn(
        'mt-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-full',
        data.success
          ? 'bg-teal-500/15 text-teal-500'
          : 'bg-red-500/15 text-red-500',
      )}>
        <Wrench className="h-3.5 w-3.5" />
      </div>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 mb-1 flex-wrap">
          <span className="text-sm font-medium font-mono">{data.tool_name}</span>
          {data.success
            ? <CheckCircle2 className="h-3.5 w-3.5 text-teal-500" />
            : <XCircle className="h-3.5 w-3.5 text-red-500" />
          }
          <span className="text-xs text-muted-foreground">{formatMs(data.duration_ms)}</span>
        </div>
        {Object.keys(data.tool_args).length > 0 && (
          <div className="text-xs text-muted-foreground font-mono bg-muted/50 rounded px-2 py-1 mb-1 whitespace-pre-wrap break-all">
            {JSON.stringify(data.tool_args, null, 2)}
          </div>
        )}
        {data.result_summary && (
          <CollapsibleText text={data.result_summary} maxLines={3} className="text-muted-foreground" />
        )}
      </div>
    </div>
  )
}

function CycleEndCard({ data }: { data: CycleEndEvent }) {
  const totalTime = Object.values(data.time_records).reduce((a, b) => a + b, 0)
  return (
    <div className="flex items-center gap-3">
      <div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-slate-500/15 text-slate-500">
        <CircleDot className="h-3.5 w-3.5" />
      </div>
      <div className="flex items-center gap-2 flex-wrap">
        <span className="text-sm text-muted-foreground">循环结束</span>
        <Badge variant="outline" className="text-[10px]">
          总耗时 {formatMs(totalTime * 1000)}
        </Badge>
        {Object.entries(data.time_records).map(([name, duration]) => (
          <span key={name} className="text-[10px] text-muted-foreground">
            {name}: {formatMs(duration * 1000)}
          </span>
        ))}
        <Badge
          variant={data.agent_state === 'running' ? 'default' : 'secondary'}
          className="text-[10px]"
        >
          {data.agent_state}
        </Badge>
      </div>
    </div>
  )
}

// ─── 可折叠文本组件 ────────────────────────────────────────────

function CollapsibleText({
  text,
  maxLines = 4,
  className,
}: {
  text: string
  maxLines?: number
  className?: string
}) {
  const [expanded, setExpanded] = useState(false)
  const lines = text.split('\n')
  const needsCollapse = lines.length > maxLines

  if (!needsCollapse || expanded) {
    return (
      <div className="relative">
        <p className={cn(
          'text-sm whitespace-pre-wrap wrap-break-word leading-relaxed',
          className,
        )}>
          {text}
        </p>
        {needsCollapse && (
          <button
            onClick={() => setExpanded(false)}
            className="text-xs text-primary hover:underline mt-1 flex items-center gap-0.5"
          >
            <ChevronDown className="h-3 w-3" /> 收起
          </button>
        )}
      </div>
    )
  }

  return (
    <div>
      <p className={cn(
        'text-sm whitespace-pre-wrap wrap-break-word leading-relaxed',
        className,
      )}>
        {lines.slice(0, maxLines).join('\n')}
      </p>
      <button
        onClick={() => setExpanded(true)}
        className="text-xs text-primary hover:underline mt-1 flex items-center gap-0.5"
      >
        <ChevronRight className="h-3 w-3" /> 展开全部 ({lines.length} 行)
      </button>
    </div>
  )
}

// ─── 回复器响应卡片 ──────────────────────────────────────────

function ReplierResponseCard({ data }: { data: ReplierResponseEvent }) {
  return (
    <Card className="border-l-4 border-l-purple-500/60">
      <CardHeader className="py-2.5 px-4 space-y-2">
        <div className="flex items-center gap-2">
          <Bot className="h-4 w-4 text-purple-500" />
          <CardTitle className="text-sm font-medium">回复器响应</CardTitle>
          <Badge variant="outline" className="text-xs font-normal ml-auto">
            {formatMs(data.duration_ms)}
          </Badge>
          {data.success ? (
            <Badge variant="secondary" className="text-xs gap-1">
              <CheckCircle2 className="h-3 w-3" /> 成功
            </Badge>
          ) : (
            <Badge variant="destructive" className="text-xs gap-1">
              <XCircle className="h-3 w-3" /> 失败
            </Badge>
          )}
          <span className="text-xs text-muted-foreground">{formatTimestamp(data.timestamp)}</span>
        </div>
        {data.content && (
          <CollapsibleText text={data.content} maxLines={6} className="text-foreground/90" />
        )}
        {data.reasoning && (
          <details className="mt-1">
            <summary className="text-xs text-muted-foreground cursor-pointer hover:text-foreground">
              思考过程
            </summary>
            <CollapsibleText text={data.reasoning} maxLines={8} className="mt-1 text-muted-foreground" />
          </details>
        )}
        {(data.prompt_tokens > 0 || data.completion_tokens > 0) && (
          <div className="flex gap-3 text-xs text-muted-foreground mt-1">
            {data.model_name && <span>模型: {data.model_name}</span>}
            <span>输入: {data.prompt_tokens}</span>
            <span>输出: {data.completion_tokens}</span>
            <span>总计: {data.total_tokens}</span>
          </div>
        )}
      </CardHeader>
    </Card>
  )
}

// ─── 时间线入口渲染器 ──────────────────────────────────────────

function TimelineEventRenderer({
  entry,
  showCycleMarkers,
}: {
  entry: TimelineEntry
  showCycleMarkers: boolean
}) {
  switch (entry.type) {
    case 'message.ingested':
      return <MessageIngestedCard data={entry.data as MessageIngestedEvent} />
    case 'cycle.start':
      if (!showCycleMarkers) return null
      return <CycleStartCard data={entry.data as CycleStartEvent} />
    case 'timing_gate.result':
      return <TimingGateCard data={entry.data as TimingGateResultEvent} />
    case 'planner.response':
      return <PlannerResponseCard data={entry.data as PlannerResponseEvent} />
    case 'planner.finalized':
      return (
        <div className="space-y-2">
          <PlannerFinalizedCard data={entry.data as PlannerFinalizedEvent} />
          <PlannerToolCallsBlock data={entry.data as PlannerFinalizedEvent} />
        </div>
      )
    case 'tool.execution':
      return <ToolExecutionCard data={entry.data as ToolExecutionEvent} />
    case 'cycle.end':
      return <CycleEndCard data={entry.data as CycleEndEvent} />
    case 'replier.response':
      return <ReplierResponseCard data={entry.data as ReplierResponseEvent} />
    // planner.request, replier.request 和 session.start 通常不需要在 timeline 中主要展示
    default:
      return null
  }
}

// ─── 主组件 ─────────────────────────────────────────────────

export function MaisakaMonitor() {
  const {
    timeline,
    sessions,
    selectedSession,
    setSelectedSession,
    connected,
    clearTimeline,
  } = useMaisakaMonitor()

  const scrollRef = useRef<HTMLDivElement>(null)
  const [autoScroll, setAutoScroll] = useState(true)
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
    const saved = localStorage.getItem('maisaka-monitor-sidebar-collapsed')
    return saved !== 'false'
  })
  const [showCycleMarkers, setShowCycleMarkers] = useState(() => {
    const saved = localStorage.getItem('maisaka-monitor-show-cycle-markers')
    return saved === 'true'
  })

  useEffect(() => {
    localStorage.setItem('maisaka-monitor-sidebar-collapsed', String(sidebarCollapsed))
  }, [sidebarCollapsed])

  useEffect(() => {
    localStorage.setItem('maisaka-monitor-show-cycle-markers', String(showCycleMarkers))
  }, [showCycleMarkers])

  // 自动滚动到底部
  useEffect(() => {
    if (autoScroll && scrollRef.current) {
      const viewport = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]')
      if (viewport) {
        viewport.scrollTop = viewport.scrollHeight
      }
    }
  }, [timeline, autoScroll])

  const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
    const target = e.currentTarget.querySelector('[data-radix-scroll-area-viewport]')
    if (!target) return
    const { scrollTop, scrollHeight, clientHeight } = target as HTMLElement
    setAutoScroll(scrollHeight - scrollTop - clientHeight < 80)
  }, [])

  // 统计当前会话的各事件类型计数
  const stats = {
    messages: timeline.filter((e) => e.type === 'message.ingested').length,
    cycles: timeline.filter((e) => e.type === 'cycle.start').length,
    toolCalls: timeline.reduce((count, entry) => {
      if (entry.type === 'tool.execution') {
        return count + 1
      }
      if (entry.type === 'planner.finalized') {
        return count + ((entry.data as PlannerFinalizedEvent).tools?.length ?? 0)
      }
      return count
    }, 0),
  }

  return (
    <div className="flex h-[calc(100vh-180px)] gap-4">
      {/* 会话侧边栏 */}
      <Card className={cn(
        'shrink-0 flex flex-col transition-[width] duration-200',
        sidebarCollapsed ? 'w-16' : 'w-52',
      )}>
        <CardHeader className={cn('py-3 space-y-0', sidebarCollapsed ? 'px-2' : 'px-3')}>
          <CardTitle className={cn(
            'text-sm font-medium flex items-center gap-2',
            sidebarCollapsed && 'justify-center text-[0px]',
          )}>
            {!sidebarCollapsed && <Activity className="h-4 w-4" />}
            聊天流
            {connected && (
              <span className={cn('flex h-2 w-2 rounded-full bg-emerald-500', !sidebarCollapsed && 'ml-auto')} />
            )}
            <Button
              variant="ghost"
              size="icon"
              className="h-6 w-6 shrink-0"
              onClick={() => setSidebarCollapsed((value) => !value)}
              title={sidebarCollapsed ? '展开侧边栏' : '折叠侧边栏'}
            >
              {sidebarCollapsed ? <ChevronRight className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
            </Button>
          </CardTitle>
        </CardHeader>
        <Separator />
        <ScrollArea className="flex-1">
          <SessionSidebar
            sessions={sessions}
            selectedSession={selectedSession}
            onSelect={setSelectedSession}
            collapsed={sidebarCollapsed}
          />
        </ScrollArea>
      </Card>

      {/* 主时间线区域 */}
      <div className="flex-1 flex flex-col min-w-0">
        {/* 顶部统计栏 */}
        <div className="flex items-center gap-3 mb-3 flex-wrap">
          <div className="flex items-center gap-4 text-sm">
            <div className="flex items-center gap-1.5 text-muted-foreground">
              <MessageSquare className="h-3.5 w-3.5" />
              <span>{stats.messages} 消息</span>
            </div>
            <div className="flex items-center gap-1.5 text-muted-foreground">
              <Brain className="h-3.5 w-3.5" />
              <span>{stats.cycles} 循环</span>
            </div>
            <div className="flex items-center gap-1.5 text-muted-foreground">
              <Wrench className="h-3.5 w-3.5" />
              <span>{stats.toolCalls} 工具调用</span>
            </div>
          </div>
          <div className="ml-auto flex items-center gap-2">
            <Button
              variant={showCycleMarkers ? 'secondary' : 'ghost'}
              size="sm"
              className="h-7 text-xs"
              onClick={() => setShowCycleMarkers((value) => !value)}
              title={showCycleMarkers ? '隐藏推理循环标记' : '显示推理循环标记'}
            >
              <CircleDot className={cn('h-3.5 w-3.5 mr-1', showCycleMarkers && 'text-primary')} />
              循环标记
            </Button>
            <Button
              variant="ghost"
              size="sm"
              className="h-7 text-xs"
              onClick={() => setAutoScroll(!autoScroll)}
            >
              <Gauge className={cn('h-3.5 w-3.5 mr-1', autoScroll && 'text-primary')} />
              {autoScroll ? '跟踪中' : '已暂停'}
            </Button>
            <Button
              variant="ghost"
              size="sm"
              className="h-7 text-xs"
              onClick={clearTimeline}
            >
              <Eraser className="h-3.5 w-3.5 mr-1" />
              清空
            </Button>
          </div>
        </div>

        {/* 时间线 */}
        <Card className="flex-1 overflow-hidden">
          <ScrollArea
            className="h-full"
            ref={scrollRef}
            onScrollCapture={handleScroll}
          >
            <div className="p-4 space-y-3">
              {timeline.length === 0 ? (
                <div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-3">
                  <Clock className="h-10 w-10 opacity-30" />
                  <p className="text-sm">等待 MaiSaka 推理事件…</p>
                  <p className="text-xs opacity-60">
                    当 MaiSaka 处理新消息时，推理过程会实时展示在这里
                  </p>
                </div>
              ) : (
                timeline.map((entry) => {
                  const rendered = <TimelineEventRenderer entry={entry} showCycleMarkers={showCycleMarkers} />
                  if (!rendered) return null
                  return (
                    <div
                      key={entry.id}
                      className="animate-in fade-in-0 slide-in-from-bottom-2 duration-300"
                    >
                      {rendered}
                      {entry.type === 'cycle.end' && (
                        <Separator className="mt-3" />
                      )}
                    </div>
                  )
                })
              )}
            </div>
          </ScrollArea>
        </Card>
      </div>
    </div>
  )
}
