<template>
  <AppLayout :is-detached="isDetached">
    <!-- 主窗口:页面内容(router-view + 页面切换过渡) -->
    <!-- ErrorBoundary 包在 transition 外层兜底:transition 的直接子节点须是真实元素根,
         若包 ErrorBoundary(slot 透传非元素根)会触发「non-element root」警告且 mode="out-in"
         过渡卡死 → 路由切换时旧页不卸载/新页不挂载 → 主菜单点了能选中但打不开内容。 -->
    <router-view v-slot="{ Component }">
      <ErrorBoundary :key="route.path">
        <transition name="page" mode="out-in">
          <component :is="Component" />
        </transition>
      </ErrorBoundary>
    </router-view>

    <!-- 分离窗口:纯内容 -->
    <template #detached>
      <router-view v-slot="{ Component }">
        <ErrorBoundary :key="route.path">
          <transition name="page" mode="out-in">
            <component :is="Component" />
          </transition>
        </ErrorBoundary>
      </router-view>
    </template>

    <!-- AI Chat Panel 内容 -->
    <template #ai-panel>
      <AiChat />
    </template>
  </AppLayout>

  <!-- 全局错误 toast(根级,主窗口与分离窗口共用)-->
  <transition name="toast">
    <div v-if="errorMsg" class="df-toast">⚠ {{ errorMsg }}</div>
  </transition>

  <!-- 审批待办浮层(应用内右上角,窗口聚焦/可见时作快速提示)。
       仅在主窗口与 ai-detached 窗口挂载(file-explorer / approval-popup 自身不挂)。
       应用失焦/最小化时的系统级提示由 useApprovalPopup 启动的独立浮窗(approval-popup)负责。 -->
  <ApprovalOverlay v-if="showApprovalOverlay" @activate="activateAiPanel" />
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { useAiStore } from './stores/ai'
import { useKnowledgeStore } from './stores/knowledge'
import { useProjectStore } from './stores/project'
import { useAppSettingsStore } from './stores/appSettings'
import i18n from './i18n'
import AiChat from './components/AiChat.vue'
import ApprovalOverlay from './components/ai/ApprovalOverlay.vue'
import ErrorBoundary from './components/ErrorBoundary.vue'
import AppLayout from './components/layout/AppLayout.vue'
import { startApprovalPopup } from '@/composables/ai/useApprovalPopup'
import { aiApi } from '@/api'

const route = useRoute()
const isDetached = computed(() => route.path === '/ai-detached' || route.path === '/file-explorer-detached' || route.path === '/approval-popup')
const aiStore = useAiStore()
const appSettings = useAppSettingsStore()

// 审批浮层仅在主窗口与 ai-detached 窗口挂载(不在 file-explorer / approval-popup 等其他分离窗口弹)
// approval-popup 是系统级浮窗本身,内部已显式渲染 pending 列表,再挂 ApprovalOverlay 会重复
const showApprovalOverlay = computed(() =>
  route.path !== '/file-explorer-detached' && route.path !== '/approval-popup'
)

/** 用户点击审批浮层 → 激活 AI 面板/分离窗口。
 *  主窗口:打开 AI 面板;分离窗口:提升焦点(窗口本身 alwaysOnTop 已置顶)。 */
async function activateAiPanel() {
  if (isDetached.value) {
    try {
      const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
      await getCurrentWebviewWindow().setFocus()
    } catch { /* 忽略 */ }
  } else {
    if (!aiStore.state.panelOpen) aiStore.togglePanel()
  }
}

// ── 全局错误 toast(消费 projectStore.error,所有 action 失败统一反馈)──
const projectStore = useProjectStore()
const errorMsg = ref('')
let errorTimer: ReturnType<typeof setTimeout> | null = null
watch(() => projectStore.error, (err) => {
  if (!err) return
  errorMsg.value = err
  projectStore.clearError() // 重置,允许连续同值错误再次触发
  if (errorTimer) clearTimeout(errorTimer)
  errorTimer = setTimeout(() => { errorMsg.value = '' }, 3500)
})

// 点击菜单自动退出最大化 → 切为分栏模式
watch(() => route.path, (path) => {
  if (!['/', '/ai-home', '/ai-detached'].includes(path) && aiStore.state.maximized) {
    aiStore.state.maximized = false
  }
})

// 主题初始化
function initTheme() {
  const saved = appSettings.get<string>('df-theme', 'dark')
  if (saved === 'light') {
    document.documentElement.setAttribute('data-theme', 'light')
  } else if (saved === 'system') {
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
    if (!prefersDark) document.documentElement.setAttribute('data-theme', 'light')
  }
}

