<template>
  <!-- 结果展示区(从 ToolCard.vue 抽离,纯渲染,逻辑等价)。
       分支顺序/条件/文案与原 ToolCard.vue 一字不变,仅搬运行内联模板。 -->

  <!-- read_file 结果:全文预览(默认)/ 命中行(search 模式) -->
  <div v-if="tc.name === 'read_file' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-file-content">
    <div class="ai-tool-file-bar">
      <span class="ai-tool-file-icon" v-html="fileIcon"></span>
      <span class="ai-tool-file-path">{{ parsed?.path }}</span>
      <!-- BUG-260625-02: search 模式返回 matches(命中行)非 content,
           旧模板取 lines/returned_lines 显「0 行」+ 空 content 区,用户误以为读取失败。
           search 模式显搜索词 + 命中数(复用 grepHitsN 文案);默认模式显行数/大小 + 展开。 -->
      <span v-if="parsed?.search" class="ai-tool-file-meta">「{{ parsed?.search }}」 {{ $t('aiTool.grepHitsN', { n: typeof parsed?.total === 'number' ? parsed.total : (parsed?.matches?.length || 0) }) }}</span>
      <span v-else class="ai-tool-file-meta">{{ parsed?.has_more
        ? $t('aiTool.linesTruncated', { shown: parsed?.returned_lines ?? 0, total: parsed?.lines ?? 0 })
        : `${parsed?.lines || 0} ${$t('aiTool.lines')}` }} · {{ formatBytes(parsed?.size) }}</span>
      <button v-if="!parsed?.search" class="ai-tool-expand-btn" @click="emit('expand-content', tc.id)">
        {{ isContentExpanded ? $t('aiTool.collapse') : $t('aiTool.expand') }}
      </button>
    </div>
    <!-- search 模式:命中行渲染(复用 grep content 样式;read_file matches 仅 {line,content} 无 file) -->
    <!-- BUG-260626-01:行号 gutter 与内容同一 flex row(IDE 风格),不再 head/content 分两行块级堆叠 -->
    <div v-if="parsed?.search && parsed?.matches?.length" class="ai-tool-grep-matches">
      <div v-for="(m, i) in parsed?.matches" :key="i" class="ai-tool-grep-match">
        <div class="ai-tool-grep-match-main">
          <span v-if="m.line !== null && m.line !== undefined" class="ai-tool-grep-line">{{ m.line }}</span>
          <code class="ai-tool-grep-content">{{ m.content }}</code>
        </div>
      </div>
    </div>
    <!-- 默认模式:全文预览 -->
    <pre v-else class="ai-tool-file-pre" :class="{ 'ai-tool-file-pre--collapsed': !isContentExpanded }"><code>{{ parsed?.content }}</code></pre>
  </div>

  <!-- list_directory 结果:文件树 -->
  <div v-else-if="tc.name === 'list_directory' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-dir-list">
    <div class="ai-tool-file-bar">
      <span class="ai-tool-file-icon ai-tool-file-icon--dir" v-html="dirIcon"></span>
      <span class="ai-tool-file-path">{{ parsed?.path }}</span>
      <span class="ai-tool-file-meta">{{ parsed?.entries?.length || 0 }} {{ $t('aiTool.items') }}</span>
      <span v-if="parsed?.truncated" class="ai-tool-file-meta ai-tool-file-meta--warn">{{ $t('aiTool.dirTruncated', { n: 1000 }) }}</span>
    </div>
    <div class="ai-tool-dir-entries">
      <div
        v-for="(entry, i) in parsed?.entries"
        :key="i"
        class="ai-tool-dir-entry"
        :class="'ai-tool-dir-entry--' + entry.type"
        :style="{ paddingLeft: `${12 + (entry.depth || 0) * 16}px` }"
      >
        <span class="ai-tool-dir-icon" :class="entry.type === 'directory' ? 'ai-tool-dir-icon--folder' : 'ai-tool-dir-icon--file'" v-html="entry.type === 'directory' ? dirIcon : fileIcon"></span>
        <span class="ai-tool-dir-name">{{ entry.name }}</span>
        <span v-if="entry.type === 'file'" class="ai-tool-dir-size">{{ formatBytes(entry.size) }}</span>
      </div>
    </div>
  </div>

  <!-- search_files 结果:匹配文件列表(对齐 list_directory 渲染,修 results 被丢弃致 body 仅显「找到 N 项」) -->
  <div v-else-if="tc.name === 'search_files' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-dir-list">
    <div class="ai-tool-file-bar">
      <span class="ai-tool-file-icon ai-tool-file-icon--dir" v-html="dirIcon"></span>
      <span class="ai-tool-file-path">{{ parsed?.path }}</span>
      <span v-if="parsed?.pattern" class="ai-tool-file-meta">「{{ parsed?.pattern }}」</span>
      <span class="ai-tool-file-meta">{{ $t('aiTool.foundN', { n: typeof parsed?.total === 'number' ? parsed.total : (parsed?.results?.length || 0) }) }}</span>
    </div>
    <div class="ai-tool-dir-entries">
      <div v-for="(entry, i) in parsed?.results" :key="i" class="ai-tool-dir-entry">
        <span class="ai-tool-dir-icon ai-tool-dir-icon--file" v-html="fileIcon"></span>
        <span class="ai-tool-dir-name">{{ entry.path }}</span>
        <span class="ai-tool-dir-size">{{ formatBytes(entry.size) }}</span>
      </div>
    </div>
  </div>

  <!-- grep 结果:跨文件内容搜索(content/files_with_matches/count 三模式,F-260621)
       对齐 search_files 文件栏样式;content 模式按命中行渲染(文件:行:内容),支持 -C 上下文折叠 -->
  <div v-else-if="tc.name === 'grep' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-grep-result">
    <div class="ai-tool-file-bar">
      <span class="ai-tool-file-icon" v-html="searchIcon"></span>
      <span class="ai-tool-file-path">{{ parsed?.path }}</span>
      <span v-if="parsed?.pattern" class="ai-tool-file-meta">「{{ parsed?.pattern }}」</span>
      <span class="ai-tool-file-meta">{{ grepHitLabel }}</span>
      <span v-if="parsed?.truncated" class="ai-tool-file-meta ai-tool-file-meta--warn">{{ $t('aiTool.grepTruncated') }}</span>
    </div>
    <!-- files_with_matches 模式:仅文件列表 -->
    <div v-if="parsed?.output_mode === 'files_with_matches'" class="ai-tool-dir-entries">
      <div v-for="(f, i) in parsed?.files" :key="i" class="ai-tool-dir-entry">
        <span class="ai-tool-dir-icon ai-tool-dir-icon--file" v-html="fileIcon"></span>
        <span class="ai-tool-dir-name">{{ f }}</span>
      </div>
    </div>
    <!-- count 模式:文件 + 命中行数 -->
    <div v-else-if="parsed?.output_mode === 'count'" class="ai-tool-dir-entries">
      <div v-for="(c, i) in parsed?.counts" :key="i" class="ai-tool-dir-entry">
        <span class="ai-tool-dir-icon ai-tool-dir-icon--file" v-html="fileIcon"></span>
        <span class="ai-tool-dir-name">{{ c.file }}</span>
        <span class="ai-tool-dir-size">{{ $t('aiTool.grepCountLines', { n: c.count }) }}</span>
      </div>
    </div>
    <!-- content 模式:命中行(文件 | 行号 gutter | 内容 同行),支持上下文折叠 -->
    <!-- BUG-260626-01:文件名/行号/内容同一 flex row,不再 head 块 + content 块分两行 -->
    <div v-else class="ai-tool-grep-matches">
      <div v-for="(m, i) in parsed?.matches" :key="i" class="ai-tool-grep-match">
        <div class="ai-tool-grep-match-main">
          <span class="ai-tool-grep-file">{{ m.file }}</span>
          <span v-if="m.line !== null && m.line !== undefined" class="ai-tool-grep-line">{{ m.line }}</span>
          <code class="ai-tool-grep-content">{{ m.content }}</code>
        </div>
        <pre v-if="m.context" class="ai-tool-grep-context"><code>{{ m.context }}</code></pre>
      </div>
    </div>
  </div>

  <!-- write_file 结果 -->
  <div v-else-if="tc.name === 'write_file' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-write-result">
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--df-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
    <span class="ai-tool-write-path">{{ parsed?.path }}</span>
    <span class="ai-tool-write-meta">{{ formatBytes(parsed?.bytes_written) }} {{ $t('aiTool.written') }}</span>
    <span v-if="typeof parsed?.old_size === 'number' && parsed.old_size > 0" class="ai-tool-write-meta">{{ $t('aiTool.writeOverwrite', { old: formatBytes(parsed.old_size), neww: formatBytes(parsed?.bytes_written) }) }}</span>
    <span v-if="typeof parsed?.encoding === 'string' && parsed.encoding !== 'utf-8'" class="ai-tool-write-meta">{{ $t('aiTool.encodingLabel', { enc: parsed.encoding }) }}</span>
  </div>

  <!-- patch_file 结果:文件路径/字节增减 + unified diff 红删绿增行级预览(复用 ai-tool-diff-pre/--add/--del/--ctx 既有样式)
       UX-260618-06:后端 generate_diff 产完整 diff,result.diff 字段此前被丢弃,用户无法验证改了什么 → 此分支渲染 diff -->
  <div v-else-if="tc.name === 'patch_file' && tc.result && tc.status === 'completed' && parsed" class="ai-tool-patch-result">
    <div class="ai-tool-file-bar">
      <span class="ai-tool-file-icon" v-html="fileIcon"></span>
      <span class="ai-tool-file-path">{{ parsed?.path }}</span>
      <span class="ai-tool-file-meta">{{ formatSizeDiff(parsed?.size_diff) }} {{ $t('aiTool.bytesUnit') }}</span>
      <span v-if="parsed?.matches_found && parsed.matches_found > 1" class="ai-tool-file-meta">{{ $t('aiTool.patchedMatches', { n: parsed.matches_found }) }}</span>
    </div>
    <div v-if="parsed?.warning" class="ai-tool-approval-reason">⚠ {{ parsed.warning }}</div>
    <div v-if="resultDiffLines.length" class="ai-tool-approval-diff">
      <pre class="ai-tool-diff-pre"><code><span v-for="(ln, idx) in resultDiffLines" :key="idx" class="ai-tool-diff-line" :class="'ai-tool-diff-line--' + ln.kind">{{ ln.text }}{{ '\n' }}</span></code></pre>
    </div>
  </div>

  <!-- run_command 结果:命令行常显 + 可折叠输出(执行后仍能看到跑了什么命令) -->
  <div
    v-else-if="tc.name === 'run_command' && tc.result && tc.status === 'completed'"
    class="ai-tool-cmd-result"
  >
    <div class="ai-tool-cmd-line" :class="{ 'ai-tool-cmd-line--failed': isFailed }">
      <span class="ai-tool-cmd-prompt">$</span>
      <code class="ai-tool-cmd-text">{{ argString(tc.args, 'command') }}</code>
      <button
        v-if="cmdOutput"
        class="ai-tool-cmd-toggle"
        :title="cmdOutputExpanded ? $t('aiTool.collapse') : $t('aiTool.expand')"
        @click="cmdOutputExpanded = !cmdOutputExpanded"
      >{{ cmdOutputExpanded ? $t('aiTool.collapse') : $t('aiTool.expand') }}</button>
    </div>
    <pre v-if="cmdOutputExpanded && cmdOutput" class="ai-tool-cmd-output"><code>{{ cmdOutput }}</code></pre>
  </div>

  <!-- http_request 结果:method + url + 状态码徽标 + 可折叠响应 body(对齐 run_command 模式 UX-260618-08) -->
  <div
    v-else-if="tc.name === 'http_request' && tc.result && tc.status === 'completed' && parsed"
    class="ai-tool-http-result"
  >
    <div class="ai-tool-http-line" :class="{ 'ai-tool-http-line--failed': isFailed }">
      <span class="ai-tool-http-method">{{ parsed?.method || 'GET' }}</span>
      <code class="ai-tool-http-url">{{ parsed?.url }}</code>
      <span class="ai-tool-http-status" :class="{ 'ai-tool-http-status--ok': !isFailed }">{{ parsed?.status }}</span>
      <button
        v-if="parsed?.body"
        class="ai-tool-cmd-toggle"
        :title="httpBodyExpanded ? $t('aiTool.collapse') : $t('aiTool.expand')"
        @click="httpBodyExpanded = !httpBodyExpanded"
      >{{ httpBodyExpanded ? $t('aiTool.collapse') : $t('aiTool.expand') }}</button>
    </div>
    <div v-if="parsed?.truncated && !httpBodyExpanded" class="ai-tool-http-trunc-warn">
      ⚠ {{ $t('aiTool.httpTruncated', { bytes: formatBytes(parsed?.body_bytes) }) }}
    </div>
    <pre v-if="httpBodyExpanded && parsed?.body" class="ai-tool-cmd-output"><code>{{ parsed.body }}</code></pre>
  </div>

  <!-- fetch_url 结果:URL + title + render_mode 徽标 + 可折叠 markdown
       render_mode='static_fallback'(obscura 未装/失败)→ 显 obscura 未装提示 + "AI 帮装"按钮
       (按钮 emit install-obscura → MessageList 调 store.sendMessage 触发 AI 装流式) -->
  <div
    v-else-if="tc.name === 'fetch_url' && tc.result && tc.status === 'completed' && parsed"
    class="ai-tool-fetch-result"
  >
    <div class="ai-tool-fetch-line">
      <span class="ai-tool-http-method ai-tool-fetch-mode" :class="'ai-tool-fetch-mode--' + (parsed?.render_mode || 'static')">
        {{ parsed?.render_mode === 'obscura' ? $t('aiTool.fetchUrlRendered')
          : parsed?.render_mode === 'obscura_failed' ? $t('aiTool.fetchUrlFallback')
          : $t('aiTool.fetchUrlStatic') }}
      </span>
      <code class="ai-tool-http-url">{{ parsed?.url }}</code>
      <span v-if="typeof parsed?.length === 'number' && parsed?.length > 0" class="ai-tool-file-meta">{{ parsed?.length }} {{ $t('aiTool.charsUnit') }}</span>
      <button
        v-if="parsed?.markdown"
        class="ai-tool-cmd-toggle"
        :title="fetchMdExpanded ? $t('aiTool.collapse') : $t('aiTool.expand')"
        @click="fetchMdExpanded = !fetchMdExpanded"
      >{{ fetchMdExpanded ? $t('aiTool.collapse') : $t('aiTool.expand') }}</button>
    </div>
    <div v-if="parsed?.title" class="ai-tool-fetch-title">{{ parsed?.title }}</div>
    <!-- obscura 未装引导(render=true 但回退静态):提示 + "AI 帮装"按钮 -->
    <div v-if="obscuraMissing" class="ai-tool-obscura-hint">
      <div class="ai-tool-obscura-hint-text">⚠ {{ parsed?.hint || $t('aiTool.fetchUrlFallbackHint') }}</div>
      <button class="ai-tool-btn ai-tool-btn--install" @click="onInstallObscura">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        {{ $t('aiTool.installObscura') }}
      </button>
    </div>
    <pre v-if="fetchMdExpanded && parsed?.markdown" class="ai-tool-cmd-output"><code>{{ parsed.markdown }}</code></pre>
  </div>

  <!-- 通用 CRUD 结果 -->
  <div
    v-else-if="tc.result && tc.status === 'completed'"
    class="ai-tool-result"
    :class="{ 'ai-tool-result--failed': isFailed }"
  >
    <div v-if="isFailed" class="ai-tool-failed-banner">⚠ {{ $t('aiTool.executionFailedHint') }}</div>
    <code>{{ formatToolResult(tc) }}</code>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import {
  isToolFailure,
  parseResult,
  formatBytes,
  argString,
  formatSizeDiff,
  combineOutputs,
  formatToolResult,
  formatGrep,
  dirIcon,
  fileIcon,
  searchIcon,
} from '@/composables/ai/useToolCard'
import { parseDiffLines } from '@/composables/ai/useToolCardRender'
import type { AiToolCallInfo } from '@/api/types'

