<template>
  <div class="message-list-wrap">
    <n-scrollbar ref="scrollbarRef" class="message-list">
      <div
        v-for="msg in messages"
        :key="msg.id"
        class="message-row"
        :class="msg.role.toLowerCase()"
      >
        <div class="avatar">{{ msg.role === 'USER' ? '我' : 'AI' }}</div>
        <div class="bubble">
          <!-- ============== USER 消息 ============== -->
          <template v-if="msg.role === 'USER'">
            <!-- 普通态：显示文本 -->
            <div v-if="!editing || editing.messageId !== msg.id" class="user-text">{{ msg.content }}</div>
            <!-- 编辑态：textarea + 保存/取消 -->
            <div v-else class="edit-area">
              <n-input
                v-model:value="editing.text"
                type="textarea"
                :autosize="{ minRows: 2, maxRows: 8 }"
                :maxlength="20000"
              />
              <div class="edit-actions">
                <n-button size="tiny" type="primary" @click="saveEdit">保存并重发</n-button>
                <n-button size="tiny" quaternary @click="cancelEdit">取消</n-button>
              </div>
            </div>
            <!-- USER 操作：编辑 + 复制（非编辑态时显示，图标形式） -->
            <div
              v-if="(!editing || editing.messageId !== msg.id) && isStableId(msg.id)"
              class="message-actions user-actions"
            >
              <button class="msg-action-btn icon-only" @click="startEdit(msg)" title="编辑后重发">
                <NIcon><CreateOutline /></NIcon>
              </button>
              <button class="msg-action-btn icon-only" @click="copyMessage(msg)" title="复制">
                <NIcon><CopyOutline /></NIcon>
              </button>
            </div>
          </template>

          <!-- ============== ASSISTANT 消息 ============== -->
          <template v-else>
            <!-- 与 WorkflowEditor「运行结果」面板顺序一致：
                 实时状态 → 思考过程 → 待办清单 → 工具调用 → 执行日志
                 全部包在 .thinking-blocks 外边框内，与最终回答左右对齐 -->
            <div v-if="hasAnyRuntime(msg) || (msg.logs && msg.logs.length)" class="thinking-blocks">
              <!-- ① 实时状态（仅 RUNNING 时显示；完成后整个隐藏） -->
              <ThinkingLive
                v-if="msg.status === 'RUNNING' && buildAssistantNode(msg).currentStatus"
                :status="buildAssistantNode(msg).currentStatus"
              />

              <!-- ② 流式思考过程（累加型，节点完成后仍保留为历史） -->
              <div v-if="buildAssistantNode(msg).streamingThinking" class="chat-thinking">
                <div class="chat-thinking-header">
                  <span class="chat-thinking-tag">思考过程</span>
                  <span v-if="msg.status === 'RUNNING'" class="chat-thinking-live">实时输出中…</span>
                  <span v-else class="chat-thinking-static">（{{ buildAssistantNode(msg).streamingThinking.length }} 字）</span>
                </div>
                <pre class="chat-thinking-body">{{ trimLeadingBlankLines(buildAssistantNode(msg).streamingThinking) }}</pre>
              </div>

              <!-- ③ 待办清单 + ④ 工具调用（AgentThinkingPanel 内部顺序与 WorkflowEditor 一致）
                   Askfor 由外层全局唯一渲染，此处传 null 避免重复 -->
              <AgentThinkingPanel
                :node="buildAssistantNode(msg)"
                :pending-ask="null"
                :reconnecting="false"
              />

              <!-- ⑤ 执行日志（与 .nrc-logs 同款 layout：time + tag + tool + msg + detail）
                   过滤掉 thinking（已在 streamingThinking 单独展示），避免条数冗余 -->
              <div v-if="visibleLogs(msg).length" class="chat-logs"
                   :class="{ live: msg.status === 'RUNNING' }">
                <div class="chat-logs-header" @click="toggleLogs(msg.id)" style="cursor: pointer">
                  <span class="chat-logs-tag">执行日志</span>
                  <span class="chat-logs-count">（{{ visibleLogs(msg).length }}）</span>
                  <span class="chat-logs-expand"
                        :class="{ expanded: expandedLogs[msg.id] === true }">▶</span>
                </div>
                <div v-show="expandedLogs[msg.id] === true" class="chat-logs-body">
                  <div v-for="(log, idx) in visibleLogs(msg)" :key="idx"
                       class="chat-log" :class="'log-type-' + logTypeSuffix(log.kind)">
                    <span class="chat-log-time">{{ formatTimestamp(log.createdAt || log.at) }}</span>
                    <span class="chat-log-tag">{{ logTypeLabel(log.kind) }}</span>
                    <span v-if="log.toolName" class="chat-log-tool">{{ log.toolName }}</span>
                    <span class="chat-log-msg">{{ log.content }}</span>
                    <div v-if="formatMetaJson(log.metaJson)" class="chat-log-detail">{{ formatMetaJson(log.metaJson) }}</div>
                  </div>
                </div>
              </div>
            </div>

            <!-- 最终结果（与 .thinking-blocks 同宽左右对齐；markdown 富文本渲染） -->
            <div v-if="msg.content" class="assistant-md" v-html="renderMarkdown(msg.content)"></div>
            <div v-if="msg.status === 'RUNNING' && !msg.content && !hasAnyRuntime(msg)" class="running-hint">
              <n-spin :size="14" /> 思考中…
            </div>
            <div v-if="msg.status === 'FAILED'" class="error-text">失败：{{ msg.error }}</div>

            <!-- ASSISTANT 操作：复制 + (条件)版本切换 + 重新生成（图标形式）。
                 versions 自动预加载（见 watch）：versions.length > 1 时显示 ‹ N/M › 切换器。 -->
            <div v-if="msg.status !== 'RUNNING' && isStableId(msg.id)" class="message-actions assistant-actions">
              <button class="msg-action-btn icon-only" @click="copyMessage(msg)" title="复制">
                <NIcon><CopyOutline /></NIcon>
              </button>
              <template v-if="versionsCache[msg.id] && versionsCache[msg.id].versions.length > 1">
                <button class="msg-action-btn icon-arrow"
                        :disabled="versionsCache[msg.id].currentIndex <= 0"
                        @click="switchVersion(msg.id, -1)"
                        title="上一版本">‹</button>
                <span class="version-info">
                  {{ versionsCache[msg.id].currentIndex + 1 }}/{{ versionsCache[msg.id].versions.length }}
                </span>
                <button class="msg-action-btn icon-arrow"
                        :disabled="versionsCache[msg.id].currentIndex >= versionsCache[msg.id].versions.length - 1"
                        @click="switchVersion(msg.id, 1)"
                        title="下一版本">›</button>
              </template>
              <button class="msg-action-btn icon-only" @click="$emit('regenerate', msg.id)" title="重新生成回复">
                <NIcon><RefreshOutline /></NIcon>
              </button>
            </div>
          </template>
        </div>
      </div>
    </n-scrollbar>

    <!-- Clarify 答题面板（全局唯一，与 workflow editor 一致：仅此处渲染） -->
    <AskforPanel
      v-if="pendingAsk"
      :pending="pendingAsk"
      @submit="onAnswer"
    />
  </div>
