<template>
  <!-- ═══ 系统级审批提示浮窗(独立 WebviewWindow 内容)═══
       设计:
       - 无边框(decorations:false) + alwaysOnTop + skipTaskbar → 系统级可见
       - 紫色主题自定义样式(非 OS notification 默认)
       - 哑渲染器:监听主窗口推送的 approval-popup-update 事件,不重复监听 AI 事件
       - 操作按钮走 dispatchApprovalIPC(按 tc.kind 分派 aiApi.approve/authorizeDir;主窗口 listener 收后端 emit → 推新快照)
       - 点击「打开审批面板」emit approval-popup-activate → 主窗口聚焦 + togglePanel
       - 拖动条:.popup-drag(region CSS,WebviewWindow dataDrag 守卫)实现窗口拖动
       - 自动定位屏幕右上角(同 ApprovalOverlay 视觉一致) -->
  <div class="popup-shell" :data-dragging="isDragging">
    <!-- 顶部拖动条(双击不最大化,因 decorations:false) -->
    <div class="popup-header" data-tauri-drag-region @mousedown="onDragStart" @mouseup="onDragEnd">
      <div class="popup-header-icon">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <path d="M9 11l3 3L22 4" />
          <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
        </svg>
        <span v-if="approvals.length > 1" class="popup-header-badge">{{ approvals.length }}</span>
      </div>
      <div class="popup-header-title">{{ $t('aiChat.popupTitle') }}</div>
      <button class="popup-header-close" :title="$t('common.close')" @click.stop="onClose">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <line x1="18" y1="6" x2="6" y2="18" />
          <line x1="6" y1="6" x2="18" y2="18" />
        </svg>
      </button>
    </div>

    <!-- 待审批列表 -->
    <div class="popup-body">
      <div v-if="approvals.length === 0" class="popup-empty">
        <!-- BUG2 修复:空态不再永久转圈(原 popup-empty-spinner 致「处理完仍转圈」误判卡死)。
             空态正常会被 maybeAutoClose 即时关窗;此 hint 仅作防御性兜底(close 失败时给可见反馈)。 -->
        <span class="popup-empty-hint">{{ $t('aiChat.popupEmptyHint') }}</span>
      </div>
      <div
        v-for="tc in approvals"
        :key="tc.id"
        class="popup-item"
        :class="'popup-item--' + (tc.kind || 'risk')"
      >
        <!-- 工具名 + kind 徽章 -->
        <div class="popup-item-header">
          <span class="popup-item-name">{{ toolDisplayName(tc) }}</span>
          <span v-if="tc.kind === 'path'" class="popup-item-tag popup-item-tag--path">
            {{ $t('aiChat.popupTagPath') }}
          </span>
          <span v-else class="popup-item-tag popup-item-tag--risk">
            {{ $t('aiChat.popupTagRisk') }}
          </span>
        </div>
        <!-- 风险说明(后端 build_approval_reason 已含工具/参数/risk 描述) -->
        <div v-if="tc.reason" class="popup-item-reason">⚠ {{ tc.reason }}</div>
        <!-- H1 修复:审批挂起时长提示(对齐 ToolCard: >2min 橙色 / >5min 红色脉冲)。
             后端 APPROVAL_TIMEOUT_MS 5min 静默拒绝,浮窗原本无计时,用户误以为还有时间。
             基于审批到达时间(popup 首次见到该 tc.id 的时刻)计时,与 ToolCard 同款语义同款样式。 -->
        <div v-if="waitSecsFor(tc.id) > 0" class="popup-item-wait" :class="'is-' + waitLevelFor(tc.id)">
          {{ $t('aiTool.waitTime', { time: formatWaitTime(waitSecsFor(tc.id)) }) }}
        </div>
        <!-- path 类:目录提示 -->
        <div v-if="tc.kind === 'path' && tc.dir" class="popup-item-dir">
          📁 {{ tc.dir }}
        </div>
        <!-- 操作按钮组:path 类显 once/session/always/deny 四选项(与 ToolCard/DirAuthDialog 同款);
             risk 类显 approve/reject 两选项。IPC 分派统一走 dispatchApprovalIPC。 -->
        <div v-if="tc.kind === 'path'" class="popup-item-actions popup-item-actions--path">
          <button
            class="popup-btn popup-btn--approve"
            :disabled="processingId === tc.id"
            @click.stop="onApproveOnce(tc)"
          >
            <span v-if="processingId === tc.id" class="popup-btn-spinner"></span>
            <svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            {{ $t('aiChat.dirAuthOnce') }}<span class="popup-btn-key">1</span>
          </button>
          <button
            class="popup-btn popup-btn--session"
            :disabled="processingId === tc.id"
            @click.stop="onApproveSession(tc)"
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            {{ $t('aiChat.dirAuthSession') }}
          </button>
          <button
            class="popup-btn popup-btn--always"
            :disabled="processingId === tc.id"
            @click.stop="onApproveAlways(tc)"
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            {{ $t('aiChat.dirAuthAlways') }}<span class="popup-btn-key">2</span>
          </button>
          <button
            class="popup-btn popup-btn--reject"
            :disabled="processingId === tc.id"
            @click.stop="onReject(tc)"
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            {{ $t('aiChat.dirAuthDeny') }}<span class="popup-btn-key">3</span>
          </button>
        </div>
        <div v-else class="popup-item-actions">
          <button
            class="popup-btn popup-btn--approve"
            :disabled="processingId === tc.id"
            @click.stop="onApprove(tc)"
          >
            <span v-if="processingId === tc.id" class="popup-btn-spinner"></span>
            <svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            {{ $t('aiTool.approve') }}<span class="popup-btn-key">1</span>
          </button>
          <button
            class="popup-btn popup-btn--reject"
            :disabled="processingId === tc.id"
            @click.stop="onReject(tc)"
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
            {{ $t('aiTool.reject') }}<span class="popup-btn-key">3</span>
          </button>
        </div>
      </div>
    </div>

    <!-- 底部:打开审批面板(聚焦主窗口) -->
    <button class="popup-footer" @click="onActivate">
      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M15 3h6v6" /><path d="M9 21H3v-6" /><path d="M21 3l-7 7" /><path d="M3 21l7-7" />
      </svg>
      {{ $t('aiChat.popupOpenPanel') }}
    </button>

    <!-- M5:path 类 deny 二次确认(拒绝不可逆,与 ToolCard 同款;独立窗口本地 ConfirmDialog,
         不复用主窗口 useConfirm reactive——跨 WebviewWindow JS 上下文隔离) -->
    <ConfirmDialog
      :visible="confirmState.visible"
      :msg="confirmState.msg"
      :danger-label="$t('common.confirm')"
      @result="answerConfirm"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { toolDisplayName } from '@/composables/ai/useToolCardHeader'