const props = defineProps<{
  /** 单条工具调用数据(结果展示只读消费) */
  tc: AiToolCallInfo
  /** read_file 内容级展开(来自父级 ToolCard,原样透传) */
  isContentExpanded: boolean
}>()

const emit = defineEmits<{
  /** read_file "展开全部/收起" → 透传父级(由 ToolCard 继续冒泡) */
  'expand-content': [id: string]
  /**
   * fetch_url static_fallback(obscura 未装)时"AI 帮装"按钮 → 透传父级 → MessageList
   * 调 store.sendMessage 触发 AI 装流式安装。仅 fetch_url 工具会发此事件。
   */
  'install-obscura': []
}>()

// run_command 输出折叠态:失败命令默认展开(便于看 stderr),成功默认折叠
const cmdOutputExpanded = ref(false)
// http_request 响应 body 折叠态:失败(非 2xx)默认展开便于看错误,成功默认折叠
const httpBodyExpanded = ref(false)
// fetch_url markdown 折叠态:默认折叠(可能很长,头部已有 title + 截断字符数摘要)
const fetchMdExpanded = ref(false)

/** 一次解析结果供模板多次读(模板引用多次,computed 避免每次 patch 重 parse) */
const parsed = computed(() => parseResult(props.tc.result))

/**
 * 是否工具失败(completed 语义)。复用 parsed 不重 parse。逻辑与模块级 isToolFailure 同源,
 * 但因 run_command 失败判定依赖 parsed.exit_code(本组件已 computed),此处就近复用避免重复 parse。
 */