</template>

<script setup>
import { watch, nextTick, ref } from 'vue'
import { NScrollbar, NSpin, NInput, NButton, NIcon } from 'naive-ui'
import { CreateOutline, CopyOutline, RefreshOutline } from '@vicons/ionicons5'
import AskforPanel from '../agent-thinking/AskforPanel.vue'
import AgentThinkingPanel from '../agent-thinking/AgentThinkingPanel.vue'
import ThinkingLive from '../agent-thinking/ThinkingLive.vue'
import { stripHermesThinkingDecoration, trimLeadingBlankLines } from '../../utils/hermesThinking'
import { renderMarkdown } from '../../utils/markdown'
import { logTypeLabel, logTypeSuffix } from '../../utils/logLabels'
import { listMessageVersions } from '../../api/chat'
// 公共日志类型上色（10 类型，三处共用：ChatMessageList / WorkflowEditor / RunHistory）
import '../../styles/log-types.css'

const props = defineProps({
  messages: { type: Array, default: () => [] },
  pendingAsk: { type: Object, default: null }
})

const emit = defineEmits(['answer', 'regenerate', 'edit', 'switch-version'])

const scrollbarRef = ref(null)

// 执行日志面板的展开状态：按 msg.id 索引；默认收起（即使 RUNNING 中也不自动展开），
// 用户可手动点击 header 切换
const expandedLogs = ref({})