import { dispatchApprovalIPC } from '@/composables/ai/useToolApproval'
import { ApprovalPopupEvents } from '@/composables/ai/useApprovalPopup'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import type { AiToolCallInfo } from '@/api/types'

const { t } = useI18n()

/** 主窗口推送的快照(唯一真相源,本组件不监听 AI 事件) */
const approvals = ref<AiToolCallInfo[]>([])
/** 操作中 id(防抖 + 按钮加载态) */
const processingId = ref<string>('')
/** 拖动态(样式反馈) */
const isDragging = ref(false)

// ── M5:path 类 deny 二次确认(独立窗口本地 confirm;拒绝不可逆——工具返 Err、loop 续跑不再重试)──
// 浮窗是独立 WebviewWindow,与主窗口 JS 上下文隔离,不复用 useConfirm 跨窗口 reactive。
// pendingDenyTc 持有「待确认拒绝」的 tc;answerConfirm 据 result 决定续跑 runApproval 或放弃。
const confirmState = reactive<{ visible: boolean; msg: string; tc: AiToolCallInfo | null }>({
  visible: false,
  msg: '',
  tc: null,
})
function answerConfirm(ok: boolean) {
  const tc = confirmState.tc
  confirmState.visible = false
  confirmState.tc = null
  if (ok && tc) {
    void runApproval(tc, false, 'deny')
  }
}