const isFailed = computed(() => {
  const tc = props.tc
  if (tc.status !== 'completed') return false
  const r = parsed.value
  if (tc.name === 'run_command') {
    if (r && typeof r.exit_code === 'number') return r.exit_code !== 0
    if (r) return true
  }
  if (!r) {
    const raw = typeof tc.result === 'string' ? tc.result : ''
    if (!raw) return false
    return /^(执行失败|Error:|Failed:)/m.test(raw)
  }
  return false
})

/** run_command 展开态全量输出(stdout+stderr 合并);空串隐藏 toggle。复用 parsed 不重 parse */
const cmdOutput = computed(() => {
  const r = parsed.value
  if (!r) return ''
  return combineOutputs(r.stdout, r.stderr)
})

/**
 * UX-260618-06: patch_file 结果 diff 行拆解(复用 parseDiffLines +add/-del/ ctx 着色)。
 * 数据源:parsed.diff(patch_file 工具执行结果字段)。长 diff 截断到前 120 行防撑爆卡片。
 */
const resultDiffLines = computed(() => parseDiffLines(parsed.value?.diff, 120))

/**
 * grep 命中计数标签(F-260621):G-1 复用 useToolCard.formatGrep 单一函数,
 * 消除原与 ToolResultBody 重复的三分支(files_with_matches/count/content 文案)。
 * parsed 为 null 返空串,等价原逻辑;非 null 委托 formatGrep。
 */
