/**
 * useAudioRecorder 的采样率协商。
 *
 * 钉的是一件事：**上报的永远是实际拿到的采样率，不是我们要的那个**。
 * 这条不变量一破，后端就会按 16kHz 去解 48kHz 的数据 —— 回放慢三倍、评分全错，
 * 而整条链路没有任何一层会报错。三种浏览器行为都要走一遍。
 */
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import ts from 'typescript'

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

// Vue runtime → 本地 shim（同 test-article-reading-engine.mjs 的做法）
source = source.replace(
  "import { ref, onUnmounted } from 'vue'",
  `const ref = v => ({ value: v })
   const onUnmounted = () => {}`,
)

const compiled = ts.transpileModule(source, {
  compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText

const mod = await import(`data:text/javascript,${encodeURIComponent(compiled)}`)

// ---- 浏览器环境替身 ----

const HARDWARE_RATE = 48000

function installEnvironment({ audioContextFactory }) {
  const track = {
    label: 'fake mic', enabled: true, muted: false, readyState: 'live',
    getSettings: () => ({}), stop() { this.readyState = 'ended' },
  }
  // Node 22 的 globalThis.navigator 只有 getter，赋值会抛 —— 用 defineProperty 盖掉
  Object.defineProperty(globalThis, 'navigator', {
    configurable: true,
    value: {
      mediaDevices: { getUserMedia: async () => ({ getTracks: () => [track], getAudioTracks: () => [track] }) },
      permissions: { query: async () => { throw new Error('unsupported') } },
    },
  })
  globalThis.AudioContext = audioContextFactory
  globalThis.AudioWorkletNode = class {
    constructor() { this.port = { onmessage: null, postMessage() {} } }
    connect() {}
    disconnect() {}
  }
  globalThis.DOMException = globalThis.DOMException ?? class extends Error {}
}

/** 造一个 AudioContext 类，`resolveRate` 决定它对 sampleRate 提示的反应。 */
function makeAudioContextClass(resolveRate) {
  return class FakeAudioContext {
    static requested = []
    constructor(options) {
      FakeAudioContext.requested.push(options?.sampleRate ?? null)
      this.sampleRate = resolveRate(options?.sampleRate)   // 可以抛
      this.state = 'running'
      this.destination = {}
      this.audioWorklet = { addModule: async () => {} }
    }
    createMediaStreamSource() { return { connect() {} } }
    createAnalyser() {
      return { fftSize: 0, frequencyBinCount: 256, connect() {}, getByteFrequencyData() {} }
    }
    async resume() { this.state = 'running' }
    async close() { this.state = 'closed' }
  }
}

async function startAndReadRate(AudioContextClass) {
  installEnvironment({ audioContextFactory: AudioContextClass })
  const recorder = mod.useAudioRecorder()
  await recorder.startRecording()
  const rate = recorder.sampleRate.value
  recorder.cleanup()
  return rate
}

// ---- 1. 浏览器支持：拿到 16kHz ----
{
  const Ctx = makeAudioContextClass(hint => hint ?? HARDWARE_RATE)
  const rate = await startAndReadRate(Ctx)
  assert.equal(rate, 16000, '支持自定义采样率时应该拿到 16kHz')
  assert.deepEqual(Ctx.requested, [16000], '应该只构造一次，并且带着 16000 的提示')
}

// ---- 2. 浏览器拒绝（旧 iOS Safari）：退回硬件默认，录音照常 ----
{
  const Ctx = makeAudioContextClass(hint => {
    if (hint) throw new Error('NotSupportedError: sample rate not supported')
    return HARDWARE_RATE
  })
  const rate = await startAndReadRate(Ctx)
  assert.equal(rate, HARDWARE_RATE, '构造抛出时必须退回硬件默认，而不是整个录音失败')
  assert.deepEqual(Ctx.requested, [16000, null], '先试 16000，抛了再试不带提示的')
}

// ---- 3. 浏览器**默默无视**提示：上报真值，不是我们要的值 ----
// 1 是「照办」、2 是「明着拒绝」，这条是第三种行为：收下提示、返回硬件值、不抛。
// 前两条都覆盖不到它，而它正是最坏的那种失败 —— 没有异常、没有日志，只是后端
// 从此按 16kHz 去解 48kHz 的数据。
{
  const Ctx = makeAudioContextClass(() => HARDWARE_RATE)   // 提示照收，返回值不变
  const rate = await startAndReadRate(Ctx)
  assert.equal(rate, HARDWARE_RATE, '浏览器无视提示时必须上报实际采样率')
  assert.notEqual(rate, 16000)
}

console.log('✓ test-article-recorder-sample-rate: 3 cases passed')
