//! 窗口分离模式 — detach/reattach/resumeInDetached/closeDetachedWindow/dock/syncToMain/startFollowMain/stopFollowMain
//!
//! 模块级私有(不进 reactive):
//! - _unlistenMove / _unlistenResize: 主窗口 move/resize 跟随的 unlistener
//!
//! 耦合:
//! - resumeInDetached / restoreGeneratingState 调 aiShared.nextMsgId 占位 ai 气泡 +
//!   invoke('ai_is_generating') 核对后端真值(主面板刷新 + 分离窗口恢复共用同款逻辑)
//! - reattachPanel/detachPanel 调 useAiPanel.persistUiState

import { invoke } from '@tauri-apps/api/core'
import { state } from '@/stores/ai'
import { nextMsgId, getConvState, convStates } from './aiShared'
import type { MessageId } from '@/api/types'
import { setStreaming } from './streamingGuard'
import { persistUiState } from './useAiPanel'
import { stopListener } from './useAiEvents'

// ── 主窗口事件跟随 unlistener ──
let _unlistenMove: (() => void) | null = null
let _unlistenResize: (() => void) | null = null

/**
 * F-09 阶段4:多会话独立窗口的 per-conv key/label 生成器。
 *
 * 旧实现用单 label 'ai-detached' + 单 key df-ai-gen/df-ai-text,多会话并发下冲突
 * (会话 A 的窗口快照被会话 B 覆盖,getByLabel 只能存一个窗口)。现按 convId 命名空间
 * 隔离:每个会话独立窗口 + 独立 localStorage 快照,互不串扰。
 * 单会话场景:convId 固定,key/label 退化为单值,行为等价。
 */
const DETACHED_LABEL_PREFIX = 'ai-detached-'
const GEN_KEY_PREFIX = 'df-ai-gen-'
const TEXT_KEY_PREFIX = 'df-ai-text-'

function detachedLabel(convId: string): string {
  return DETACHED_LABEL_PREFIX + convId
}
function genKey(convId: string): string {
  return GEN_KEY_PREFIX + convId
}
function textKey(convId: string): string {
  return TEXT_KEY_PREFIX + convId
}

/** 分离 AI 面板到独立窗口(若该会话窗口已存在则聚焦);快照当前生成态供分离窗口接管 */
async function detachPanel(convId?: string) {
  const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
  const targetConv = convId || state.activeConversationId || ''
  const label = targetConv ? detachedLabel(targetConv) : 'ai-detached'
  const existing = await WebviewWindow.getByLabel(label)
  if (existing) {
    await existing.setFocus()
    return
  }
  // 快照当前生成态,供分离窗口接管(保持正在进行的对话)
  // F-09:用 per-conv key 写入(多会话各自快照互不覆盖)
  // 批4 双轨收口:读 getConvState(enum 真相源)派生,替代旧 generatingConvs.has。
  // 桥接语义:has(convId)=true 等价于 conv_state∈{generating,stopping,compressed}(非终止三态)。
  if (state.streaming && targetConv) {
    const cs = getConvState(targetConv)
    const gen = cs === 'generating' || cs === 'stopping' || cs === 'compressed'
    if (gen) {
      localStorage.setItem(genKey(targetConv), targetConv)
      localStorage.setItem(textKey(targetConv), state.currentText)
    }
  }
  // 主窗口 state 与分离窗口 state 独立(各自 webview 独立 JS context,
  // stores/ai.ts 模块级单例仅在同 webview 内共享),清主窗口 state 不影响分离窗口生成态。
  // 分离窗口接管生成后,主窗口不应再持该会话生成态残留(防重开面板时 UI 显生成中幽灵/进度条)。
  // F-09:仅从 convStates 移除该 conv(其他会话可能仍在生成),不全局清空。
  // watchdog 在主窗口 AiChat 卸载(App.vue v-if detached)→ stopListener → clearStreamWatchdog 时已清,
  // 此处仅清内存 state 视觉残留;localStorage 快照保留供 resumeInDetached 恢复。
  setStreaming(false, { convId: targetConv || null, reason: 'detachPanel-clear-main' })
  if (targetConv) convStates.delete(targetConv)
  const sep = targetConv ? `?conv=${encodeURIComponent(targetConv)}` : ''
  const url = window.location.origin + window.location.pathname + '#/ai-detached' + sep
  const win = new WebviewWindow(label, {
    url,
    title: 'DevFlow AI',
    width: 600, // U-260618: 加宽(原 520 偏窄),与吸附态 AI_DOCK_WIDTH 一致
    height: 700,
    minWidth: 360,
    minHeight: 400,
    center: true,
    decorations: true,
    alwaysOnTop: true, // 分离窗口默认置顶(AI 助手常态:用户在主窗口工作时希望 AI 辅助窗口可见)
  })
  win.once('tauri://created', () => {
    state.panelOpen = false
    persistUiState()
  })
  win.once('tauri://error', (e) => {
    console.error('[AI] 窗口创建失败:', e)
    state.detached = false
  })
  win.once('tauri://destroyed', () => {
    state.detached = false
    state.docked = false
    // R3: X 关分离窗口后主面板自动恢复,无需手动 Ctrl+I
    state.panelOpen = true
    stopFollowMain()
  })
  state.detached = true
  // 时序加固:detached 置 true 后立即停主窗口 listener,不依赖 v-if → AiChat onBeforeUnmount 的
  // 异步传播。消除 detached=true 到 AiChat 实际卸载的窗口期(reattach 重叠期/HMR/极速 mount-unmount
  // 竞态下,onBeforeUnmount 可能延迟,旧 listener 残留会与分离窗口新 listener 并存竞态)。
  // stopListener 同步释放后端 listener + 清看门狗,分离窗口 resumeInDetached 起自己的 startListener。
  stopListener()
}

