<template>
  <div class="ai-tool-calls" ref="rootEl">
    <!-- 顶部操作栏：批量审批(全局收起已下沉到首个可折叠分组标题行,U-260618) -->
    <div v-if="pendingCount > 0" class="ai-tool-topbar">
      <!-- 批量操作栏（存在待审批项时显示） -->
      <div class="ai-batch-approve">
        <button class="ai-btn ai-btn--sm" @click="$emit('batch-approve', 'approve')">
          {{ $t('aiTool.approveAll', { n: pendingCount }) }}
        </button>
        <button class="ai-btn ai-btn--sm ai-btn--danger" @click="$emit('batch-approve', 'reject')">
          {{ $t('aiTool.rejectAll') }}
        </button>
      </div>
    </div>

    <template v-for="(group, gi) in groupedToolCalls" :key="group.name">
      <!-- 分组标题：同名工具 >=2 个时显示 -->
      <div v-if="group.calls.length >= 2" class="ai-tool-group-header" @click="toggleGroupCollapse(gi)">
        <span class="ai-tool-group-icon" v-html="groupIcon(group.name)"></span>
        <span class="ai-tool-group-name">{{ groupDisplayName(group) }}</span>
        <span class="ai-tool-group-count">{{ group.calls.length }}</span>
        <span class="ai-tool-group-files">{{ groupFileSummary(group) }}</span>
        <span class="ai-tool-group-chevron" :class="{ 'is-open': !isGroupCollapsed(gi) }">▸</span>
        <!-- 全部展开/收起(U-260618:从 topbar 下沉到首个可折叠分组标题行尾巴,与 run command 同行) -->
        <div
          v-if="gi === firstCollapsibleGi"
          class="ai-tool-global-toggle"
          @click.stop="toggleAllGroups"
        >
          <span class="ai-tool-global-toggle-icon">{{ allGroupsExpanded ? '▾' : '▸' }}</span>
          <span>{{ allGroupsExpanded ? $t('aiTool.collapseAll') : $t('aiTool.expandAll') }}</span>
        </div>
      </div>
      <ToolCard
        v-for="(tc, ci) in group.calls"
        :key="tc.id"
        :tc="tc"
        :isExpanded="isCardExpanded(tc, gi, ci)"
        :isContentExpanded="expandedTools.has(tc.id)"
        @toggle="() => toggleCardExpand(tc, gi, ci)"
        @expand-content="toggleExpand"
        @approve="(e) => emit('approve', e)"
        @install-obscura="() => emit('install-obscura')"
        :class="{ 'ai-tool-card--group-hidden': group.calls.length >= 2 && isGroupCollapsed(gi) }"
      />
    </template>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import ToolCard from './ToolCard.vue'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import type { AiToolCallInfo } from '@/api/types'

// useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key)
const { t } = useI18n()

const props = defineProps<{
  /** 当前消息的工具调用列表 */
  toolCalls: AiToolCallInfo[]
}>()

const emit = defineEmits<{
  /**
   * 审批按钮 → 父组件转发 store.approveToolCall。
   * path_auth 审批链阶段3b:path 类带 decision('once'|'session'|'always'|'deny'),risk 类仅 approved boolean。
   */
  approve: [{ id: string; approved: boolean; decision?: 'once' | 'session' | 'always' | 'deny' }]
  /** 批量审批 → 父组件调用 approveAll/rejectAll */
  'batch-approve': [decision: 'approve' | 'reject']
  /**
   * fetch_url static_fallback(obscura 未装)时"AI 帮装"按钮 → 透传父级 MessageList,
   * 调 store.sendMessage 触发 AI 装流式。仅 fetch_url 工具会发此事件。
   */
  'install-obscura': []
}>()

