//! 流式看门狗 — 无数据超时兜底
//!
//! 后端断连/异常不发 AiCompleted/AiError 时,前端自动收尾+提示,
//! 避免 streaming 永久 true 卡死对话(用户报"流式文字停在中途"即此类)。
//!
//! 耦合说明:
//! - sendMessage 启动生成时 resetStreamWatchdog() 启动计时
//! - handleEvent 每个活跃事件(delta/工具/新轮/审批结果)重置;审批等待/完成/错误 clear
//! - 超时回调 onStreamTimeout 直接改 state 并补一条错误消息
//!
//! 依赖:nextMsgId 取自 aiShared(原从 useAiEvents 取,构成 useAiEvents ↔ useAiStream
//! 循环依赖;下沉到 aiShared 后本模块不再 import useAiEvents,环消除)

import { state } from '@/stores/ai'
import { t } from '@/i18n/i18n-helpers'
import { nextMsgId, convStates } from './aiShared'
import type { MessageId } from '@/api/types'
import { forceResetStreaming } from './streamingGuard'
import { emit } from '@tauri-apps/api/event'

/// 流式整体超时(ms)。
///
/// 设计权衡:
/// - 后端 stream_llm.rs 首 chunk 超时 10s + STREAM_IDLE_TIMEOUT 120s,正常请求远不到此
/// - 后端 execute_with_heartbeat 工具执行补 30s AiHeartbeat,正常工具执行也不会到此
/// - 但 LLM 重试间期(breaker 3轮)无 heartbeat——LLM 生成(~15s)+工具执行(~5s)循环
///   3轮总耗约 60-75s,45s 会误杀。改为 90s:覆盖 breaker 3轮 + margin。
export const STREAM_TIMEOUT_MS = 90000

// TD-260621-01 per-conv 看门狗:模块级单计时器 → convId → timer Map。
//
// 背景(F-09 多会话并发):原单 timer 下,A 会话 delta 重置计时器会顶掉 B 会话已挂起的 timer,
// 或 A 超时连累 B 的 generatingConvs(全清)。per-conv Map 后,各会话独立计时,互不顶替/连累。
//
// 向后兼容(避撞 F-09 禁动域):convId 可选。禁动域 useAiSend.ts/useAiApproval.ts 的 14 处调用
// 不传 convId,走 _legacyWatchdog fallback(模块级单 timer,行为对齐旧版);本批域 useAiEvents.ts
// 的调用点(已知 convId)传 convId 走 per-conv Map。两路并存,等 F-09 统一迁移调用点后可删 fallback。
//
// per-conv 路径与 legacy 路径互不干扰:convId 路径只动 Map,legacy 路径只动 _legacyWatchdog。
// 实际并发场景:F-09 已让 useAiEvents 按 conversation_id 路由事件(handleEvent:546 isCurrent 判定),
// 非 current conv 的事件不触发 reset/clear(reset/clear 在 isCurrent 分支内调用),故 per-conv
// 路径天然只跟踪当前展示会话的活跃 timer,跨会话切换时旧 conv 的 timer 由其 AiCompleted/AiError 精确清。
const _watchdogTimers = new Map<string, ReturnType<typeof setTimeout>>()
// 禁动域 fallback 单 timer(useAiSend/useAiApproval 无 convId 调用走此,行为对齐旧版)。
let _legacyWatchdog: ReturnType<typeof setTimeout> | null = null

/**
 * 看门狗超时回调:收尾 streaming 态并补错误消息(走 forceResetStreaming 兜底复位,
 * 对齐 [[devflow-generating-statemachine]])。
 *
 * TD-260621-01 per-conv:携带 convId 时,forceResetStreaming(reason, convId) 经 setStreaming
 * 仅复位该会话 streaming 态。批4 双轨收口后 generatingConvs 已退役,convStates 项由各会话
 * 收尾事件精确管理(on/off 后端均 emit 收尾态),本回调不再触碰生成态集合。convId 为空
 * (legacy fallback 路径)时全局兜底,行为对齐旧版。
 */
