const TARGET_SAMPLE_RATE = 16_000;

function mixToMono(buffer: AudioBuffer): AudioBuffer {
  if (buffer.numberOfChannels === 1) return buffer;
  const mono = new AudioBuffer({
    length: buffer.length,
    numberOfChannels: 1,
    sampleRate: buffer.sampleRate,
  });
  const out = mono.getChannelData(0);
  const channels = buffer.numberOfChannels;
  for (let ch = 0; ch < channels; ch += 1) {
    const data = buffer.getChannelData(ch);
    for (let i = 0; i < buffer.length; i += 1) {
      out[i] += data[i] / channels;
    }
  }
  return mono;
}

function encodeWav(audioBuffer: AudioBuffer): Blob {
  const channel = audioBuffer.getChannelData(0);
  const dataSize = channel.length * 2;
  const buffer = new ArrayBuffer(44 + dataSize);
  const view = new DataView(buffer);

  const writeString = (offset: number, value: string) => {
    for (let i = 0; i < value.length; i += 1) {
      view.setUint8(offset + i, value.charCodeAt(i));
    }
  };

  writeString(0, 'RIFF');
  view.setUint32(4, 36 + dataSize, true);
  writeString(8, 'WAVE');
  writeString(12, 'fmt ');
  view.setUint32(16, 16, true);
  view.setUint16(20, 1, true);
  view.setUint16(22, 1, true);
  view.setUint32(24, audioBuffer.sampleRate, true);
  view.setUint32(28, audioBuffer.sampleRate * 2, true);
  view.setUint16(32, 2, true);
  view.setUint16(34, 16, true);
  writeString(36, 'data');
  view.setUint32(40, dataSize, true);

  let offset = 44;
  for (let i = 0; i < channel.length; i += 1) {
    const sample = Math.max(-1, Math.min(1, channel[i] ?? 0));
    view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
    offset += 2;
  }

  return new Blob([buffer], { type: 'audio/wav' });
}

/** 将浏览器录音转为 16kHz 单声道 WAV，提升 ASR 解码成功率。失败时返回原 blob。 */
export async function blobToWav16kMono(blob: Blob): Promise<Blob> {
  if (!blob.size) return blob;

  const audioContext = new AudioContext();
  try {
    const arrayBuffer = await blob.arrayBuffer();
    let decoded: AudioBuffer;
    try {
      decoded = await audioContext.decodeAudioData(arrayBuffer.slice(0));
    } catch {
      return blob;
    }

    const mono = mixToMono(decoded);
    const duration = mono.duration;
    if (!Number.isFinite(duration) || duration <= 0) return blob;

    const offline = new OfflineAudioContext(
      1,
      Math.max(1, Math.ceil(duration * TARGET_SAMPLE_RATE)),
      TARGET_SAMPLE_RATE,
    );
    const source = offline.createBufferSource();
    source.buffer = mono;
    source.connect(offline.destination);
    source.start(0);
    const rendered = await offline.startRendering();
    if (!rendered.length) return blob;
    return encodeWav(rendered);
  } finally {
    await audioContext.close().catch(() => undefined);
  }
}