const grepHitLabel = computed(() => {
  const r = parsed.value
  return r ? formatGrep(r) : ''
})

/**
 * fetch_url obscura 未装检测(render=true 但 obscura 未装):
 * 后端 render=true 且 obscura 未装 → result.render_mode='obscura_not_installed' + hint 引导。
 * 装了但失败/超时 → 'obscura_failed'(不弹引导,只 hint)。此 computed 仅未装弹"AI 帮装"。
 */
const obscuraMissing = computed(() => {
  if (props.tc.name !== 'fetch_url') return false
  const r = parsed.value
  if (!r) return false
  const mode = typeof r.render_mode === 'string' ? r.render_mode : ''
  return mode === 'obscura_not_installed'
})

/**
 * fetch_url "AI 帮装 obscura" → emit 透传到 MessageList,调 store.sendMessage
 * 触发 AI run_command 装流式。安装完成后用户重试即可用 obscura 渲染。
 * 提示语足够具体(给出两种装法),AI 拿到能直接 run_command 不需再问。
 */
function onInstallObscura() {
  emit('install-obscura')
}

// SW-260618-06: status 切到 completed 时按成功/失败初始化折叠态(失败默认展开便于看 stderr/错误 body)。
// immediate 时若初始非 completed,两 ref 保持默认 false,无副作用。
watch(() => props.tc.status, (s) => {
  if (s === 'completed') {
    cmdOutputExpanded.value = isToolFailure(props.tc)
    httpBodyExpanded.value = isToolFailure(props.tc)
  }
}, { immediate: true })
</script>