export function onStreamTimeout(convId?: string) {
  // BUG-2026-07-17 根治性重构:状态机必须能自我收敛。
  //
  // 原问题:streaming=true 写在多个入口(doSend/regenerate/edit/L1入队),
  // 但 false 只在 cleanupTerminatedConversation 里。任一漏掉即永久卡住。
  //
  // 新逻辑:看门狗是状态机自我收敛的唯一包底,超时即彻底重置:
  // - 清 streaming=false
  // - 清 convStates 项(按钮 stopBtnState 读它)
  // - 清 currentText/queue
  // - 清 watchdog timer
  // 不管 stillGenerating 检测(即使已经 false 也再清理一次,完备包底)。
  console.warn(`[AI-Watchdog] 超时触发,强制收敛状态${convId ? `(convId=${convId})` : '(全局)'}`)

  // 彻底清理三处状态源,保证状态机收敛到 idle
  forceResetStreaming('onStreamTimeout(无数据包底)', convId)
  if (convId) {
    convStates.delete(convId)
  } else {
    // AIC-FIX-17-P0-1:legacy 单 timer 只跟踪当前活跃会话,不清所有 conv(防误杀其他并发会话)。
    if (state.activeConversationId) {
      convStates.delete(state.activeConversationId)
    }
  }
  clearStreamWatchdog(convId)

  // 广播清审批计时器(流式已中断,审批计时器无意义)
  void emit('ai-approval-clear-timers', {})

  // 检测是否是 "工具已执行完后续中断"(区别文案)
  let hasCompletedTool = false
  for (let i = state.messages.length - 1; i >= 0; i--) {
    const toolCalls = state.messages[i].toolCalls
    if (!toolCalls) continue
    for (let j = 0; j < toolCalls.length; j++) {
      if (toolCalls[j].status === 'completed') {
        hasCompletedTool = true
        break
      }
    }
    if (hasCompletedTool) break
  }

  // 补一条错误气泡(仅在实际还在生成态时补,避免重复补)
  // 这里直接补——forceResetStreaming 已重置状态,不会重复触发
  const content = hasCompletedTool
    ? t('ai.streamInterruptedAfterTool')
    : t('ai.streamInterrupted')
  state.messages.push({
    id: `timeout-${nextMsgId()}` as MessageId,
    role: 'assistant',
    content,
    isError: true,
    timestamp: Date.now(),
  })
}

/**
 * 启动/重置看门狗(活跃事件或 sendMessage 时调用)。
 *
 * TD-260621-01 per-conv:传 convId → 走 per-conv Map(arm 前 clear 该 conv 旧 timer,防重入堆积);
 * 省略 convId(禁动域 useAiSend/useAiApproval 调用)→ 走 _legacyWatchdog fallback(单 timer,行为对齐旧版)。
 * setTimeout 回调闭包捕获 convId,到期 onStreamTimeout(convId) 精确清该 conv 态。
 */
export function resetStreamWatchdog(convId?: string) {
  if (convId) {
    const old = _watchdogTimers.get(convId)
    if (old) clearTimeout(old)
    const timer = setTimeout(() => onStreamTimeout(convId), STREAM_TIMEOUT_MS)
    _watchdogTimers.set(convId, timer)
    // 根治(TD-260621-01 遗留):切到 per-conv 时清 legacy(doSend 启动的 legacy timer 与活跃事件
    // per-conv 共存期,legacy 是残留)。防 AiCompleted clear(convId) 清不到 legacy 致 130s 误报。
    if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
  } else {
    // 禁动域 fallback:行为对齐旧版单 timer。
    if (_legacyWatchdog) clearTimeout(_legacyWatchdog)
    _legacyWatchdog = setTimeout(() => onStreamTimeout(), STREAM_TIMEOUT_MS)
  }
}

/**
 * 清除看门狗(完成/错误/审批等待时调用)。
 *
 * TD-260621-01 per-conv:传 convId → 仅清该 conv 的 timer;省略 → 清 _legacyWatchdog fallback。
 * 注:省略 convId 时**不**清 Map(避免禁动域全局 clear 误清并发会话的 per-conv timer)。
 * stopListener 卸载场景需清全部,调 clearAllStreamWatchdogs()。
 */
export function clearStreamWatchdog(convId?: string) {
  if (convId) {
    const timer = _watchdogTimers.get(convId)
    if (timer) { clearTimeout(timer); _watchdogTimers.delete(convId) }
    // 根治(TD-260621-01 遗留):doSend reset 走 legacy,AiCompleted clear 走 per-conv Map,clear 清不到
    // legacy 致 130s 残留误报。批2 单 loop 阶段 legacy 单例只属当前会话,clear(convId) 兜底清 legacy 安全;
    // 批3+ 多会话落地时 useAiSend reset 迁 per-conv + 删 legacy fallback,此兜底随之移除。
    if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
  } else {
    if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
  }
}

/** 清除全部看门狗 timer(per-conv Map + legacy fallback)—— stopListener 卸载场景调用,
 *  防回调写已卸载组件的 state。 */
export function clearAllStreamWatchdogs() {
  for (const timer of _watchdogTimers.values()) clearTimeout(timer)
  _watchdogTimers.clear()
  if (_legacyWatchdog) { clearTimeout(_legacyWatchdog); _legacyWatchdog = null }
}

export function useAiStream() {
  return {
    onStreamTimeout,
    resetStreamWatchdog,
    clearStreamWatchdog,
    clearAllStreamWatchdogs,
  }
}