/** 关闭分离窗口并回到内嵌面板(主窗口侧调用)。
 *  F-09:多会话并发下可能存在多个 detached 窗口,全部关闭并清各自 per-conv 快照。 */
async function reattachPanel() {
  stopFollowMain()
  const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
  // 关闭所有 ai-detached-* 前缀的窗口(含旧版单 label 'ai-detached' 兼容)
  const all = await WebviewWindow.getAll()
  for (const w of all) {
    const lbl = w.label
    if (lbl === 'ai-detached' || lbl.startsWith(DETACHED_LABEL_PREFIX)) {
      try { await w.close() } catch { /* 忽略单个窗口关闭失败 */ }
    }
  }
  state.detached = false
  state.docked = false
  state.panelOpen = true
  persistUiState()
  // 时序加固:reattach 关闭分离窗口后,显式停分离窗口残留 listener(分离窗口 close 异步,
  // onBeforeUnmount stopListener 可能延迟),防主面板重新挂载 startListener 时与旧 listener 重叠竞态。
  stopListener()
  // F-09:清所有 per-conv 快照(分离窗口已全关,快照无消费方)
  cleanupAllDetachedSnapshots()
}

/** 清理所有 df-ai-gen-* / df-ai-text-* 快照(分离窗口全关 / 整轮收尾时调用) */
function cleanupAllDetachedSnapshots(): void {
  const keysToRemove: string[] = []
  for (let i = localStorage.length - 1; i >= 0; i--) {
    const k = localStorage.key(i)
    if (!k) continue
    if (k.startsWith(GEN_KEY_PREFIX) || k.startsWith(TEXT_KEY_PREFIX) || k === 'df-ai-gen' || k === 'df-ai-text') {
      keysToRemove.push(k)
    }
  }
  for (const k of keysToRemove) localStorage.removeItem(k)
}

/**
 * 重建生成中态(分离窗口 resumeInDetached 与主面板 onMounted 恢复共用)。
 *
 * 读 localStorage df-ai-gen-${convId}/df-ai-text-${convId}(F-09 per-conv key),核对后端 ai_is_generating 真值后重建:
 * 1. state.currentText = df-ai-text-${convId}(已生成文本)
 * 2. push 占位 assistant 气泡(content='')
 * 3. state.streaming = true
 * 4. convStates.set(df-ai-gen, 'generating')(批4 双轨收口:替代旧 generatingConvs.add)
 *
 * 幂等:若 state.streaming 已是 true(已恢复过/正在生成),直接返回不重复 push。
 * 这对主面板刷新场景至关重要:detachPanel 时主窗口 state 已被清 streaming=false
 * (见 detachPanel 注释),分离窗口用各自 webview JS context 独立 state;主面板刷新后
 * state 从单例初值 streaming=false 起步,本函数安全重建。
 *
 * R4: 先核对 ai_is_generating 真值才重建,避免 localStorage 残留导致假气泡 +
 * 130s 后 watchdog 假超时。后端 false 时清残留快照返回。
 *
 * @returns true=已重建;false=未恢复(无快照/后端未生成/核对失败/已生成中)
 */