// 根元素 ref(scrollToFirstPending 查 DOM 用)
const rootEl = ref<HTMLElement>()
// 卡片级折叠（整卡 body 显隐，区别于 read_file 内容级）
const expandedCards = ref(new Set<string>())
// UX-260616-03:用户主动展开的卡片(记忆态,不被自动收起清除)。
// 与 expandedCards 区分:expandedCards 是"展开"语义的当前态(含默认首卡),
// userExpandedCards 是"用户明确点开"的持久标记,collapseInactive 跳过这些卡。
// 语义:用户读过的卡不强制折叠,符合"跟随阅读位置"的增强目标。
const userExpandedCards = ref(new Set<string>())
// UX-260617-23:用户主动折叠记忆(修复首卡 ci===0 兜底 return true 导致无法折叠)。
// isCardExpanded 最优先判断,覆盖默认首卡展开 + userExpandedCards 记忆。
const userCollapsedCards = ref(new Set<string>())
// 内容级展开（read_file "展开全部"）
const expandedTools = ref(new Set<string>())
// 分组折叠状态（按分组索引）；多卡片分组默认收起
const collapsedGroups = ref<Set<number>>(new Set())

interface ToolCallGroup {
  name: string
  calls: AiToolCallInfo[]
}

/** 按 tool.name 全局分组：同名工具归为一组（不要求相邻），保持原始顺序 */
const groupedToolCalls = computed<ToolCallGroup[]>(() => {
  const map = new Map<string, AiToolCallInfo[]>()
  // 第一遍：按 name 收集
  for (const tc of props.toolCalls) {
    const arr = map.get(tc.name)
    if (arr) arr.push(tc)
    else map.set(tc.name, [tc])
  }
  // 第二遍：按 toolCalls 首次出现顺序输出（保证显示顺序与调用顺序一致）
  const seen = new Set<string>()
  const groups: ToolCallGroup[] = []
  for (const tc of props.toolCalls) {
    if (!seen.has(tc.name)) {
      seen.add(tc.name)
      groups.push({ name: tc.name, calls: map.get(tc.name)! })
    }
  }
  return groups
})

/** 可折叠分组数（calls >= 2 的组），控制全局按钮显隐 */
const collapsibleGroupCount = computed(() =>
  groupedToolCalls.value.filter(g => g.calls.length >= 2).length
)

/** 首个可折叠分组索引(U-260618:全部展开/收起按钮下沉到此分组标题行尾巴) */
const firstCollapsibleGi = computed(() => {
  for (let i = 0; i < groupedToolCalls.value.length; i++) {
    if (groupedToolCalls.value[i].calls.length >= 2) return i
  }
  return -1
})

/** 待审批工具数（status === 'pending_approval'），控制批量操作栏显隐 */
const pendingCount = computed(() =>
  props.toolCalls.filter(tc => tc.status === 'pending_approval').length
)

/** 是否所有可折叠分组都已展开 */
const allGroupsExpanded = computed(() => {
  if (collapsibleGroupCount.value === 0) return true
  return groupedToolCalls.value.every((g, gi) =>
    g.calls.length < 2 || !collapsedGroups.value.has(gi)
  )
})

/** 初始化默认折叠：多卡片分组（>=2）默认收起，首条不参与 group-hidden 但跟随折叠态 */
function initDefaultCollapse(): void {
  const set = new Set<number>()
  for (let i = 0; i < groupedToolCalls.value.length; i++) {
    if (groupedToolCalls.value[i].calls.length >= 2) {
      set.add(i)
    }
  }
  collapsedGroups.value = set
}

/** 首次有数据时执行默认折叠初始化（watch 防重复，修复异步加载竞态 BUG-B） */
let initialized = false
watch(
  () => props.toolCalls.length,
  (len) => {
    if (len > 0 && !initialized) {
      initialized = true
      initDefaultCollapse()
    }
  },
  { immediate: true }
)

function isGroupCollapsed(gi: number): boolean {
  return collapsedGroups.value.has(gi)
}

function toggleGroupCollapse(gi: number): void {
  const next = new Set(collapsedGroups.value)
  if (next.has(gi)) next.delete(gi)
  else next.add(gi)
  collapsedGroups.value = next
}

/** 全部展开 / 全部收起切换 */
function toggleAllGroups(): void {
  const next = new Set<number>()
  if (allGroupsExpanded.value) {
    // 当前全展开 → 全部收起
    for (let i = 0; i < groupedToolCalls.value.length; i++) {
      if (groupedToolCalls.value[i].calls.length >= 2) next.add(i)
    }
  }
  // 当前非全展开 → 全部展开（next 保持空 Set）
  collapsedGroups.value = next
}