<style scoped>
/* 结果展示区样式(从 ToolCard.vue 迁移,仅含结果分支用到的 class;卡片容器/header/审批/骨架
   等样式留 ToolCard.vue)。失败横幅/通用结果失败态样式一并迁移,因结果区独立判定 isFailed。 */
.ai-tool-failed-banner {
  padding: 4px 10px 2px;
  font-size: 11px;
  font-weight: 500;
  color: var(--df-danger);
  background: var(--df-danger-bg);
  border-bottom: 0.5px solid rgba(240,101,101,0.2);
}
.ai-tool-result--failed {
  background: var(--df-danger-bg);
  border-top-color: rgba(240,101,101,0.2);
}
.ai-tool-result--failed code {
  color: var(--df-danger);
}

/* -- File Content (read_file) -- */
.ai-tool-file-content {
  border-top: 0.5px solid var(--df-border);
}
.ai-tool-file-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  background: color-mix(in srgb, var(--df-bg) 80%, transparent);
  border-bottom: 0.5px solid var(--df-border);
}
.ai-tool-file-icon {
  display: flex;
  align-items: center;
  color: var(--df-info);
  flex-shrink: 0;
}
.ai-tool-file-icon--dir {
  color: var(--df-warning);
}
.ai-tool-file-path {
  font-family: var(--df-font-mono);
  font-size: 10px;
  color: var(--df-accent);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ai-tool-file-meta {
  font-family: var(--df-font-mono);
  font-size: 9px;
  color: var(--df-text-dim);
  flex-shrink: 0;
}
.ai-tool-file-meta--warn {
  color: var(--df-warning);
}

/* grep 结果(F-260621):文件栏复用 ai-tool-file-bar,命中行用等宽渲染 */
.ai-tool-grep-result {
  border-top: 0.5px solid var(--df-border);
}
.ai-tool-grep-matches {
  padding: 4px 0;
  max-height: 260px;
  overflow-y: auto;
}
.ai-tool-grep-match {
  padding: 3px 12px;
  border-bottom: 0.5px solid rgba(255,255,255,0.03);
}
.ai-tool-grep-match:last-child {
  border-bottom: none;
}
/* BUG-260626-01:命中行主体 = 单行 flex row(文件名 | 行号 gutter | 内容 并排),
   取代原 head 块 + content 块的垂直两行堆叠(行号内容分行根因)。 */
.ai-tool-grep-match-main {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: var(--df-font-mono);
  font-size: 11px;
}
.ai-tool-grep-file {
  color: var(--df-accent);
  flex-shrink: 0;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 10px;
  padding-top: 1px;
}
.ai-tool-grep-line {
  color: var(--df-text-dim);
  flex-shrink: 0;
  min-width: 2em;
  text-align: right;
  font-size: 10px;
  user-select: none;
  padding-top: 1px;
}
.ai-tool-grep-content {
  flex: 1;
  min-width: 0;
  font-family: var(--df-font-mono);
  font-size: 11px;
  color: var(--df-text);
  white-space: pre-wrap;
  word-break: break-all;
  background: rgba(61,219,160,0.05);
  padding: 2px 6px;
  border-radius: var(--df-radius-xs, 3px);
}
.ai-tool-grep-context {
  margin: 2px 0 0;
  padding: 0;
  max-height: 120px;
  overflow: auto;
  font-family: var(--df-font-mono);
  font-size: 10px;
  color: var(--df-text-dim);
  background: rgba(255,255,255,0.02);
  border-radius: var(--df-radius-xs, 3px);
}
.ai-tool-grep-context code {
  display: block;
  padding: 4px 6px;
  white-space: pre;
}
.ai-tool-expand-btn {
  font-family: var(--df-font-sans);
  font-size: 10px;
  color: var(--df-text-dim);
  background: rgba(255,255,255,0.04);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm);
  padding: 2px 8px;
  cursor: pointer;
  flex-shrink: 0;
  transition: all 0.15s var(--df-ease);
}
.ai-tool-expand-btn:hover {
  color: var(--df-text-secondary);
  background: rgba(255,255,255,0.08);
  border-color: var(--df-border-strong);
}
.ai-tool-file-pre {
  margin: 0;
  padding: 10px 12px;
  background: color-mix(in srgb, var(--df-bg) 60%, transparent);
  font-family: var(--df-font-mono);
  font-size: 11px;
  line-height: 1.6;
  color: var(--df-text-secondary);
  overflow-x: auto;
  tab-size: 4;
}
.ai-tool-file-pre--collapsed {
  max-height: 180px;
  /* hidden→auto: 默认高度内可直接滚轮看完整内容，不必先点"展开全部" */
  overflow-y: auto;
  position: relative;
}
.ai-tool-file-pre--collapsed::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 48px;
  background: linear-gradient(transparent, color-mix(in srgb, var(--df-bg) 90%, transparent));
  pointer-events: none;
}