function toggleLogs(msgId) {
  // 用 $set 语义：直接给 reactive ref 重新赋一个新对象，确保 Vue 3 能追踪
  expandedLogs.value = { ...expandedLogs.value, [msgId]: !expandedLogs.value[msgId] }
}

/* ============== 编辑用户消息（inline） ============== */
// editing: null 或 { messageId, text }
const editing = ref(null)

function isStableId(id) {
  // tmp-user-*/tmp-assistant-* 前缀为乐观插入的占位消息，没有后端 id，不支持操作
  return typeof id === 'number' || /^\d+$/.test(String(id))
}

function startEdit(msg) {
  editing.value = { messageId: msg.id, text: msg.content || '' }
}

function cancelEdit() {
  editing.value = null
}

function saveEdit() {
  if (!editing.value) return
  const { messageId, text } = editing.value
  if (!text || !text.trim()) return
  emit('edit', { messageId, content: text })
  editing.value = null
}

/* ============== 版本切换（懒加载 → 进入稳定态自动预加载） ============== */
// versionsCache: { [msgId]: { versions: [], currentIndex: number } }
// 进入稳定态（SUCCESS / FAILED）的 ASSISTANT 消息自动预加载 versions，
// versions.length > 1 时显示 ‹ N/M › 切换器；否则完全不渲染版本相关 UI。
const versionsCache = ref({})

async function loadVersions(msgId) {
  try {
    const res = await listMessageVersions(msgId)
    if (res && res.data) {
      versionsCache.value = {
        ...versionsCache.value,
        [msgId]: {
          versions: res.data.versions || [],
          currentIndex: res.data.currentIndex ?? -1
        }
      }
    }
  } catch (e) {
    console.warn('[ChatMessageList] loadVersions 失败', e?.message || e)
  }
}

/** 切换版本：dir = -1 上一版本，dir = +1 下一版本 */
function switchVersion(msgId, dir) {
  const cache = versionsCache.value[msgId]
  if (!cache) return
  const targetIdx = cache.currentIndex + dir
  if (targetIdx < 0 || targetIdx >= cache.versions.length) return
  const targetId = cache.versions[targetIdx].id
  // 更新本地 currentIndex 缓存，让 UI 立即响应
  versionsCache.value = {
    ...versionsCache.value,
    [msgId]: { ...cache, currentIndex: targetIdx }
  }
  emit('switch-version', targetId)
}

/* ============== 复制消息内容到剪贴板 ============== */
async function copyMessage(msg) {
  const text = msg.content || ''
  if (!text) return
  try {
    await navigator.clipboard.writeText(text)
  } catch {
    // 兜底：选区 + execCommand
    const ta = document.createElement('textarea')
    ta.value = text
    ta.style.position = 'fixed'
    ta.style.opacity = '0'
    document.body.appendChild(ta)
    ta.select()
    try { document.execCommand('copy') } catch { /* ignore */ }
    document.body.removeChild(ta)
  }
}

