#!/usr/bin/env node
/**
 * streamingGuard 单测(对齐 memory [[devflow-generating-statemachine]] 前端落地)。
 *
 * 背景:前端无 vitest(引入框架超本批白名单),故用零依赖 Node 内置 assert 跑断言。
 * 本脚本内联 streamingGuard.ts 的联动规则副本,验证:
 *   1. 合法跃迁(false→true / true→false)正确改 streaming + 联动 generatingConvs
 *   2. 幂等写(true→true / false→false)不拒绝(放行,记日志)
 *   3. 联动规则:value=true && convId → add(convId);value=false && convId → delete(convId);
 *      无 convId 不动 generatingConvs(全局兜底场景,避免误删并发会话)
 *   4. feature flag 关 → guard 退化为直接赋值(不联动 generatingConvs)
 *   5. forceResetStreaming(panic 兜底):复位 streaming + 清 currentText + 清 queue
 *
 * 同步约定:若 streamingGuard.ts 的联动规则变更,本脚本副本须同步更新(脚本顶部
 * 注释已标注对应源码行)。
 *
 * 运行:node scripts/verify-streaming-guard.mjs
 */
import assert from 'node:assert/strict'

let passed = 0
function test(name, fn) {
  try {
    fn()
    passed++
    console.log(`  ✓ ${name}`)
  } catch (e) {
    console.error(`  ✗ ${name}`)
    console.error(`    ${e.message}`)
    process.exitCode = 1
  }
}

// ── mock state + appSettings(对齐 stores/ai.ts state 形态) ──
function makeState() {
  return {
    streaming: false,
    currentText: '',
    queue: [],
    generatingConvs: new Set(),
  }
}

/**
 * setStreaming 联动逻辑副本(同步自 src/composables/ai/streamingGuard.ts setStreaming)。
 * 与源码逐行对齐:flag 开 → 幂等观测 + 联动 generatingConvs + 赋值;flag 关 → 直接赋值。
 */
function setStreaming(state, value, opts = {}, guardEnabled = true) {
  const { convId } = opts
  if (!guardEnabled) {
    state.streaming = value
    return
  }
  // 联动 generatingConvs(源码 streamingGuard.ts:80-87)
  if (convId) {
    if (value) {
      state.generatingConvs.add(convId)
    } else {
      state.generatingConvs.delete(convId)
    }
  }
  state.streaming = value
}

/** forceResetStreaming 副本(源码 streamingGuard.ts forceResetStreaming)。
 *  TD-260621-01 per-conv:convId 可选。传 convId → 仅清该 conv 的 generatingConvs(per-conv 超时场景);
 *  省略 → 全局兜底,generatingConvs 不动(由 AiCompleted/AiError 精确管理)。 */
function forceResetStreaming(state, _reason, convId) {
  setStreaming(state, false, convId ? { convId } : {})
  state.currentText = ''
  state.queue = []
}

console.log('streamingGuard 单测:')

// ── 1. 合法跃迁 ──
test('false→true: streaming 置 true + convId 入 Set', () => {
  const s = makeState()
  setStreaming(s, true, { convId: 'conv-1' })
  assert.equal(s.streaming, true)
  assert.ok(s.generatingConvs.has('conv-1'), 'conv-1 应在 generatingConvs 中')
})

test('true→false: streaming 置 false + convId 出 Set', () => {
  const s = makeState()
  s.generatingConvs.add('conv-1')
  setStreaming(s, true, { convId: 'conv-1' })
  setStreaming(s, false, { convId: 'conv-1' })
  assert.equal(s.streaming, false)
  assert.ok(!s.generatingConvs.has('conv-1'), 'conv-1 应已移除')
})

// ── 2. 幂等写不拒绝 ──
test('true→true 幂等: 不拒绝,convId add 幂等无副作用', () => {
  const s = makeState()
  setStreaming(s, true, { convId: 'conv-1' })
  setStreaming(s, true, { convId: 'conv-1' }) // 幂等
  assert.equal(s.streaming, true)
  assert.equal(s.generatingConvs.size, 1, 'Set 不应重复添加')
})

test('false→false 幂等: 不拒绝', () => {
  const s = makeState()
  setStreaming(s, false, {})
  assert.equal(s.streaming, false)
})

// ── 3. 联动规则 ──
test('无 convId 的 true: 不动 generatingConvs', () => {
  const s = makeState()
  setStreaming(s, true, {}) // 无 convId
  assert.equal(s.streaming, true)
  assert.equal(s.generatingConvs.size, 0, '无 convId 不应 add')
})

test('无 convId 的 false(全局兜底): 不动 generatingConvs(避免误删并发会话)', () => {
  const s = makeState()
  s.generatingConvs.add('conv-A')
  s.generatingConvs.add('conv-B')
  setStreaming(s, false, {}) // 全局兜底,无 convId
  assert.equal(s.streaming, false)
  assert.equal(s.generatingConvs.size, 2, '并发会话生成态不应被全局复位误删')
})

test('convId 为空串: 视为无 convId,不动 generatingConvs', () => {
  const s = makeState()
  setStreaming(s, true, { convId: '' })
  assert.equal(s.streaming, true)
  assert.equal(s.generatingConvs.size, 0)
})

// ── 4. feature flag 关 ──
test('flag 关: guard 退化为直接赋值,不联动 generatingConvs', () => {
  const s = makeState()
  setStreaming(s, true, { convId: 'conv-1' }, /* guardEnabled */ false)
  assert.equal(s.streaming, true)
  assert.equal(s.generatingConvs.size, 0, 'flag 关不联动 Set')
})

// ── 5. forceResetStreaming(panic 兜底) ──
test('forceResetStreaming: 复位 streaming + 清 currentText + 清 queue', () => {
  const s = makeState()
  s.streaming = true
  s.currentText = '流式中途文字'
  s.queue = [{ text: '排队消息' }]
  s.generatingConvs.add('conv-1')
  forceResetStreaming(s, 'onStreamTimeout(130s)')
  assert.equal(s.streaming, false, 'streaming 应复位 false')
  assert.equal(s.currentText, '', 'currentText 应清空')
  assert.equal(s.queue.length, 0, 'queue 应清空')
  // 注:forceResetStreaming 不传 convId,generatingConvs 不动(由 AiCompleted/AiError 精确管理)
  assert.ok(s.generatingConvs.has('conv-1'), '兜底复位不应误删并发会话生成态')
})

// ── 6. TD-260621-01 forceResetStreaming per-conv ──
test('TD-260621-01 forceResetStreaming(convId): 仅清该 conv 的 generatingConvs,不误杀并发会话', () => {
  const s = makeState()
  s.streaming = true
  s.generatingConvs.add('conv-A')
  s.generatingConvs.add('conv-B')
  // A 超时,携 convId='conv-A' → 仅 delete conv-A,B 保留
  forceResetStreaming(s, 'onStreamTimeout(130s 无数据)', 'conv-A')
  assert.equal(s.streaming, false, 'streaming 应复位 false')
  assert.ok(!s.generatingConvs.has('conv-A'), 'conv-A(超时会话)应被清')
  assert.ok(s.generatingConvs.has('conv-B'), 'conv-B(并发正常会话)不应被误杀')
})

console.log(`\n${passed} passed`)
if (process.exitCode === 1) {
  console.error('FAILED')
  process.exit(1)
} else {
  console.log('ALL PASSED')
}
