<template>
  <!-- F-260619-03 Phase B + path_auth 审批链阶段1: 路径授权弹窗(LLM 想访问白名单外目录,
       后端挂起 loop 等用户决定)。阶段1 改遍历 pendingDirAuths(数组)——同轮多个文件工具落不同
       未授权目录时后端连发多条 AiDirAuthRequired,数组并存不互覆盖。
       条件:pendingDirAuths 非空(AiDirAuthRequired 事件置)+ 当前视图正在生成(防切走后误显)。
       每项独立四按钮:点"本次" → ai_authorize_dir(decision=once, 写 once 层单次授权,执行后清)→ 后端执行工具 + try_continue。
       点"当前会话" → ai_authorize_dir(decision=session, 写 session 层会话授权,切会话清)→ 执行 + 续 loop。
       点"始终放行" → ai_authorize_dir(decision=always, 写持久化 Settings KV)→ 执行 + 续 loop。
       点"拒绝" → ai_authorize_dir(decision=deny, 工具返 Err)→ 续 loop。
       store 单例共享(state/aiApi),pendingDirAuths 模块级 ref(useAiEvents)直接导入。
       toast 经 emit 转父(保持单一 toast 源)。 -->
  <div v-for="item in visibleDirAuths" :key="item.id" ref="dirAuthRefs" class="ai-dir-auth">
    <span class="ai-dir-auth-text">{{ $t('aiChat.dirAuthRequired') }}</span>
    <span class="ai-dir-auth-hint">
      {{ $t('aiChat.dirAuthHint', { tool: item.tool, path: item.path }) }}
    </span>
    <div class="ai-dir-auth-actions">
      <button
        class="ai-dir-auth-btn ai-dir-auth-btn--once"
        :disabled="actingIds.has(item.id)"
        @click="handleAuthorize(item.id, 'once')"
      >{{ $t('aiChat.dirAuthOnce') }}</button>
      <button
        class="ai-dir-auth-btn ai-dir-auth-btn--session"
        :disabled="actingIds.has(item.id)"
        @click="handleAuthorize(item.id, 'session')"
      >{{ $t('aiChat.dirAuthSession') }}</button>
      <button
        class="ai-dir-auth-btn ai-dir-auth-btn--always"
        :disabled="actingIds.has(item.id)"
        @click="handleAuthorize(item.id, 'always')"
      >{{ $t('aiChat.dirAuthAlways') }}</button>
      <button
        class="ai-dir-auth-btn ai-dir-auth-btn--deny"
        :disabled="actingIds.has(item.id)"
        @click="handleAuthorize(item.id, 'deny')"
      >{{ $t('aiChat.dirAuthDeny') }}</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, computed, watch, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAiStore } from '../../stores/ai'
import { pendingDirAuths } from '../../composables/ai/useAiEvents'
import { aiApi } from '../../api'

const emit = defineEmits<{
  (e: 'toast', payload: { msg: string; type: 'error' | 'warning' | 'info' }): void
}>()

const store = useAiStore()
const { t } = useI18n()

// F-260620 根治(授权挂起无声卡死):挂起态弹窗显示只依赖 isGenerating(conv),不依赖 streaming。
// streaming 会被流式看门狗超时清(useAiStream onStreamTimeout),而 AiDirAuthRequired 挂起常发生在
// 首轮慢响应(看门狗已超时清 streaming)之后——此时 streaming=false 但后端 generating=true、
// generatingConvs 含 conv(useAiEvents handleEvent 对非完成/错误事件 add)。若守卫仍 && streaming,
// 挂起弹窗永不显示 → loop 无声卡死(用户发消息无响应)。去 streaming,isGenerating(conv) 单条件覆盖。
// F-09: 多会话并发 — isGenerating(active) 替代单值比对
const isViewingGenerating = computed(() =>
  store.isGenerating(store.state.activeConversationId),
)

// pendingDirAuths 非空 + 当前视图正在生成(防切走后误显)双重守卫。
// 阶段1:数组化后,visibleDirAuths 是 pendingDirAuths 的派生视图(按 conv 过滤当前会话挂起项),
// 与 showDirAuthCard 合并为单一 computed(非空即显)。
const visibleDirAuths = computed(() => {
  if (!isViewingGenerating.value) return []
  // 仅显当前会话的挂起项(active conv);其他会话的挂起不在本视图弹窗。
  const active = store.state.activeConversationId
  return pendingDirAuths.value.filter(p => !p.conversationId || p.conversationId === active)
})

// 阶段1:逐 id 追踪"处理中"态(数组多挂起并存,单 boolean 不够)。
// reactive Set 的增删驱动按钮 disabled;Set 判定 O(1)。
const actingIds = reactive(new Set<string>())