/* ============== 进入稳定态自动预加载版本 ==============
 * 监听 messages 变化，对所有「ASSISTANT + 稳定态 + 持久化 id + 未加载过」的消息
 * 自动调 loadVersions，避免用户手动点「版本」入口；versions.length > 1 时才显示切换器。
 *
 * <p>同时清理 versionsCache 里已不在 messages 中的 stale id——版本切换后 reloadCurrentSession
 * 会替换整个 messages 数组（只保留 root→target 主路径），如果不清理旧 cached id，
 * 后续 switchVersion 仍可能基于过期 currentIndex 跳到无效消息，造成"切几次就消失"的连锁错乱。</p>
 */
watch(
  () => props.messages.map(m => `${m.id}:${m.role}:${m.status}`).join(','),
  () => {
    // 1. 清理 stale cache：移除已不在 messages 数组中的 cached id
    const visibleIds = new Set(props.messages.map(m => m.id))
    const staleKeys = Object.keys(versionsCache.value)
      .filter(k => !visibleIds.has(Number(k)))
    if (staleKeys.length > 0) {
      const next = { ...versionsCache.value }
      for (const k of staleKeys) delete next[k]
      versionsCache.value = next
    }
    // 2. 对未加载版本的稳定态 ASSISTANT 消息懒加载 versions
    for (const m of props.messages) {
      if (m.role !== 'ASSISTANT') continue
      if (m.status === 'RUNNING') continue
      if (!isStableId(m.id)) continue
      if (versionsCache.value[m.id]) continue
      loadVersions(m.id)
    }
  },
  { immediate: true, flush: 'post' }
)

/**
 * 返回要在"执行日志"面板展示的 logs 子集。
 *
 * <p>chat 端 SSE 把所有 kind（thinking/status/step/tool_progress/tool_call/tool_result/todo_update/
 * clarify_request）都存为 log，导致条数极多。其中：
 * <ul>
 *   <li>{@code thinking} → 已在 streamingThinking 累加展示，过滤</li>
 *   <li>{@code clarify_request} → 已在全局 AskforPanel 展示，过滤</li>
 *   <li>{@code status/step/tool_progress/tool_call/tool_result/todo_update} → 与"实时状态/工具调用/待办"
 *       面板有时间线重复，但作为"执行日志"全量时间线仍有价值，保留</li>
 * </ul>
 */
function visibleLogs(msg) {
  const logs = msg.logs || []
  return logs.filter(l => l && l.kind !== 'thinking' && l.kind !== 'clarify_request')
}

/**
 * 时间戳格式化：与 WorkflowEditor / RunHistory 完全一致（zh-CN 小时制）。
 * 优先用后端持久化的 createdAt；流式中尚未持久化的回退到本地事件接收时间 at。
 */
function formatTimestamp(ts) {
  if (!ts) return ''
  const d = new Date(ts)
  return d.toLocaleTimeString('zh-CN', { hour12: false })
}

/**
 * 把 metaJson（JSON 字符串）格式化为美化后的多行 JSON，作为日志 detail 展示。
 * 非字符串 / 解析失败时返回空（不展示）。
 */
function formatMetaJson(metaJson) {
  if (!metaJson) return ''
  if (typeof metaJson !== 'string') {
    try { return JSON.stringify(metaJson, null, 2) } catch { return '' }
  }
  try { return JSON.stringify(JSON.parse(metaJson), null, 2) } catch { return '' }
}

// 新消息或日志变更时滚动到底部
watch(
  () => props.messages.map(m => `${m.id}:${m.logs?.length || 0}`).join(','),
  () => {
    nextTick(() => {
      const inst = scrollbarRef.value
      if (inst && inst.scrollTo) {
        inst.scrollTo({ top: 9999999, behavior: 'smooth' })
      }
    })
  }
)

function onAnswer(payload) {
  // AskforPanel emit('submit', value)；转为 {clarifyId, answer}
  emit('answer', {
    clarifyId: props.pendingAsk?.clarifyId,
    answer: payload
  })
}

/**
 * 是否有任何运行时态内容（实时状态/思考过程/工具/TODO）。
 * 用于决定 RUNNING 中是否需要显示"思考中…"占位（没有任何流式内容时才显示）。
 */