/* -- Directory List (list_directory / search_files) -- */
.ai-tool-dir-list {
  border-top: 0.5px solid var(--df-border);
}
.ai-tool-dir-entries {
  padding: 4px 0;
  max-height: 220px;
  overflow-y: auto;
}
.ai-tool-dir-entry {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 3px 12px;
  font-size: 11px;
  transition: background 0.1s;
}
.ai-tool-dir-entry:hover {
  background: rgba(255,255,255,0.03);
}
.ai-tool-dir-entry--directory .ai-tool-dir-name {
  font-weight: 500;
  color: var(--df-text);
}
.ai-tool-dir-icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.ai-tool-dir-icon--folder { color: var(--df-warning); }
.ai-tool-dir-icon--file   { color: var(--df-text-dim); }
.ai-tool-dir-name {
  font-family: var(--df-font-mono);
  color: var(--df-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}
.ai-tool-dir-size {
  font-family: var(--df-font-mono);
  font-size: 9px;
  color: var(--df-text-dim);
  flex-shrink: 0;
  opacity: 0.7;
}

/* -- Write File Result -- */
.ai-tool-write-result {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-top: 0.5px solid var(--df-border);
  background: rgba(61,219,160,0.04);
}
.ai-tool-write-path {
  font-family: var(--df-font-mono);
  font-size: 11px;
  color: var(--df-accent);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ai-tool-write-meta {
  font-size: 10px;
  color: var(--df-success);
  flex-shrink: 0;
}

/* -- patch_file diff 预览(复用审批 diff 同款 token) -- */
.ai-tool-approval-reason {
  padding: 4px 10px 6px;
  margin-bottom: 6px;
  font-size: 11px;
  color: var(--df-warning, #d97706);
  background: var(--df-warning-bg, rgba(217, 119, 6, 0.08));
  border-radius: var(--df-radius-sm, 4px);
}
.ai-tool-approval-diff {
  margin: 0 10px 8px;
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm, 4px);
  overflow: hidden;
}
.ai-tool-diff-pre {
  margin: 0;
  padding: 6px 0;
  max-height: 240px;
  overflow: auto;
  font-family: var(--df-font-mono);
  font-size: 11px;
  line-height: 1.5;
  background: var(--df-bg-card);
}
.ai-tool-diff-pre code {
  display: block;
  white-space: pre;
}
.ai-tool-diff-line {
  display: block;
  padding: 0 10px;
  white-space: pre-wrap;
  word-break: break-all;
}
.ai-tool-diff-line--add { color: var(--df-success, #3ddb9c); background: var(--df-success-bg, rgba(61, 219, 160, 0.08)); }
.ai-tool-diff-line--del { color: var(--df-danger, #f06565); background: var(--df-danger-bg, rgba(240, 101, 101, 0.08)); }
.ai-tool-diff-line--ctx { color: var(--df-text-dim, #888); }

/* -- Generic Result --
   max-height 100px→200px:通用结果常含 JSON/结构化文本,100px 仅约 5 行,默认滚动条挤压可读性;
   对齐 ai-tool-file-pre--collapsed(180px) 与 ai-tool-dir-entries(220px) 量级。 */
.ai-tool-result {
  margin: 0;
  padding: 8px 10px;
  background: rgba(61,219,160,0.04);
  border-top: 0.5px solid var(--df-border);
  border-radius: 0;
  font-family: var(--df-font-mono);
  font-size: 11px;
  color: var(--df-success);
  max-height: 200px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* -- run_command 结果:命令行(常显) + 可折叠输出 -- */
.ai-tool-cmd-result {
  margin: 0;
  border-top: 0.5px solid var(--df-border);
  font-family: var(--df-font-mono);
  font-size: 11px;
}
.ai-tool-cmd-line {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(61,219,160,0.04);
}
.ai-tool-cmd-line--failed {
  background: var(--df-danger-bg);
}
.ai-tool-cmd-prompt {
  flex-shrink: 0;
  font-weight: 600;
  color: var(--df-success);
}
.ai-tool-cmd-line--failed .ai-tool-cmd-prompt {
  color: var(--df-danger);
}
.ai-tool-cmd-text {
  flex: 1;
  min-width: 0;
  white-space: pre-wrap;
  word-break: break-all;
  color: var(--df-text);
}
.ai-tool-cmd-toggle {
  flex-shrink: 0;
  padding: 2px 4px;
  border: none;
  border-radius: var(--df-radius-sm);
  background: transparent;
  color: var(--df-text-dim);
  font-size: 10.5px;
  cursor: pointer;
}
.ai-tool-cmd-toggle:hover {
  color: var(--df-text);
  background: var(--df-bg-hover, rgba(255,255,255,0.06));
}
.ai-tool-cmd-output {
  margin: 0;
  padding: 8px 10px;
  max-height: 160px;
  overflow-y: auto;
  background: rgba(0,0,0,0.18);
  color: var(--df-text-dim);
  white-space: pre-wrap;
  word-break: break-all;
  border-top: 0.5px solid var(--df-border);
}

/* -- http_request 结果:method 徽标 + url + 状态码(复用 cmd-line 视觉 token) -- */
.ai-tool-http-result {
  margin: 0;
  border-top: 0.5px solid var(--df-border);
  font-family: var(--df-font-mono);
  font-size: 11px;
}
.ai-tool-http-line {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(61,219,160,0.04); /* 成功默认(对齐 cmd-line);失败由 --failed 覆盖 */
}
.ai-tool-http-line--failed {
  background: var(--df-danger-bg);
}
.ai-tool-http-method {
  flex-shrink: 0;
  font-weight: 600;
  padding: 1px 5px;
  border-radius: var(--df-radius-sm);
  background: rgba(255,255,255,0.08);
  color: var(--df-text);
  font-size: 10px;
}
.ai-tool-http-url {
  flex: 1;
  min-width: 0;
  white-space: pre-wrap;
  word-break: break-all;
  color: var(--df-text-dim);
}
.ai-tool-http-status {
  flex-shrink: 0;
  font-weight: 600;
  color: var(--df-danger);
}
.ai-tool-http-status--ok {
  color: var(--df-success);
}
.ai-tool-http-trunc-warn {
  padding: 4px 10px;
  font-size: 10.5px;
  color: var(--df-warning, #f0c75e);
  border-top: 0.5px solid var(--df-border);
}

/* -- fetch_url 结果:复用 http-line 视觉 token;render_mode 徽标按 mode 着色 -- */
.ai-tool-fetch-result {
  margin: 0;
  border-top: 0.5px solid var(--df-border);
  font-family: var(--df-font-mono);
  font-size: 11px;
}
.ai-tool-fetch-line {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(94,175,240,0.04);
}
.ai-tool-fetch-mode {
  /* 复用 .ai-tool-http-method 基样(template 已加该 class),此处仅覆盖 mode 着色 */
  font-size: 10px;
}
.ai-tool-fetch-mode--static,
.ai-tool-fetch-mode--obscura {
  color: var(--df-success, #3ddb9c);
}
.ai-tool-fetch-mode--static_fallback {
  color: var(--df-warning, #f0c75e);
}
.ai-tool-fetch-title {
  padding: 4px 10px 2px;
  font-family: var(--df-font-sans);
  font-size: 11px;
  font-weight: 600;
  color: var(--df-text);
  word-break: break-word;
}
/* obscura 未装引导(static_fallback):warning 色调提示 + AI 帮装按钮 */
.ai-tool-obscura-hint {
  margin: 4px 10px 6px;
  padding: 6px 8px;
  background: var(--df-warning-bg, rgba(240,199,94,0.08));
  border: 0.5px solid var(--df-warning, #f0c75e);
  border-radius: var(--df-radius-sm, 4px);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ai-tool-obscura-hint-text {
  font-family: var(--df-font-sans);
  font-size: 10.5px;
  color: var(--df-warning, #f0c75e);
  line-height: 1.5;
  word-break: break-word;
}
/* AI 帮装按钮(accent 色调,对齐 batch-approve 视觉) */
.ai-tool-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 12px;
  font-size: 11px;
  font-weight: 500;
  border-radius: var(--df-radius-sm, 4px);
  border: none;
  background: var(--df-accent, #5b8def);
  color: #fff;
  cursor: pointer;
  font-family: var(--df-font-sans);
  transition: filter 0.15s var(--df-ease);
}
.ai-tool-btn:hover { filter: brightness(1.1); }
</style>
