//! streaming 状态写收敛 guard(对齐 memory [[devflow-generating-statemachine]] 前端落地)。
//!
//! 背景:`state.streaming`(当前活跃视图的流式态)原本散布在 8+ 处直接赋值
//! (useAiSend/useAiEvents/useAiConversations/useAiWindow/useAiPanel/useAiStream),
//! 任一新增 return 漏写、或异常路径(前端 JS 错误/窗口崩溃恢复)跳过复位 →
//! streaming 永久 true 卡死输入框(stop 按钮常驻、新消息入队不发)。
//!
//! 本模块把写侧收敛到单一 `setStreaming` 入口,做两件事:
//! 1. 合法性观测:`true→true` / `false→false` 幂等场景记 debug 日志(不拒绝——
//!    resetStreamWatchdog 等副作用场景需合法重入),异常模式(如 false 时仍在跑看门狗)
//!    记 warn 日志供排查卡死。
//! 2. feature flag `df-ai-generating-statemachine`:关时 guard 退化为直接赋值,
//!    不校验,回退原散布语义(灰度/回退开关)。
//!
//! 批4 双轨收口后:generating 态真相归 convStates(enum),本 guard 不再联动 generatingConvs
//! (bool 轨已退役)。streaming 单值(state.streaming)是全局当前视图流式态,另一条过渡态,
//! 归 F-09 B 路线 per-conv streaming 收口,本批暂留。doSend setStreaming(true,{convId}) 后,
//! 生成态由 useAiEvents handleEvent 活跃事件兜底写 setConvState('generating') 标记。
//!
//! 注:本 guard 是「写收敛 + 可观测」二合一,不是拒绝式状态机——
//! streaming 是 boolean 无真正非法跃迁,guard 价值在集中入口与日志而非拦截。

import { state } from '@/stores/ai'
import { convStates } from './aiShared'
import { useAppSettingsStore } from '@/stores/appSettings'

/// feature flag key(appSettings KV)。关=回退散布语义(灰度/回退用)。
const STREAMING_GUARD_FLAG = 'df-ai-generating-statemachine'

const appSettings = useAppSettingsStore()

/// guard 是否启用(读 appSettings,默认开=true)。
///
/// 默认开的理由:本 guard 是收敛性改造(行为等价 + 加可观测),非行为变更,
/// 默认开启直接收效;flag 仅作紧急回退通道(若发现回归,用户可在设置里关掉回退原散布语义)。
function isGuardEnabled(): boolean {
  return appSettings.get<boolean>(STREAMING_GUARD_FLAG, true)
}

/**
 * streaming 写收敛入口 —— 替代散布的 `state.streaming = true/false`(原 14 处,现全收敛至此)。
 * @param value 目标流式态
 * @param opts.convId 关联会话 id(批4 收口后仅用于日志可观测,不再联动 generatingConvs;
 *   生成态真相归 convStates,由 useAiEvents 兜底写 setConvState 标记)。
 * @param opts.reason 调用方标注(日志可观测,如 'AiCompleted'/'stopChat'/'onStreamTimeout')。
 *
 * 行为:
 * - flag 关 → 直接 `state.streaming = value`(回退散布语义,不校验)。
 * - flag 开 → 幂等检测(value===当前值记 debug)、写日志。
 */
export function setStreaming(
  value: boolean,
  opts: { convId?: string | null; reason?: string } = {},
): void {
  const { convId, reason } = opts

  // flag 关:回退散布语义,直接赋值不动其他。
  if (!isGuardEnabled()) {
    state.streaming = value
    return
  }

  const prev = state.streaming

  // 幂等观测:同值重复写不拒绝(resetStreamWatchdog 等副作用场景需合法重入),
  // 仅记 debug 日志供排查「为何重复置位」的卡死场景。
  if (prev === value) {
    console.debug(
      `[AI-Guard] setStreaming 幂等(prev=${prev}, convId=${convId ?? 'none'}, reason=${reason ?? 'unknown'})`,
    )
  } else {
    console.debug(
      `[AI-Guard] setStreaming ${prev}→${value} (convId=${convId ?? 'none'}, reason=${reason ?? 'unknown'})`,
    )
  }

  // 批4 双轨收口:generating 态真相归 convStates(enum),本 guard 不再联动 generatingConvs
  // (bool 轨已退役)。streaming 单值是另一条过渡态(F-09 per-conv streaming 收口前保留)。
  state.streaming = value
}

/**
 * 强制复位 streaming=false 的兜底入口(panic/卡死恢复用)。
 *
 * 与 setStreaming(false) 区别:本函数额外清 currentText + queue,
 * 专用于看门狗超时 / 前端异常恢复等「必须彻底清态」场景。
 *
 * TD-260621-01 per-conv:看门狗超时携带 convId 时,经 setStreaming 仅复位该会话 streaming 态;
 * 该会话的 convStates 项由各自会话收尾事件精确管理(on/off 后端均会 emit 收尾态)。
 *
 * 注:currentText/queue 仍是单例(F-09 域,本批不动),per-conv 超时在多会话并发下
 * 会清当前累积的 currentText——但 currentText 仅活跃视图写入(useAiEvents handleEvent delta 累积),
 * 而超时仅发生在该 conv 长时间无数据,此场景 currentText 多为空或已 flush,
 * 实际误伤面远小于 generatingConvs 全清(队列 queue 的清理由 F-09 统一改,见 TD-260621-01 注释)。
 *
 * @param reason 兜底来源(日志可观测)
 * @param convId 关联会话 id(看门狗 per-conv 超时传;省略=全局兜底)
 */
export function forceResetStreaming(reason: string, convId?: string): void {
  console.warn(`[AI-Guard] forceResetStreaming(卡死兜底): ${reason} (convId=${convId ?? 'none'})`)
  setStreaming(false, { convId: convId ?? null, reason })
  state.currentText = ''
  state.queue = []
  // BUG-2026-07-17 根治:同时清理 convStates 项。
  // 原实现仅清 streaming/currentText/queue,但按钮 stopBtnState 优先读 convStates
  // (ChatInput.vue:243),convStates=generating 时仍显红色停止按钮。后端未发
  // AiConvStateChanged 时(conv_state 永久卡住),必须前端主动清 convStates 才能解锁 UI。
  if (convId) {
    convStates.delete(convId)
  } else {
    // 全局兜底:无 convId 时清所有生成态(卡死恢复场景,精确性不重要)
    convStates.clear()
  }
}