function hasAnyRuntime(msg) {
  const logs = msg.logs || []
  if (logs.length === 0) return false
  const node = buildAssistantNode(msg)
  return !!(node.currentStatus || node.streamingThinking || node.currentTool
    || (node.toolHistory && node.toolHistory.length) || (node.todos && node.todos.length))
}

/**
 * 把 assistant 消息的 logs 数组（按 kind 累积的事件流）转换为
 * AgentThinkingPanel / ThinkingLive 期望的"节点对象"。
 *
 * kind 取值与 useWorkflowRunner / HermesBridgeClient 一致：
 *   thinking | status | step | tool_progress | tool_call | tool_result | todo_update | clarify_request
 *
 * 分发规则与 useWorkflowRunner.handleNodeStreamEvent 保持一致：
 *   - thinking → streamingThinking 累加
 *   - status / step / tool_progress → currentStatus 覆盖
 *   - tool_call → 进入 currentTool，旧 currentTool 入 toolHistory
 *   - tool_result → 配对最近的 tool_call（currentTool 或 history[0]）的 result
 *   - todo_update → todos 数组覆盖
 *   - clarify_request → 已由 useGlobalRuns 拦截到 pendingAsk，此处跳过
 */
function buildAssistantNode(msg) {
  const logs = msg.logs || []
  const node = {
    status: msg.status === 'RUNNING' ? 'running'
      : (msg.status || '').toLowerCase(),
    currentStatus: '',
    currentTool: null,
    toolHistory: [],
    todos: [],
    streamingThinking: '',
  }
  let pending = null
  for (const log of logs) {
    const kind = log.kind
    const content = log.content || ''
    const toolName = log.toolName || null
    if (kind === 'thinking') {
      if (content) node.streamingThinking += content
    } else if (kind === 'status') {
      node.currentStatus = stripHermesThinkingDecoration(content)
    } else if (kind === 'step') {
      try {
        const obj = JSON.parse(content)
        const iteration = obj.iteration ?? '?'
        node.currentStatus = `第 ${iteration} 轮 LLM 调用`
      } catch { /* ignore */ }
    } else if (kind === 'tool_progress') {
      try {
        const obj = JSON.parse(content)
        if (obj.event_type === 'tool.started' && obj.name) {
          const tn = obj.name ?? '工具'
          node.currentStatus = `正在调用 ${tn}`
        }
      } catch { /* ignore */ }
    } else if (kind === 'tool_call') {
      if (pending) {
        node.toolHistory.unshift(pending)
      } else if (node.currentTool) {
        node.toolHistory.unshift(node.currentTool)
      }
      node.currentTool = { name: toolName, argsSummary: content, result: null }
      pending = node.currentTool
    } else if (kind === 'tool_result') {
      if (pending) {
        pending.result = content
        node.toolHistory.unshift(pending)
        pending = null
        node.currentTool = null
      } else if (node.currentTool) {
        node.currentTool.result = content
        node.toolHistory.unshift(node.currentTool)
        node.currentTool = null
      }
    } else if (kind === 'todo_update') {
      try {
        const parsed = JSON.parse(content)
        const arr = Array.isArray(parsed) ? parsed : (parsed?.todos || [])
        if (Array.isArray(arr)) node.todos = arr
      } catch { /* ignore */ }
    }
  }
  // 兜底：最后一个 tool_call 没 result
  if (pending) {
    // 已经被赋值给 node.currentTool，保持执行中状态
  }
  // toolHistory 与编辑器一致：unshift 顺序（最新在前）
  return node
}
</script>