// M8:卡片根元素 ref 数组(v-for ref 收集)。挂起弹窗出现在输入区上方,常被消息流/进度条挤出视口,
// 用户看不到按钮 → 挂起无声卡死。新增项时滚动定位到卡片让审批入口立即可见。
const dirAuthRefs = ref<HTMLElement[]>([])

/** 点三选项之一:按 id 定位挂起项,调 ai_authorize_dir。后端 remove pending → 写授权/拒 →
 *  execute → try_continue。不主动清 pendingDirAuths——由后端 AiApprovalResult/AiCompleted/AiError
 *  在 useAiEvents 内按 id filter / 清空。IPC 失败回滚 actingIds 让用户可重试。 */
async function handleAuthorize(id: string, decision: 'once' | 'session' | 'always' | 'deny'): Promise<void> {
  if (actingIds.has(id)) return
  actingIds.add(id)
  try {
    await aiApi.authorizeDir(id, decision)
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e)
    emit('toast', { msg: t('aiChat.dirAuthFailed', { msg }), type: 'error' })
    actingIds.delete(id) // IPC 失败:复位该 id,允许重试
    return
  }
  // F-260620 根治(授权按钮永转圈):IPC 成功后 try_continue spawn 续跑 loop,续跑 loop 可能立刻又
  // 触发 AiDirAuthRequired(同 id 不可能,但其他 id 可能)置新 pendingDirAuths 项;该 id 的项会由
  // AiApprovalResult 按 id filter 移除。这里不依赖 pendingDirAuths 状态复位 actingIds——改为
  // watch pendingDirAuths,某 id 离开数组(已被后端消费)即删 actingIds,精准复位。
}

/** pendingDirAuths 变化时,离开数组(已被后端 AiApprovalResult/AiCompleted/AiError 按 id 移除)的
 *  id 从 actingIds 删,精准复位"处理中"态(数组化后多挂起,单 watch===null 模式不再适用)。 */
watch(() => pendingDirAuths.value, (items) => {
  const liveIds = new Set(items.map(p => p.id))
  for (const id of [...actingIds]) {
    if (!liveIds.has(id)) actingIds.delete(id)
  }
})

/** M8:visibleDirAuths 出现新挂起项时,scrollIntoView 让审批入口立刻可见(挂起弹窗在输入区上方,
 *  易被消息流/进度条挤出视口,用户不滚动看不到 → 挂起无声卡死)。仅新增项触发滚动,避免已可见项
 *  反复跳动打扰。 */
watch(() => visibleDirAuths.value.map(d => d.id).join('|'), async () => {
  await nextTick()
  // 滚到最新(数组末)项,block:'center' 让按钮组居中可见
  const last = dirAuthRefs.value[dirAuthRefs.value.length - 1]
  if (last) last.scrollIntoView({ block: 'center', behavior: 'smooth' })
})
</script>

<style scoped>
.ai-dir-auth {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: 6px;
  padding: 6px 8px;
  background: color-mix(in srgb, var(--df-warning) 10%, transparent);
  border: 0.5px solid color-mix(in srgb, var(--df-warning) 40%, transparent);
  border-radius: var(--df-radius);
}
.ai-dir-auth-text {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--df-warning);
}
.ai-dir-auth-hint {
  font-size: 10.5px;
  color: var(--df-text-dim);
  opacity: 0.9;
  word-break: break-all;
}
.ai-dir-auth-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  flex-wrap: wrap;
}
.ai-dir-auth-btn {
  border: none;
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: calc(var(--df-radius) - 2px);
  transition: filter 0.15s var(--df-ease);
}
.ai-dir-auth-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.ai-dir-auth-btn--once {
  background: var(--df-accent);
  color: #fff;
}
.ai-dir-auth-btn--once:hover:not(:disabled) { filter: brightness(1.1); }
.ai-dir-auth-btn--session {
  background: color-mix(in srgb, var(--df-accent) 15%, transparent);
  color: var(--df-accent);
  border: 0.5px solid color-mix(in srgb, var(--df-accent) 40%, transparent);
}
.ai-dir-auth-btn--session:hover:not(:disabled) { filter: brightness(1.1); }
.ai-dir-auth-btn--always {
  background: var(--df-warning);
  color: #000;
}
.ai-dir-auth-btn--always:hover:not(:disabled) { filter: brightness(1.1); }
.ai-dir-auth-btn--deny {
  background: transparent;
  color: var(--df-text-dim);
  border: 0.5px solid var(--df-border);
}
.ai-dir-auth-btn--deny:hover:not(:disabled) {
  color: var(--df-text);
  border-color: var(--df-text-dim);
}
</style>
