import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import ts from 'typescript'

const sourceUrl = new URL('../src/views/Editor/EnglishSpeaking/services/articleErrors.ts', import.meta.url)
let source = await readFile(sourceUrl, 'utf8')

// `lang` 是 PPT 运行时的响应式对象；测试里换成 identity 桩，直接断言「返回的是哪个 key」，
// 而不是断言译文 —— 译文归 test-article-reading-i18n.mjs 管，这里只管映射对不对。
source = source
  // 桩返回 key 本身，于是断言的是「映射到哪个 key」而不是译文。带占位符的那条例外：
  // 它多挂一个 `{n}`，好让「段号有没有被填进去」也能被断言到。
  .replace(
    "import { lang } from '@/main'",
    "const lang = new Proxy({}, { get: (_, k) => k === 'ssArticleErrDemoAudio' ? 'ssArticleErrDemoAudio:{n}' : String(k) })",
  )
  .replace(/import \{[^}]*\} from '\.\/articleReading'/, 'class ArticleApiError extends Error { constructor(m, s) { super(m); this.status = s } }')

const compiled = ts.transpileModule(source, {
  compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText
const { friendlyArticleError, articleActiveCountFromError } = await import(`data:text/javascript,${encodeURIComponent(compiled)}`)

const cases = [
  ['Config not found', 'ssArticleErrConfigNotFound'],
  ['Config is not an article config', 'ssArticleErrNotArticleConfig'],
  ['Article content is empty', 'ssArticleErrContentEmpty'],
  ['Session not found', 'ssArticleErrSessionNotFound'],
  ['Paragraph not found', 'ssArticleErrParagraphNotFound'],
  ['Session is abandoned', 'ssArticleSessionExpired'],
  ['Session has ended', 'ssArticleErrSessionEnded'],
  ['Report already generated', 'ssArticleErrReportGenerated'],
  ['Segmentation failed', 'ssArticleSegmentFailed'],
  ['Segmentation returned empty output', 'ssArticleSegmentFailed'],
  ['content is required', 'ssArticleErrBadRequest'],
  ['configId is required', 'ssArticleErrBadRequest'],
  ['userId is required', 'ssArticleErrBadRequest'],
  ['users is required', 'ssArticleErrBadRequest'],
  ['users capped at 100', 'ssArticleErrBadRequest'],
]

for (const [detail, key] of cases) {
  assert.equal(friendlyArticleError(new Error(detail)), key, `bare detail: ${detail}`)
}

// request() 万一没拆干净、或者别的 service 抛了带包装的 message，也要吃得下
assert.equal(
  friendlyArticleError(new Error('{"detail":"Session not found"}')),
  'ssArticleErrSessionNotFound',
  'raw JSON body',
)
assert.equal(
  friendlyArticleError(new Error('[404] {"detail":"Config not found"}')),
  'ssArticleErrConfigNotFound',
  '@/services/speaking parse() 的 `[404] {...}` 形状',
)

// 409「有人在练」：人数照旧要能摘出来（弹框靠它），但它**不该**被当成错误文案 ——
// 2026-07-24 起那是问句：handleApply 截住它去弹确认框（spec §4.3 B）。真漏到 toast 只能是
// 别处误用，那就走通用兜底，绝不出现「暂时无法修改」这种已经不成立的话。
assert.equal(
  friendlyArticleError(new Error('Students are practising: 3')),
  'ssArticleErrGeneric',
  '409 不再映射成一句「无法修改」',
)
assert.equal(articleActiveCountFromError(new Error('Students are practising: 3')), 3)
assert.equal(articleActiveCountFromError(new Error('Students are practising: 0')), 0)
assert.equal(articleActiveCountFromError(new Error('Session not found')), null)
assert.equal(articleActiveCountFromError(null), null)

// 保存配置时整篇示范音频同步合成，失败的 detail 带着段号（`[2]` / `[1, 3]`）。
// 段号是老师唯一能据以行动的信息，必须填进文案，不能只回一句笼统的失败。
assert.equal(
  friendlyArticleError(new Error('Demo audio synthesis failed: [2]')),
  'ssArticleErrDemoAudio:2',
  'single failing paragraph',
)
assert.equal(
  friendlyArticleError(new Error('Demo audio synthesis failed: [1, 3]')),
  'ssArticleErrDemoAudio:1、3',
  'several failing paragraphs',
)
assert.equal(
  friendlyArticleError(new Error('{"detail":"Demo audio synthesis failed: [4]"}')),
  'ssArticleErrDemoAudio:4',
  'wrapped in a JSON body',
)

// 兜底：绝不把原始内容漏出去
const leaks = [
  new Error('{"detail":"some brand new backend error"}'),
  new Error('TypeError: Failed to fetch'),
  new Error(''),
  'a bare string',
  null,
  undefined,
  { nope: true },
]
for (const cause of leaks) {
  const out = friendlyArticleError(cause)
  assert.equal(out, 'ssArticleErrGeneric', `fallback for ${JSON.stringify(String(cause))}`)
  assert.ok(!out.includes('detail'), 'never leak the response body')
  assert.ok(!out.includes('{'), 'never leak JSON')
}

console.log(`article errors: ${cases.length + 2} mappings + ${leaks.length} fallbacks OK`)