/**
 * 分组显示名：工具名 → i18n 友好标签(英文化,原硬编码中文)。
 * UX-260617-17:用 aiTool.toolGroup.* 子键查表;未命中的工具回退 _ → 空格风格。
 */
// tool.name → aiTool.toolGroup.* 子键(与后端 tool_registry 工具名一一对应)
const TOOL_GROUP_I18N_KEY: Record<string, string> = {
  read_file: 'readFile',
  write_file: 'writeFile',
  list_directory: 'listDirectory',
  grep: 'grep',
  create_task: 'createTask',
  create_project: 'createProject',
  create_idea: 'createIdea',
  list_tasks: 'listTasks',
  list_projects: 'listProjects',
  list_ideas: 'listIdeas',
  delete_project: 'deleteProject',
  restore_project: 'restoreProject',
  purge_project: 'purgeProject',
  update_project: 'updateProject',
  run_workflow: 'runWorkflow',
}

function groupDisplayName(group: ToolCallGroup): string {
  const subKey = TOOL_GROUP_I18N_KEY[group.name]
  if (subKey) return t(`aiTool.toolGroup.${subKey}`)
  return group.name.replace(/_/g, ' ')
}

/** 从工具调用参数中提取路径摘要(取前 3 个文件名)。
 *  供分组标题行展示,让用户一眼知道影响了哪些文件。*/
function groupFileSummary(group: ToolCallGroup): string {
  const paths: string[] = []
  for (const tc of group.calls) {
    const args = tc.args as Record<string, unknown> | undefined
    const p = args?.path
    if (typeof p === 'string' && p.trim()) {
      // 取最后一段文件名(或最后两级目录)
      const parts = p.trim().split(/[/\\]/)
      const short = parts.length >= 2
        ? parts.slice(-2).join('/')
        : parts[parts.length - 1]
      if (!paths.includes(short)) paths.push(short)
      if (paths.length >= 3) break
    }
  }
  return paths.length ? ' · ' + paths.join(' · ') : ''
}

/**
 * 单卡是否展开：
 * - 分组未折叠 → 用户手动 expandedCards 或首卡默认展开
 * - 分组已折叠 → false（整组隐藏）
 * - 多卡片分组 + 未手动操作过 → 仅首卡(ci===0)展开
 * - UX-260616-03:userExpandedCards(用户主动展开记忆)优先,即使父级 collapseInactive
 *   清了 expandedCards,记忆态仍保持展开(用户读过的卡不强制折叠)。
 */
function isCardExpanded(tc: AiToolCallInfo, gi: number, ci: number): boolean {
  if (isGroupCollapsed(gi)) return false
  // UX-260617-23:用户主动折叠最优先(覆盖默认首卡 ci===0 兜底展开 + 用户展开记忆)
  if (userCollapsedCards.value.has(tc.id)) return false
  // UX-260616-03:用户主动展开的卡片记忆态优先(不被自动收起影响)
  if (userExpandedCards.value.has(tc.id)) return true
  // 用户手动切换过该卡(当前态)
  if (expandedCards.value.has(tc.id)) return true
  // 多卡片分组：仅首卡默认展开
  if (groupedToolCalls.value[gi]?.calls.length >= 2) return ci === 0
  // 单卡组：默认展开
  return true
}

function toggleCardExpand(tc: AiToolCallInfo, gi: number, ci: number) {
  const id = tc.id
  // 用 isCardExpanded 判当前真实态(含默认首卡展开),而非 expandedCards(默认首卡不在其中,
  // 否则首卡点 toggle 会误判"当前折叠"反向展开)
  const currentlyExpanded = isCardExpanded(tc, gi, ci)
  if (currentlyExpanded) {
    // 展开 → 折叠
    expandedCards.value.delete(id)
    userExpandedCards.value.delete(id)
    userCollapsedCards.value.add(id)
  } else {
    // 折叠 → 展开
    expandedCards.value.add(id)
    userExpandedCards.value.add(id)
    userCollapsedCards.value.delete(id)
  }
}