// ── H1:审批挂起时长计时(对齐 ToolCard useToolApproval 的 waitSecs/waitLevel)──
// 浮窗独立于主窗口,不直接复用 useToolApproval(那个绑定单 tc + 完整状态机 emit/防抖,
// 浮窗已用 processingId/runApproval 自管)。这里基于「到达时间」(popup 首次见到 tc.id 的时刻)
// 每秒刷新。语义/样式/阈值与 ToolCard 同款:120s long / 300s urgent。
/** tc.id → 到达时刻(ms)。审批首次出现在 approvals 即记时;移除即清。 */
const arrivalTimes = reactive(new Map<string, number>())
/** tc.id → 已等待秒数(每秒 tick 更新)。 */
const waitSecsMap = reactive(new Map<string, number>())
let _waitTimer: ReturnType<typeof setInterval> | null = null

/** 格式化秒为 MM:SS(与 useToolApproval.formatWaitTime 同款) */
function formatWaitTime(secs: number): string {
  const m = Math.floor(secs / 60)
  const s = secs % 60
  return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
/** 取某条审批已等待秒数(无记录返回 0) */
function waitSecsFor(id: string): number {
  return waitSecsMap.get(id) ?? 0
}
/** 取等待级别(对齐 ToolCard: 300s urgent / 120s long / 其余 normal) */
function waitLevelFor(id: string): 'normal' | 'long' | 'urgent' {
  const s = waitSecsFor(id)
  if (s >= 300) return 'urgent'
  if (s >= 120) return 'long'
  return 'normal'
}
/** 每秒刷新所有挂起审批的 waitSecs(单 timer 驱动多 tc,避免每条一个 interval) */
function _tickWaitSecs() {
  const now = Date.now()
  for (const [id, t0] of arrivalTimes.entries()) {
    waitSecsMap.set(id, Math.floor((now - t0) / 1000))
  }
}

/**
 * 同步到达时间到当前 approvals 快照。
 * - 新出现的 id(popup 首次见到该审批)→ 记 Date.now() 起算。
 * - 已不在快照的 id(被处理 / 后端清掉)→ 清 arrival + waitSecs,防 Map 无界增长 + 防幽灵计时。
 * - 已记录的 id 保留原时刻(快照重推不影响计时,审批真实挂起时长不被重置)。
 */
function _syncArrivalTimes(list: AiToolCallInfo[]) {
  const seen = new Set<string>()
  for (const tc of list) {
    seen.add(tc.id)
    if (!arrivalTimes.has(tc.id)) {
      arrivalTimes.set(tc.id, Date.now())
      waitSecsMap.set(tc.id, 0)
    }
  }
  for (const id of Array.from(arrivalTimes.keys())) {
    if (!seen.has(id)) {
      arrivalTimes.delete(id)
      waitSecsMap.delete(id)
    }
  }
}

let _unlistenUpdate: UnlistenFn | null = null
let _positioned = false
/** 关窗兜底:防止 close() promise 卡住或被拒导致「正在关闭…」永久停留。
 *  close() 在 Tauri 2.x 触发 closeRequested(非强制),某些情况下原生窗口未及时销毁 →
 *  Vue 组件保持挂载 + 空态 hint 永久可见。此处超时强制 destroy() 兜底。 */
let _closeFallbackTimer: ReturnType<typeof setTimeout> | null = null
const CLOSE_TIMEOUT_MS = 1500

/** Esc 键关浮窗(键盘可达性)。模块级命名函数,onBeforeUnmount 据此精确移除同一引用。
 *  仅响应 Esc(KeyDown),其他键透传(不拦截 Tab/Enter 等原生 button 导航)。
 *
 *  7c:数字键快捷审批(对列表顶部第一条生效,多审批场景用户应逐条点或用方向键导航)。
 *  - path 类:1=once / 2=always / 3=deny(deny 仍走二次确认,与按钮同款不可逆守卫)
 *  - risk 类:1=approve / 3=reject(2 仅 path 类有 always,risk 类忽略 '2')
 *  守卫:
 *  - 确认对话框打开时(confirmState.visible)禁用快捷键,避免与确认按钮的 Enter/Esc 冲突
 *  - 处理中(processingId)时忽略,防重复触发
 *  - 输入元素聚焦时不拦截(input/textarea/contenteditable 场景,本浮窗暂无 input 但防御)
 *  - Ctrl/Meta/Alt 组合不拦截(避免劫持浏览器/系统快捷键) */
function _onKeyDown(e: KeyboardEvent) {
  if (e.key === 'Escape') {
    e.preventDefault()
    void onClose()
    return
  }
  // 7c:数字键快捷审批
  if (e.key === '1' || e.key === '2' || e.key === '3') {
    // 组合键(Ctrl+1 等)不拦截;确认对话框打开时不拦截;处理中不拦截
    if (e.ctrlKey || e.metaKey || e.altKey) return
    if (confirmState.visible) return
    if (processingId.value) return
    // 输入元素聚焦时不拦截(防御:本浮窗无 input,但 ConfirmDialog 未来或有)
    const tgt = e.target as HTMLElement | null
    if (tgt && (tgt.tagName === 'INPUT' || tgt.tagName === 'TEXTAREA' || tgt.isContentEditable)) return
    const first = approvals.value[0]
    if (!first) return
    e.preventDefault()
    if (e.key === '1') {
      // 1 = once(path) / approve(risk)
      if (first.kind === 'path') void onApproveOnce(first)
      else void onApprove(first)
    } else if (e.key === '2') {
      // 2 = always(仅 path 类;risk 类无 always,忽略)
      if (first.kind === 'path') void onApproveAlways(first)
    } else {
      // 3 = deny(path) / reject(risk)
      void onReject(first)
    }
  }
}

onMounted(async () => {
  // 首屏定位到屏幕右上角(只定位一次,后续保留用户拖动后位置)
  if (!_positioned) {
    _positioned = true
    try {
      const { currentMonitor } = await import('@tauri-apps/api/window')
      const { LogicalPosition } = await import('@tauri-apps/api/dpi')
      const mon = await currentMonitor()
      if (mon) {
        const win = getCurrentWebviewWindow()
        const w = 380
        // 逻辑坐标(mon.size 是物理像素,scaleFactor 转逻辑);仅用 X 定位右侧,Y 用固定顶部 16px
        const scale = mon.scaleFactor || 1
        const screenW = mon.size.width / scale
        const x = screenW - w - 16
        const y = 16 + (mon.position.y / scale)
        await win.setPosition(new LogicalPosition(x, y))
      }
    } catch { /* 定位失败不影响功能 */ }
  }

  // 监听主窗口推送的 pending 快照
  try {
    _unlistenUpdate = await listen<{ approvals: AiToolCallInfo[] }>(
      ApprovalPopupEvents.EVT_UPDATE,
      (e) => {
        const next = e.payload?.approvals || []
        approvals.value = next
        // H1:同步到达时间(新 id 记时;消失 id 清时 + 清秒数,避免 Map 无界增长)
        _syncArrivalTimes(next)
        // BUG2 修复:主窗口推空快照(所有 pending 已清)→ 立即关浮窗,避免停留在空态转圈
        void maybeAutoClose()
      },
    )
  } catch (e) {
    console.error('[ApprovalPopup] 监听 update 事件失败:', e)
  }

  // 主动拉一次数据(主窗口可能在浮窗 listen 之前 emit,本次拉取兜底)
  try {
    const { emit } = await import('@tauri-apps/api/event')
    // 用 request-reply 太重,直接 emit 一个轻量事件让主窗口重推快照
    await emit('approval-popup-ready', {})
  } catch { /* ignore */ }

  // Esc 快捷键关浮窗(键盘可达性:无需鼠标即可关)。Tab/Enter 原生 button 已支持,不另加。
  // _onKeyDown 是模块级引用,onBeforeUnmount 据此 removeEventListener(保证移除的是同一引用)
  window.addEventListener('keydown', _onKeyDown)

  // H1:启动挂起时长计时(每秒刷新 waitSecsMap)。单 timer 驱动所有 tc,
  // onBeforeUnmount 清理。立即 tick 一次让首屏(若已有 approvals)马上显示。
  _tickWaitSecs()
  if (_waitTimer) clearInterval(_waitTimer)
  _waitTimer = setInterval(_tickWaitSecs, 1000)
})

onBeforeUnmount(() => {
  _unlistenUpdate?.()
  _unlistenUpdate = null
  // 清 Esc listener(防组件卸载后仍监听 window 致内存泄漏/重复触发)
  window.removeEventListener('keydown', _onKeyDown)
  // H1:清挂起计时器(防卸载后仍 tick 致空 Map 操作/内存泄漏)
  if (_waitTimer) { clearInterval(_waitTimer); _waitTimer = null }
  if (_closeFallbackTimer) {
    clearTimeout(_closeFallbackTimer)
    _closeFallbackTimer = null
  }
})

/** 关窗统一实现:close 先行(优雅),超时/失败兜底 destroy()(强制销毁)。
 *  根因修复(BUG3+):原实现仅 await getCurrentWebviewWindow().close(),而 Tauri 2.x 的
 *  close() 触发 closeRequested(非强制),若原生窗口因任意原因未及时销毁(已知偶发),
 *  Vue 组件保持挂载 → 浮窗内空态 hint「正在关闭…」永久可见,UI 卡死。
 *  - 兜底1:close() 失败 → 立即 destroy() 强制销毁
 *  - 兜底2:close() 1.5s 未完成 → destroy() 强制销毁(超时定时器)
 *  - 兜底3:destroy() 也失败 → 控制台报错(不再静默吞掉,便于排查) */
async function closeWithFallback(): Promise<void> {
  const win = getCurrentWebviewWindow()
  // 超时兜底:close 卡住则强制 destroy
  _closeFallbackTimer = setTimeout(() => {
    _closeFallbackTimer = null
    console.warn('[ApprovalPopup] close() 超时,降级 destroy()')
    win.destroy().catch((e: unknown) => {
      console.error('[ApprovalPopup] destroy() 兜底也失败:', e)
    })
  }, CLOSE_TIMEOUT_MS)
  try {
    await win.close()
    // close 成功:取消超时定时器(若已触发则 destroy 已并行无害)
    if (_closeFallbackTimer) {
      clearTimeout(_closeFallbackTimer)
      _closeFallbackTimer = null
    }
  } catch (e) {
    // close 失败:取消超时,降级 destroy()
    if (_closeFallbackTimer) {
      clearTimeout(_closeFallbackTimer)
      _closeFallbackTimer = null
    }
    console.warn('[ApprovalPopup] close() 失败,降级 destroy():', e)
    try {
      await win.destroy()
    } catch (e2) {
      console.error('[ApprovalPopup] destroy() 兜底也失败:', e2)
    }
  }
}

/** path 类:本次会话授权(decision='once') */
async function onApproveOnce(tc: AiToolCallInfo) {
  await runApproval(tc, false, 'once')
}
/** M4:path 类:当前会话授权(decision='session',对齐 ToolCard/DirAuthDialog 四按钮) */
async function onApproveSession(tc: AiToolCallInfo) {
  await runApproval(tc, false, 'session')
}
/** path 类:写持久白名单(decision='always') */
async function onApproveAlways(tc: AiToolCallInfo) {
  await runApproval(tc, false, 'always')
}
/** risk 类:批准(approved=true) */
async function onApprove(tc: AiToolCallInfo) {
  await runApproval(tc, true)
}
/** path 类 deny / risk 类 reject(统一入口,kind 由 dispatchApprovalIPC 据 tc.kind 分派)。
 *  M5:path 类拒绝前二次确认(拒绝不可逆);risk reject 直接执行。 */
async function onReject(tc: AiToolCallInfo) {
  if (tc.kind === 'path') {
    confirmState.msg = t('aiChat.dirAuthDenyConfirm')
    confirmState.tc = tc
    confirmState.visible = true
    return
  }
  await runApproval(tc, false, undefined)
}

/** 审批执行统一壳:防抖(processingId)+ 调共享 IPC 分派 + 乐观移除 + 空列表自动关浮窗。
 *  IPC 分派规则(path→authorizeDir / risk→approve)抽离至 dispatchApprovalIPC,
 *  与 useAiApproval.approveToolCall 同源,消除「同语义两套逻辑」漂移。 */
async function runApproval(tc: AiToolCallInfo, approved: boolean, decision?: 'once' | 'session' | 'always' | 'deny') {
  if (processingId.value === tc.id) return
  processingId.value = tc.id
  try {
    await dispatchApprovalIPC(tc, approved, decision)
    // 乐观移除(主窗口 listener 收后端 emit 后会推新快照覆盖,此乐观仅缩短视觉延迟)
    approvals.value = approvals.value.filter(a => a.id !== tc.id)
    // BUG2 修复:乐观移除后若列表空,立即关浮窗(避免空列表停留 popup-empty 永久转圈态)
    await maybeAutoClose()
  } catch (e) {
    console.error('[ApprovalPopup] 审批失败:', e)
  } finally {
    processingId.value = ''
  }
}

/** 列表清空时自动关浮窗。
 *  场景:用户处理完最后一条审批 / 主窗口推空快照(EVT_UPDATE approvals:[])。
 *  修复 BUG2:原实现空列表渲染 popup-empty + 永久 popup-empty-spinner,用户看到「列表空 + 一直转」
 *  误以为卡死。空 = 完成,应即时关浮窗(不等待主窗口 watch closePopup 的跨窗口往返)。 */
async function maybeAutoClose() {
  if (approvals.value.length > 0) return
  // 先关窗(确保用户立刻看到反馈),再 emit 清主窗口引用(顺序与 onClose 一致)
  // 关窗走 closeWithFallback 兜底(close 超时/失败 → destroy 强制销毁),防「正在关闭…」永久
  await closeWithFallback()
  try {
    const { emit } = await import('@tauri-apps/api/event')
    await emit(ApprovalPopupEvents.EVT_CLOSED, {})
  } catch (e) {
    console.warn('[ApprovalPopup] emit EVT_CLOSED 失败:', e)
  }
}

/** 点击「打开审批面板」:emit 主窗口聚焦 + togglePanel */
async function onActivate() {
  try {
    const { emit } = await import('@tauri-apps/api/event')
    await emit(ApprovalPopupEvents.EVT_ACTIVATE, {})
  } catch { /* ignore */ }
}

/** 关闭浮窗(用户主动关 → emit closed 让主窗口清引用,允许下次有新审批再弹)
 *  修复 BUG3:原顺序 emit→close,若 emit 跨窗口分发阻塞/异常(close 永不执行)→ 关闭按钮「无效」。
 *  改为先 close(即时反馈,core:window:allow-close 已授权),再 emit 清主窗口引用;
 *  即便 emit 失败,主窗口 win.once('tauri://destroyed') 也会清 _popupWin(双保险)。 */
async function onClose() {
  // 关窗走 closeWithFallback 兜底(close 超时/失败 → destroy 强制销毁)
  await closeWithFallback()
  try {
    const { emit } = await import('@tauri-apps/api/event')
    await emit(ApprovalPopupEvents.EVT_CLOSED, {})
  } catch (e) {
    console.warn('[ApprovalPopup] emit EVT_CLOSED 失败:', e)
  }
}

// 拖动:JS API 主动驱动 + data-tauri-drag-region 双保险。
// 根因:Tauri 2.x data-tauri-drag-region 默认不冒泡到子元素(svg/span/title/close 按钮"吃掉"
// 鼠标事件),用户拖到子元素上拖不动(只有 header 空白缝隙可拖)。JS API startDragging() 绕过此限制——
// 主动 mousedown 调用,不依赖属性冒泡;data-tauri-drag-region 保留作 fallback(若 JS API 失败仍可拖)。
// button 区域不拖(closest('button') 跳过),保证审批按钮点击优先(mousedown 冒泡到 header 时不触发拖动)。
async function onDragStart(e: MouseEvent) {
  // 仅左键触发拖动(右键/中键忽略,防误触);button 元素的 mousedown 不拖(让点击优先)
  if (e.button !== 0) return
  if ((e.target as HTMLElement)?.closest('button')) return
  isDragging.value = true
  try {
    const win = getCurrentWebviewWindow()
    await win.startDragging()
  } catch (err) {
    // JS API 失败时降级:data-tauri-drag-region 属性仍在 header 上,原生守卫接管
    console.warn('[ApprovalPopup] startDragging() 失败,降级到 data-tauri-drag-region:', err)
  }
}
function onDragEnd() { isDragging.value = false }
</script>

<style scoped>
/* 浮窗根:无边框 + 紫色主题阴影 */
.popup-shell {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  background: var(--df-bg-card);
  border: 0.5px solid var(--df-accent);
  border-left: 3px solid var(--df-accent);
  border-radius: var(--df-radius-md);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(123, 111, 240, 0.2);
  overflow: hidden;
  user-select: none;
}
.popup-shell[data-dragging="true"] {
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6), 0 0 0 2px var(--df-accent);
}