export async function restoreGeneratingState(opts?: { fromMainPanel?: boolean; convId?: string }): Promise<boolean> {
  // 幂等:已生成中态不再重建(防主面板 + 分离窗口共享 state 时双重恢复;
  // 主面板与分离窗口 state 各自独立,但稳妥起见仍守卫)
  if (state.streaming) return false
  const fromMainPanel = opts?.fromMainPanel ?? false
  // 主面板刷新场景:detachPanel 未触发 → df-ai-gen-* 不存在。
  // 此时用 state.activeConversationId 作为生成中会话(刷新前 loadConversations 已恢复),
  // 无 df-ai-text-* 快照 → currentText 从空开始(刷新前已生成部分丢失,刷新固有代价;
  // 后端后续 AiTextDelta 仍会追加,核心是恢复 streaming 态 + 占位气泡消除假死)。
  // 分离窗口场景:读 detachPanel 写入的 df-ai-gen-${convId} 快照(F-09 per-conv key)。
  const gen = fromMainPanel
    ? state.activeConversationId
    : (opts?.convId && localStorage.getItem(genKey(opts.convId))) || localStorage.getItem('df-ai-gen')
  if (!gen) return false
  // 核对后端 ai_is_generating 真值
  // F-260616-09 B 批4(决策 e):传 gen(目标 conv)精确查,不读全局单值。
  let backendGenerating = false
  try {
    backendGenerating = await invoke<boolean>('ai_is_generating', { conversationId: gen })
  } catch (e) {
    console.error('[AI] restoreGeneratingState: 核对 ai_is_generating 失败,跳过重建生成态', e)
    return false
  }
  if (!backendGenerating) {
    // 后端已不再生成,清残留快照(仅分离窗口快照路径),不重建
    if (!fromMainPanel) {
      if (opts?.convId) {
        localStorage.removeItem(genKey(opts.convId))
        localStorage.removeItem(textKey(opts.convId))
      }
      localStorage.removeItem('df-ai-gen')
      localStorage.removeItem('df-ai-text')
    }
    return false
  }
  // 恢复已生成文本并补占位,后续 delta 继续追加
  // 主面板无 df-ai-text-* 快照,保持 currentText 当前值(空),仅靠占位气泡 + streaming 态接收后续 delta
  if (!fromMainPanel) {
    state.currentText = (opts?.convId && localStorage.getItem(textKey(opts.convId))) || localStorage.getItem('df-ai-text') || ''
  }
  state.messages.push({
    id: `ai-${nextMsgId()}` as MessageId,
    role: 'assistant',
    content: '',
    timestamp: Date.now(),
  })
  setStreaming(true, { convId: gen, reason: 'restoreGeneratingState' })
  // 批4 双轨收口:恢复生成中态写 convStates(enum 真相源),替代旧 generatingConvs.add。
  // setStreaming 联动已退役(批4),streaming 单值与 convStates 项并行写无冲突。
  convStates.set(gen, 'generating')
  return true
}

/** 分离窗口挂载时接管主窗口当前对话(含生成中态) */
async function resumeInDetached(convId: string | null) {
  // R5: 不自动 switchConversation —— 分离窗口可看历史列表,但不主动覆盖主窗口 activeConversationId
  //     (避免主窗口期间发消息落错会话)。若需切换会话,由用户在分离窗口显式操作触发。
  // F-09: 透传 convId 给 restoreGeneratingState,定位正确的 per-conv 快照 key。
  await restoreGeneratingState({ convId: convId || undefined })
}

/** 关闭分离窗口自身(分离窗口侧调用)。
 *  F-09:从当前 webview 的 label 反推 convId,清自己的 per-conv 快照。 */
async function closeDetachedWindow() {
  state.docked = false
  stopFollowMain()
  // 时序加固:关闭分离窗口前先停 listener,防 close 异步传播期间 onBeforeUnmount 延迟、
  // 主面板重挂载 startListener 与旧分离窗口 listener 短暂并存(极速 mount-unmount/HMR 竞态)。
  stopListener()
  const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
  const currentWin = getCurrentWebviewWindow()
  const lbl = currentWin.label
  // per-conv label 'ai-detached-<convId>' → 清对应快照;旧 label 'ai-detached' → 清遗留单 key
  if (lbl.startsWith(DETACHED_LABEL_PREFIX)) {
    const convId = lbl.slice(DETACHED_LABEL_PREFIX.length)
    localStorage.removeItem(genKey(convId))
    localStorage.removeItem(textKey(convId))
  } else {
    localStorage.removeItem('df-ai-gen')
    localStorage.removeItem('df-ai-text')
  }
  await currentWin.close()
}

/** 吸附/取消吸附：AI 窗口跟随主窗口右侧 */
async function dockDetached() {
  // 取消吸附
  if (state.docked) {
    state.docked = false
    stopFollowMain()
    return
  }

  try {
    await syncToMain()
    state.docked = true
    startFollowMain()
  } catch (e) {
    console.error('[AI] 吸附失败:', e)
  }
}

/** 吸附态 AI 窗口宽度/间隙常量(U-260618) */
const AI_DOCK_WIDTH = 600
const AI_DOCK_GAP = 4
// 防 syncToMain 改主窗口 size 触发 onResized 递归回调
let _isSyncing = false
// 拖动/缩放防抖:用户拖动主窗口期间 onMoved/onResized 高频触发,
// 直接 sync 会让 setSize/setPosition 排队 → AI 窗口抖动 + 性能浪费。
// 防后 150ms 才执行,用户停下才同步(手感顺 + 不会重重复复中中间态同步)。
let _syncTimer: ReturnType<typeof setTimeout> | null = null
const SYNC_DEBOUNCE_MS = 150