// Ctrl+I / Cmd+I 三态循环:关闭 → 分栏打开 → 最大化 → 关闭(cyclePanelMode)
// UI 按钮/审批激活仍用 togglePanel 二态开关,语义互不干扰
function handleKeydown(e: KeyboardEvent) {
  if ((e.ctrlKey || e.metaKey) && e.key === 'i') {
    e.preventDefault()
    aiStore.cyclePanelMode()
  }
}

// 系统级审批浮窗(独立窗口):仅主窗口启动(ai-detached/approval-popup 自身不启动,避免重复 watch)
let _stopApprovalPopup: (() => void) | null = null
let _unlistenPopupReady: (() => void) | null = null
async function activateForPopup() {
  // 浮窗「打开审批面板」按钮 → 聚焦主窗口 + 打开 AI 面板/激活分离窗口
  // 修复 BUG1:setFocus 先行(已授权,失焦场景有效),show 兜底(需 core:window:allow-show,
  // capabilities/default.json 已补)。原顺序 show→setFocus,show reject 直接吞掉 setFocus,
  // 主窗口最小化/隐藏时 show 失败 → 用户看到「点了没反应」。
  try {
    const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
    const mainWin = await WebviewWindow.getByLabel('main')
    if (mainWin) {
      // setFocus 先行(对未聚焦但可见的窗口即可恢复焦点);show 兜底取消最小化/隐藏
      try { await mainWin.setFocus() } catch { /* setFocus 失败不阻塞 show */ }
      try { await mainWin.show() } catch { /* show 未授权或不可用,忽略(setFocus 已尽力) */ }
    }
  } catch { /* 忽略 */ }
  if (!aiStore.state.detached && !aiStore.state.panelOpen) {
    aiStore.togglePanel()
  }
}

onMounted(async () => {
  // 1. 先把 SQLite 全表 KV 填进 appSettings 缓存(后续 get/useSetting 才能读到真实值)
  await appSettings.loadAll()
  // 2. 一次性迁移:把旧 localStorage 的 11 个 key 导入 SQLite(仅首次跑,之后 localStorage 清净)
  await migrateLegacyLocalStorage()
  // 3. 缓存已就绪,初始化主题/locale
  initTheme()
  // F-260618-23: 根 onMounted 恢复 Agentic 轮次/重试到后端(AtomicUsize 纯内存,重启回默认)。
  // GeneralPanel.vue onMounted 仅 Settings 页挂载,用户直接进 AI Chat 不同步 → 后端默认 10 与前端显示持久值不一致。
  // clamp 与 GeneralPanel 语义对齐(0=不限透传,1-50 clamp;双保险,IPC 幂等值相同无害)
  // 包 try/catch 防 IPC 失败 reject onMounted(对齐 dataChangedListener 模式)
  try {
    const iter = appSettings.get<number>('df-ai-agent-max-iterations', 10)
    const iterClamped = iter === 0 ? 0 : Math.min(50, Math.max(1, iter ?? 10))
    const retry = appSettings.get<number>('df-ai-agent-max-retries', 3)
    const retryClamped = Math.min(10, Math.max(0, retry ?? 3))
    await aiApi.setAgentMaxIterations(iterClamped)
    await aiApi.setAgentMaxRetries(retryClamped)
    // 设置走查-2026-06-21:并发配置同属后端 AtomicUsize 纯内存,重启回默认。原 onMounted
    // 漏同步 global/per-conv-concurrency → 用户改并发→重启→不进 Settings 直接用→后端默认 3/2
    // 前后端不一致。补齐对齐 iter/retry 恢复模式。
    const globalConv = appSettings.get<number>('df-llm-global-concurrency', 3)
    const perConvConv = appSettings.get<number>('df-llm-per-conv-concurrency', 2)
    await aiApi.setConcurrencyConfig(Math.max(1, globalConv ?? 3), Math.max(1, perConvConv ?? 2))
  } catch (e) {
    console.error('恢复 Agentic 轮次/重试/并发配置失败:', e)
  }
  const savedLang = appSettings.get<string | null>('df-language', null)
  if (savedLang) {
    i18n.global.locale.value = savedLang as 'zh-CN' | 'en'
  }
  window.addEventListener('keydown', handleKeydown)
  // 加载知识库候选数(供侧栏 knowledge badge 显示)
  knowledgeStore.loadCandidates()
  // AR-11:数据变更联动监听(AI 工具 create/update/delete 后端 emit df-data-changed → store listen 刷新对应列表,全局挂载因用户可在任意页)
  // CR-260615-21:包 try/catch 防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效
  try {
    await projectStore.startDataChangedListener()
  } catch (e) {
    console.error('启动数据变更监听失败:', e)
  }
  // 系统级审批浮窗:仅主窗口启动(approval-popup 自身路由不启动,避免重复 watch)。
  // 设计见 useApprovalPopup.ts:主窗口 watch pendingApprovals → 弹/更新/关浮窗(单一真相源)。
  if (route.path !== '/approval-popup') {
    _stopApprovalPopup = startApprovalPopup(
      () => aiStore.state.pendingApprovals,
      activateForPopup,
    )
    // 浮窗 webview 挂载后会 emit 'approval-popup-ready' 请求初始快照(防主窗口在浮窗 listen 前
    // emit 丢失)。此处 listen 后立即重推当前 pendingApprovals。
    try {
      const { listen } = await import('@tauri-apps/api/event')
      const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
      _unlistenPopupReady = await listen('approval-popup-ready', async () => {
        const popup = await WebviewWindow.getByLabel('approval-popup')
        if (popup) {
          await popup.emit('approval-popup-update', { approvals: aiStore.state.pendingApprovals })
        }
      })
    } catch (e) {
      console.error('[ApprovalPopup] ready 监听失败:', e)
    }
  }
})