/* 顶部拖动条 */
.popup-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: linear-gradient(135deg, var(--df-accent-bg), transparent);
  border-bottom: 0.5px solid var(--df-border, rgba(123, 111, 240, 0.2));
  cursor: move;
}
.popup-header-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--df-accent-bg);
  color: var(--df-accent);
  flex-shrink: 0;
}
.popup-header-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--df-danger);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  border: 1.5px solid var(--df-bg-card);
}
.popup-header-title {
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  color: var(--df-text);
}
.popup-header-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: var(--df-radius-sm);
  background: transparent;
  color: var(--df-text-dim);
  cursor: pointer;
  flex-shrink: 0;
  transition: all 0.15s var(--df-ease);
}
.popup-header-close:hover {
  background: var(--df-sidebar-hover);
  color: var(--df-text);
}

/* 列表区(可滚动) */
.popup-body {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.popup-empty {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-height: 80px;
  padding: 16px;
}
.popup-empty-hint {
  font-size: 11px;
  color: var(--df-text-dim);
  line-height: 1.4;
}

/* 单条审批项 */
.popup-item {
  padding: 8px 10px;
  border-radius: var(--df-radius-sm);
  background: var(--df-sidebar-hover);
  border-left: 2px solid var(--df-accent);
  transition: all 0.15s var(--df-ease);
}
.popup-item--path { border-left-color: var(--df-accent); }
.popup-item--risk { border-left-color: var(--df-warning, #f0a020); }
.popup-item:hover {
  background: var(--df-sidebar-active, rgba(123, 111, 240, 0.12));
}

.popup-item-header {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}
.popup-item-name {
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  color: var(--df-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.popup-item-tag {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 8px;
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.popup-item-tag--path {
  background: var(--df-accent-bg);
  color: var(--df-accent);
}
.popup-item-tag--risk {
  background: rgba(240, 160, 32, 0.18);
  color: var(--df-warning, #f0a020);
}

.popup-item-reason {
  font-size: 11px;
  color: var(--df-text-dim);
  line-height: 1.4;
  margin-bottom: 4px;
  word-break: break-word;
  white-space: normal;
}
.popup-item-dir {
  font-size: 10px;
  color: var(--df-text-dim);
  margin-bottom: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* H1:审批挂起时长提示(对齐 ToolCard .ai-tool-wait-time)。
   >2min 橙色脉冲 / >5min 红色更急脉冲;pulseGlow 复用 styles/global.css 全局 keyframes。
   用户主要从浮窗审批,后端 APPROVAL_TIMEOUT_MS 5min 静默拒绝前给可见预警。 */
.popup-item-wait {
  padding: 2px 8px;
  margin-bottom: 6px;
  font-size: 10px;
  font-family: var(--df-font-mono);
  color: var(--df-text-dim, #888);
  border-radius: var(--df-radius-sm, 4px);
  background: var(--df-bg-card, transparent);
}
.popup-item-wait.is-long {
  color: var(--df-warning, #d97706);
  animation: pulseGlow 1.5s ease-in-out infinite;
}
.popup-item-wait.is-urgent {
  color: var(--df-danger, #e5484d);
  animation: pulseGlow 1s ease-in-out infinite;
}

.popup-item-actions {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.popup-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 5px 8px;
  border: none;
  border-radius: var(--df-radius-sm);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s var(--df-ease);
}
.popup-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
/* 7c:数字键快捷键标记(1/2/3 对应 once/approve、always、deny/reject)。
   小尺寸单字符徽章,提示键盘可达性;继承父按钮对比色但降透明度作辅助信息层。 */
.popup-btn-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 13px;
  height: 13px;
  padding: 0 3px;
  margin-left: 1px;
  border-radius: var(--df-radius-xs, 3px);
  font-family: var(--df-font-mono);
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  background: rgba(0, 0, 0, 0.18);
  opacity: 0.7;
}
.popup-btn--approve {
  background: var(--df-accent);
  color: #fff;
}
.popup-btn--approve:hover:not(:disabled) {
  background: var(--df-accent-hover, #6558d8);
}
/* M4:session 按钮(弱填充态,介于 approve 强填充与 always 描边之间,视觉层级 once>session>always) */
.popup-btn--session {
  background: var(--df-accent-bg);
  color: var(--df-accent);
}
.popup-btn--session:hover:not(:disabled) {
  background: color-mix(in srgb, var(--df-accent) 30%, transparent);
}
.popup-btn--always {
  background: var(--df-accent-bg);
  color: var(--df-accent);
  border: 0.5px solid var(--df-accent);
}
.popup-btn--always:hover:not(:disabled) {
  background: var(--df-accent);
  color: #fff;
}
.popup-btn--reject {
  background: var(--df-sidebar-hover);
  color: var(--df-text-dim);
  border: 0.5px solid var(--df-border, rgba(255, 255, 255, 0.1));
}
.popup-btn--reject:hover:not(:disabled) {
  background: rgba(255, 80, 80, 0.15);
  color: var(--df-danger, #e5484d);
}
.popup-btn-spinner {
  width: 10px;
  height: 10px;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: df-spin 0.6s linear infinite;
  display: inline-block;
}

/* 底部:打开审批面板 */
.popup-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px;
  border: none;
  border-top: 0.5px solid var(--df-border, rgba(123, 111, 240, 0.2));
  background: var(--df-accent-bg);
  color: var(--df-accent);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s var(--df-ease);
}
.popup-footer:hover {
  background: var(--df-accent);
  color: #fff;
}

@keyframes df-spin {
  to { transform: rotate(360deg); }
}
</style>