<style scoped>
.message-list-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.message-list {
  flex: 1;
  padding: 16px 24px;
}
.message-row {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
  align-items: flex-start;
}
.message-row.user {
  flex-direction: row-reverse;
}
.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  /* v1.2：深色主题，与系统深色风格一致 */
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-secondary, #b0b0b0);
  border: 1px solid rgba(255, 255, 255, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  flex-shrink: 0;
}
.message-row.user .avatar {
  background: rgba(37, 99, 235, 0.18);
  color: #93c5fd;
  border-color: rgba(37, 99, 235, 0.3);
}
.bubble {
  max-width: 75%;
  padding: 10px 14px;
  /* v1.2：深色主题气泡，不再用 naive-ui 的 --n-color（白色） */
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-primary, #e0e0e0);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
}
.message-row.user .bubble {
  background: rgba(37, 99, 235, 0.12);
  border-color: rgba(37, 99, 235, 0.25);
  color: var(--text-primary, #e0e0e0);
}
.user-text {
  font-size: 14px;
  white-space: pre-wrap;
  word-break: break-word;
}
.running-hint {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-tertiary, #888);
  font-size: 12px;
  margin-top: 6px;
}
.error-text {
  color: #f87171;
  font-size: 12px;
  margin-top: 6px;
}

/* v1.2：流式思考过程区块（与 WorkflowEditor .nrc-thinking 视觉对齐） */
.chat-thinking {
  margin: 4px 0 6px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(168, 85, 247, 0.15);
  border-radius: 4px;
  overflow: hidden;
  font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', ui-monospace, monospace;
}
.chat-thinking-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  font-size: 11px;
  background: rgba(168, 85, 247, 0.06);
  border-bottom: 1px solid rgba(168, 85, 247, 0.15);
}
.chat-thinking-tag {
  color: #c084fc;
  font-weight: 600;
}
.chat-thinking-live {
  color: #c084fc;
  font-size: 10px;
}
.chat-thinking-static {
  color: var(--text-tertiary, #888);
  font-size: 10px;
}
.chat-thinking-body {
  margin: 0;
  padding: 8px 10px;
  max-height: 240px;
  overflow-y: auto;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-secondary, #b0b0b0);
  white-space: pre-wrap;
  word-break: break-word;
}

/* 思考类面板统一外框（实时状态/思考过程/待办/工具/执行日志）
   与最终回答 .assistant-md 同宽左右对齐：均紧贴 bubble 内壁 */
.thinking-blocks {
  padding: 6px;
  margin-bottom: 6px;
  border: 1px solid rgba(168, 85, 247, 0.18);
  border-radius: 6px;
  background: rgba(168, 85, 247, 0.03);
}
/* 内部子面板上下间距收紧，避免与外框 double padding */
.thinking-blocks > :deep(*) {
  margin-top: 4px;
}
.thinking-blocks > :deep(*:first-child) {
  margin-top: 0;
}
/* v1.4：清除子组件自带左右外边距，使所有面板同宽对齐
   .thinking-live / .tool-calls-wrap / .todo-panel 原本带 margin: ? 10px
   （为 WorkflowEditor 的 .node-result-card 无 padding 容器准备），
   在 chat 的 .thinking-blocks（已有 6px padding）内会造成双重缩进 */
.thinking-blocks :deep(.thinking-live),
.thinking-blocks :deep(.tool-calls-wrap),
.thinking-blocks :deep(.todo-panel) {
  margin-left: 0;
  margin-right: 0;
}

/* v1.5：执行日志（与 WorkflowEditor / RunHistory .nrc-logs 视觉完全一致）
   margin: 0 → 由 .thinking-blocks > :deep(*) 统一控制间距，与 AgentThinkingPanel 同宽。
   上色规则已迁至 src/styles/log-types.css（10 类型三处共用）。 */
.chat-logs {
  margin: 0;
  border-radius: 6px;
  background: rgba(168, 85, 247, 0.05);
  border: 1px solid rgba(168, 85, 247, 0.2);
  overflow: hidden;
  font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', ui-monospace, monospace;
}
.chat-logs.live {
  border-color: rgba(168, 85, 247, 0.5);
  box-shadow: 0 0 0 1px rgba(168, 85, 247, 0.15);
}
.chat-logs-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  font-size: 11px;
  background: rgba(168, 85, 247, 0.08);
  cursor: pointer;
  user-select: none;
}
.chat-logs-tag {
  color: #c084fc;
  font-weight: 600;
}
.chat-logs-count {
  color: #888;
  font-size: 10px;
}
.chat-logs-expand {
  margin-left: auto;
  font-size: 10px;
  color: var(--text-tertiary, #666);
  transition: transform 0.2s;
  flex-shrink: 0;
}
.chat-logs-expand.expanded {
  transform: rotate(90deg);
}
.chat-logs-body {
  max-height: 360px;
  overflow-y: auto;
}
/* v1.5：单条日志 layout 与 .nrc-log 完全一致（非 flex，time+tag 同行，msg/detail 换行） */
.chat-log {
  padding: 5px 10px;
  border-bottom: 1px solid rgba(168, 85, 247, 0.1);
  font-size: 11px;
  line-height: 1.5;
}
.chat-log:last-child {
  border-bottom: none;
}
.chat-log-time {
  font-family: inherit;
  font-size: 10px;
  color: var(--text-tertiary, #555);
  margin-right: 6px;
}
.chat-log-tag {
  font-size: 10px;
  padding: 1px 4px;
  border-radius: 3px;
  margin-right: 6px;
  font-weight: 600;
}
.chat-log-tool {
  font-size: 10px;
  color: #fbbf24;
  font-weight: 600;
  margin-right: 6px;
}
.chat-log-msg {
  display: block;
  margin-top: 2px;
  color: var(--text-secondary, #ccc);
  word-break: break-all;
  white-space: pre-wrap;
}
.chat-log-detail {
  margin-top: 3px;
  font-family: 'Cascadia Code', 'Fira Code', monospace;
  font-size: 10px;
  color: var(--text-tertiary, #888);
  background: rgba(0, 0, 0, 0.2);
  padding: 4px 6px;
  border-radius: 4px;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 100px;
  overflow-y: auto;
}

/* ============== 消息操作按钮（编辑 / 重新生成 / 版本切换） ============== */
/* 默认隐藏，气泡 hover 时显示，避免静态时视觉嘈杂 */
.message-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  opacity: 0;
  transition: opacity 0.15s;
}
.message-row:hover .message-actions {
  opacity: 1;
}
/* user 行是 row-reverse，bubble 在视觉左侧；actions 左对齐显得更自然 */
.user-actions { justify-content: flex-start; }
.assistant-actions { justify-content: flex-start; }

.msg-action-btn {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--text-secondary, #b0b0b0);
  font-size: 11px;
  border-radius: 3px;
  cursor: pointer;
  transition: all 0.15s;
  font-family: inherit;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.msg-action-btn:hover:not(:disabled) {
  background: rgba(168, 85, 247, 0.12);
  border-color: rgba(168, 85, 247, 0.35);
  color: #c084fc;
}
.msg-action-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
/* 图标按钮：22×22 方形 */
.msg-action-btn.icon-only {
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 14px;
}
/* 箭头按钮 ‹ ›：紧凑型，用于版本切换 */
.msg-action-btn.icon-arrow {
  padding: 0 4px;
  font-size: 14px;
  font-weight: 700;
  min-width: 18px;
}
.version-info {
  font-size: 10px;
  color: var(--text-tertiary, #888);
  font-family: 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
  min-width: 32px;
  text-align: center;
}

/* ============== 编辑模式（USER 消息） ============== */
.edit-area {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.edit-area :deep(.n-input .n-input-wrapper) {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(168, 85, 247, 0.3);
  border-radius: 4px;
}
.edit-area :deep(.n-input__textarea-el) {
  color: var(--text-primary, #e0e0e0);
  font-size: 13px;
}
.edit-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

/* ============== 最终回答 markdown 渲染 ==============
   字号约定：正文 14px；H6 加粗同正文（14px），H5 15px，H4 16px，H3 17px，H2 18px，H1 19px。
   表格/代码/引用/公式 全部适配深色主题。 */
.assistant-md {
  font-size: 14px;
  line-height: 1.7;
  color: var(--text-primary, #e0e0e0);
  word-break: break-word;
  overflow-wrap: anywhere;
}
.assistant-md :deep(*:first-child) { margin-top: 0 !important; }
.assistant-md :deep(*:last-child) { margin-bottom: 0 !important; }
.assistant-md :deep(p) { margin: 6px 0; }
.assistant-md :deep(h1),
.assistant-md :deep(h2),
.assistant-md :deep(h3),
.assistant-md :deep(h4),
.assistant-md :deep(h5),
.assistant-md :deep(h6) {
  margin: 12px 0 6px;
  font-weight: 700;
  line-height: 1.4;
  color: #f1f5f9;
}
.assistant-md :deep(h6) { font-size: 14px; }
.assistant-md :deep(h5) { font-size: 15px; }
.assistant-md :deep(h4) { font-size: 16px; }
.assistant-md :deep(h3) { font-size: 17px; }
.assistant-md :deep(h2) { font-size: 18px; }
.assistant-md :deep(h1) { font-size: 19px; }

/* 列表：与正文对齐，紧凑间距 */
.assistant-md :deep(ul),
.assistant-md :deep(ol) {
  margin: 6px 0;
  padding-left: 22px;
}
.assistant-md :deep(li) { margin: 2px 0; }
.assistant-md :deep(li > input[type="checkbox"]) {
  margin-right: 6px;
  vertical-align: middle;
}

/* 引用块 */
.assistant-md :deep(blockquote) {
  margin: 8px 0;
  padding: 4px 12px;
  border-left: 3px solid rgba(168, 85, 247, 0.5);
  background: rgba(168, 85, 247, 0.06);
  color: var(--text-secondary, #b0b0b0);
}
.assistant-md :deep(blockquote p) { margin: 4px 0; }

/* 行内代码 */
.assistant-md :deep(code) {
  font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.08);
  color: #fbbf24;
}
/* 代码块（pre > code） */
.assistant-md :deep(pre) {
  margin: 8px 0;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.32);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 6px;
  overflow-x: auto;
}
.assistant-md :deep(pre code) {
  background: transparent;
  color: var(--text-primary, #e0e0e0);
  padding: 0;
  font-size: 12.5px;
  line-height: 1.6;
}

/* 链接 */
.assistant-md :deep(a) {
  color: #93c5fd;
  text-decoration: none;
  border-bottom: 1px dashed rgba(147, 197, 253, 0.4);
}
.assistant-md :deep(a:hover) { border-bottom-style: solid; }

/* 分隔线 */
.assistant-md :deep(hr) {
  margin: 12px 0;
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

/* 图片 */
.assistant-md :deep(img) {
  max-width: 100%;
  border-radius: 4px;
}

/* ============== 表格 ============== */
.assistant-md :deep(table) {
  width: 100%;
  margin: 8px 0;
  border-collapse: collapse;
  font-size: 13px;
  display: block;
  overflow-x: auto;
}
.assistant-md :deep(thead) {
  background: rgba(168, 85, 247, 0.12);
}
.assistant-md :deep(th),
.assistant-md :deep(td) {
  padding: 6px 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  text-align: left;
  vertical-align: top;
}
.assistant-md :deep(th) {
  font-weight: 600;
  color: #e2e8f0;
}
.assistant-md :deep(tbody tr:nth-child(2n+1)) {
  background: rgba(255, 255, 255, 0.02);
}
.assistant-md :deep(tbody tr:hover) {
  background: rgba(168, 85, 247, 0.06);
}

/* ============== KaTeX 公式 ==============
   块级公式居中、有上下 margin；行内公式不换行 */
.assistant-md :deep(.katex-display) {
  margin: 10px 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 0;
}
.assistant-md :deep(.katex) {
  font-size: 1.05em;
  color: #e2e8f0;
}
</style>