function toggleExpand(id: string) {
  if (expandedTools.value.has(id)) expandedTools.value.delete(id)
  else expandedTools.value.add(id)
}

/**
 * 收起不在 activeIds 中的已完成/已拒绝卡片（供父组件 auto-collapse watch 调用）。
 * UX-260616-03:userExpandedCards(用户主动展开记忆)不被清除——用户读过的卡
 * 即使不再 active(running/pending/write_file)也保持展开,避免"我刚才在看,突然被收起"。
 * 仅清 expandedCards(默认态/首卡)与 expandedTools(内容级展开,跟随卡片折叠)。
 */
function collapseInactive(activeIds: Set<string>): void {
  // CR-260615-31:对称过滤 expandedTools(原只清 expandedCards,expandedTools 残留致折叠后内容展开态)
  if (expandedCards.value.size > 0) {
    expandedCards.value = new Set([...expandedCards.value].filter(id => activeIds.has(id)))
  }
  if (expandedTools.value.size > 0) {
    expandedTools.value = new Set([...expandedTools.value].filter(id => activeIds.has(id)))
  }
  // UX-260616-03:不清 userExpandedCards(记忆态),isCardExpanded 优先读它保持展开。
}

/**
 * 滚动到第一个 pending_approval 卡片。
 * 若该卡处于折叠分组(display:none → offsetParent 为 null),先展开所有折叠组,
 * 下一帧重查并滚动。供 AiChat 头部审批徽标点击调用。
 */
function scrollToFirstPending(): void {
  const root = rootEl.value
  if (!root) return
  const card = root.querySelector<HTMLElement>('.ai-tool-card--pending_approval')
  if (!card) return
  if (card.offsetParent === null) {
    // 处于折叠分组内 → 展开后下一帧重查
    collapsedGroups.value = new Set()
    nextTick(() => {
      root.querySelector<HTMLElement>('.ai-tool-card--pending_approval')
        ?.scrollIntoView({ behavior: 'smooth', block: 'center' })
    })
    return
  }
  card.scrollIntoView({ behavior: 'smooth', block: 'center' })
}

// BUG-260624-02(授权弹窗卡死根治·诊断 workflow high 置信):监听 pending 审批卡到达事件,
// 自动展开折叠分组 + scroll 到 pending 卡。根因:统一审批开关 df-ai-unified-approval 默认开 →
// path 审批走 ToolCard 内联,但同名工具≥2 分组默认折叠(initDefaultCollapse)+ group-hidden
// (display:none)→ 审批按钮对用户不可见 → 5min 超时(aiShared APPROVAL_TIMEOUT_MS)静默
// authorizeDir/approve(deny)→ 误显"用户拒绝" → 用户全程未见审批入口即"卡死/被拒"。
// scrollToFirstPending(:285)已能自动展开折叠组(offsetParent===null → collapsedGroups 清空),
// 原仅 AiChat 头部徽标手动触发(:230);此处 pending 到达即自动调用,让审批卡必可见可操作。
let _unlistenPending: UnlistenFn | null = null
// 论证 workflow 回归中优:防卸载竞态——await listen 解析前组件已卸载(HMR/快速切会话)时,
// onBeforeUnmount 跑到 _unlistenPending 仍 null 跳过 unlisten → 孤儿监听器泄漏。disposed 标志兜底。
let _disposed = false
onMounted(async () => {
  const fn = await listen('ai-pending-arrived', () => {
    // BUG-260624-01 残留优化:本实例无 pending 卡时短路返回,避免大列表 N 个 ToolCardList 实例
    // 每次广播都跑 querySelector(N 实例 × M 次 emit = N×M 次 DOM 查询)。原靠 scrollToFirstPending
    // 内 querySelector 返 null no-op 天然过滤(功能正确),此处显式 props 过滤把无 pending 实例挡在
    // 进入函数前。含 pending 的实例再走 nextTick + scrollToFirstPending(展开折叠组 + scroll)。
    if (!props.toolCalls.some(tc => tc.status === 'pending_approval')) return
    nextTick(() => scrollToFirstPending())
  })
  // 解析时若已卸载(HMR/极速 mount/unmount),立即 unlisten 刚拿到的句柄,防孤儿监听器。
  if (_disposed) { fn(); return }
  _unlistenPending = fn
})
onBeforeUnmount(() => {
  _disposed = true
  _unlistenPending?.()
  _unlistenPending = null
})