/// 一次性迁移:遍历遗留的 localStorage key,若有值则导入 appSettings(SQLite),
/// 然后删除 localStorage 中的旧 key。导入时按各 key 原存格式解析成 JS 值再交给 set
/// (set 内部会 JSON.stringify)。之后 localStorage 只剩 df-ai-gen / df-ai-text 流式快照。
const LEGACY_KEYS_RAW = new Set(['df-theme', 'df-language', 'df-ai-language']) // 裸字符串
const LEGACY_KEYS_JSON = new Set([ // 已 JSON.stringify(裸值或对象)
  'df-ai-width', 'df-ai-ui', 'df-show-token-usage',
  'df-llm-global-concurrency', 'df-llm-per-conv-concurrency',
  'df-ai-active-conv', 'df-connections',
])
function parseLegacy(key: string, raw: string): unknown {
  if (LEGACY_KEYS_RAW.has(key)) return raw // 裸字符串,如 'dark' / 'zh-CN'
  // JSON 类:有的存的是 JSON.stringify('zh-CN') → '"zh-CN"'(虽不在本集合,保险起见 parse)
  try {
    return JSON.parse(raw)
  } catch {
    return raw
  }
}
async function migrateLegacyLocalStorage(): Promise<void> {
  const allKeys = [...LEGACY_KEYS_RAW, ...LEGACY_KEYS_JSON]
  for (const key of allKeys) {
    const raw = localStorage.getItem(key)
    if (raw === null || raw === '') continue
    try {
      await appSettings.set(key, parseLegacy(key, raw))
    } catch (e) {
      console.error(`[migrate] 导入 ${key} 失败:`, e)
    }
    localStorage.removeItem(key)
  }
}

// 知识库 store(侧栏 badge 计数在 AppSidebar 内消费,此处仅确保 store 实例化供 onMounted loadCandidates)
const knowledgeStore = useKnowledgeStore()

onUnmounted(() => {
  window.removeEventListener('keydown', handleKeydown)
  projectStore.stopDataChangedListener?.()
  _stopApprovalPopup?.()
  _unlistenPopupReady?.()
  _stopApprovalPopup = null
  _unlistenPopupReady = null
})
</script>

<style scoped>
/* — Page Transition — */
.page-enter-active { transition: opacity 0.2s var(--df-ease), transform 0.2s var(--df-ease); }
.page-leave-active { transition: opacity 0.12s var(--df-ease), transform 0.12s var(--df-ease); }
.page-enter-from { opacity: 0; transform: translateY(6px); }
.page-leave-to { opacity: 0; transform: translateY(-4px); }

/* ═══ 全局错误 toast ═══ */
.df-toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  background: var(--df-danger); color: #fff;
  padding: 10px 18px; border-radius: var(--df-radius-sm);
  font-size: 13px; z-index: 9999; max-width: 90vw;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.toast-enter-active, .toast-leave-active { transition: all 0.25s var(--df-ease); }
.toast-enter-from, .toast-leave-to { opacity: 0; transform: translate(-50%, 10px); }
</style>