/** 同步 AI 窗口位置到主窗口右侧(保留 GAP 间隙)。
 *
 * 设计原则(本次优化重点):
 * 1. 不反向改主窗口 size——原实现在拖动主窗口时 setSize 主窗口,使主窗口在拖动过程中自己也在变,
 *    拖动体验极差。现改为:主窗口是真相源,AI 窗口跟随到主窗口右侧;若右侧空间不够,AI 窗口本身让位
 *    (往左偏移或缩宽),不动主窗口。
 * 2. 不动主窗口最大化状态——原实现强制 unmaximize 主窗口,破坏用户最大化习惯。
 *    现改为:主窗口最大化时仅同步高度,AI 窗口贴右侧屏幕边显示。
 * 3. 仅同步位置与高度(跟随主窗口高度),宽度保持 AI_DOCK_WIDTH 不变(避免宽度反复跳)。
 */
async function syncToMain() {
  if (_isSyncing) return
  _isSyncing = true
  try {
    const { WebviewWindow, getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
    const { PhysicalPosition, PhysicalSize } = await import('@tauri-apps/api/dpi')
    const { currentMonitor } = await import('@tauri-apps/api/window')
    const mainWin = await WebviewWindow.getByLabel('main')
    const aiWin = getCurrentWebviewWindow()
    if (!mainWin) return

    const monitor = await currentMonitor()
    const screenWidth = monitor?.size.width ?? 0

    const pos = await mainWin.outerPosition()
    const size = await mainWin.innerSize()
    const maximized = await mainWin.isMaximized()

    // 计算目标位置(默认贴主窗口右侧 + GAP)
    let targetX = pos.x + size.width + AI_DOCK_GAP
    let targetY = pos.y
    let targetHeight = size.height
    let targetWidth = AI_DOCK_WIDTH

    if (maximized) {
      // 主窗口最大化时:不动主窗口。AI 贴屏幕右侧(主窗口全屏时右侧没空间,AI 覆盖在主窗口上)。
      // 保持 AI 默认高度不变(跟随屏幕高度易越界),高度不动。
      if (screenWidth > 0) {
        targetX = screenWidth - AI_DOCK_WIDTH - AI_DOCK_GAP
      }
      targetHeight = size.height > targetHeight ? targetHeight : size.height
    } else if (screenWidth > 0 && targetX + AI_DOCK_WIDTH > screenWidth) {
      // 非最大化但主+AI 超屏幕:AI 窗口本身让位(缩窄或往左偏),不动主窗口。
      // 优先策略:贴屏幕右侧边,AI 窗口宽度可能被主窗口右边缘掩盖一部分,但不强迫主窗口缩小。
      // 若主窗口右侧几乎贴边(targetX < pos.x + 100,主窗口已占满宽度),AI 覆盖在主窗口右侧上方(置顶态自然可见)。
      targetX = Math.max(pos.x + 100, screenWidth - AI_DOCK_WIDTH - AI_DOCK_GAP)
    }

    await aiWin.setPosition(new PhysicalPosition(targetX, targetY))
    await aiWin.setSize(new PhysicalSize(targetWidth, targetHeight))
  } catch (e) {
    console.error('[AI] syncToMain 失败:', e)
  } finally {
    _isSyncing = false
  }
}

/** 同步防抖包装:拖动/缩放高频事件不走立即同步,防 150ms 后执行。 */
function scheduleSync() {
  if (_syncTimer) clearTimeout(_syncTimer)
  _syncTimer = setTimeout(() => {
    _syncTimer = null
    void syncToMain()
  }, SYNC_DEBOUNCE_MS)
}

/** 启动主窗口 move/resize 跟随(吸附时调用,防后 150ms 同步防抖动) */
async function startFollowMain() {
  stopFollowMain()
  const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
  const mainWin = await WebviewWindow.getByLabel('main')
  if (!mainWin) return
  _unlistenMove = await mainWin.onMoved(() => { scheduleSync() })
  _unlistenResize = await mainWin.onResized(() => { scheduleSync() })
}

/** 停止主窗口跟随 + 清防抖计时器 */
function stopFollowMain() {
  if (_syncTimer) { clearTimeout(_syncTimer); _syncTimer = null }
  _unlistenMove?.()
  _unlistenResize?.()
  _unlistenMove = null
  _unlistenResize = null
}

export function useAiWindow() {
  return {
    detachPanel,
    reattachPanel,
    resumeInDetached,
    restoreGeneratingState,
    closeDetachedWindow,
    dockDetached,
    syncToMain,
    startFollowMain,
    stopFollowMain,
  }
}