defineExpose({ collapseInactive, scrollToFirstPending })

/* -- 分组图标(复用 ToolCard 的 toolCategory 逻辑) -- */
function groupIcon(name: string): string {
  if (name === 'read_file') return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>'
  if (name === 'write_file') return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>'
  if (name === 'list_directory') return '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>'
  if (name.includes('create')) return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'
  if (name.includes('delete')) return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>'
  return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>'
}
</script>

<style scoped>
.ai-tool-calls {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* -- 顶部操作栏：批量审批 + 全局收起合并同行(UX-260616-02) -- */
.ai-tool-topbar {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* -- 批量操作栏 -- */
.ai-batch-approve {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--df-radius-sm);
  background: rgba(255,255,255,0.02);
}
/* .ai-btn 基类:批量审批按钮(历史重构遗漏定义,补全治本) */
.ai-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 6px 12px;
  border: none;
  border-radius: var(--df-radius-sm);
  background: var(--df-accent);
  color: #fff;
  font-family: var(--df-font-sans);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: filter 0.15s, background 0.15s;
  white-space: nowrap;
}
.ai-btn:hover { filter: brightness(1.1); }
.ai-btn--sm { padding: 4px 10px; font-size: 11px; }
.ai-btn--danger {
  background: var(--df-danger);
  color: #fff;
}
.ai-btn--danger:hover { filter: brightness(1.1); }

/* -- 全部展开/收起(U-260618:从 topbar 下沉到分组标题行,移除独立按钮 padding/背景,
      高度=文字行高,不撑高分组标题行;融入标题视觉,仅 hover 变色) -- */
.ai-tool-global-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  user-select: none;
  font-size: 11px;
  color: var(--df-text-dim);
  transition: color 0.15s var(--df-ease);
  /* 推到分组标题行尾巴右对齐 */
  margin-left: auto;
}
.ai-tool-global-toggle:hover {
  color: var(--df-text-secondary);
}
.ai-tool-global-toggle-icon {
  font-size: 10px;
  transition: transform 0.15s var(--df-ease);
}

/* -- 分组标题 -- */
.ai-tool-group-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  /* U-260618: max-width 对齐气泡/卡片(90%),不拉满 ai-msg-content 右侧 */
  max-width: var(--df-msg-max-width);
  cursor: pointer;
  user-select: none;
  border-radius: var(--df-radius-sm);
  transition: background 0.15s var(--df-ease);
  font-size: 11px;
  color: var(--df-text-secondary);
}
.ai-tool-group-header:hover {
  background: rgba(255,255,255,0.03);
}
.ai-tool-group-icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--df-text-dim);
}
.ai-tool-group-name {
  font-weight: 500;
  color: var(--df-text-secondary);
}
.ai-tool-group-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  font-size: 10px;
  font-weight: 600;
  background: var(--df-accent-bg);
  color: var(--df-accent);
}
.ai-tool-group-chevron {
  /* U-260618: 移除 margin-left:auto,让全部展开/收起按钮(global-toggle)占据行尾右对齐 */
  font-size: 10px;
  color: var(--df-text-dim);
  transition: transform 0.15s var(--df-ease);
  flex-shrink: 0;
}
.ai-tool-group-chevron.is-open {
  transform: rotate(90deg);
}

/* -- 分组隐藏态的卡片。
   UX-260616-03: 保持 display:none(原行为)。
   不改 max-height/opacity 动画的原因:flex 容器 gap:8px 对零高 item 仍贡献 gap,
   会留双倍间距致布局回归;display:none 移出流才无 gap。
   真正的高度动画需 <Transition> JS 钩子(高度 auto→0 不可纯 CSS transition),
   本轮野人 lite 不重构 ToolCard 单卡级折叠(v-show 同因)。
   触发时机扩展(新消息/新轮次)已落地,见 AiChat.vue watch。 -- */
.ai-tool-card--group-hidden {
  display: none;
}
</style>
