// Web audio decoder.
//
// Runs FULLY CLIENT-SIDE: the encoded bytes never leave the browser. We hand
// them to the platform's `AudioContext.decodeAudioData`, which can natively
// decode whatever container/codec the browser supports — typically mp3, m4a/mp4
// (AAC), wav, ogg/opus, and (in most modern browsers) flac. The browser does
// the heavy codec work; we then downmix + resample the PCM to 16 kHz mono.

import type { AudioDecoder, AudioFileInput } from './decode';
import type { PcmAudio } from '../types';
import { toMono16k } from './resample';

/**
 * Resolve a usable `AudioContext` constructor. Standard browsers expose
 * `window.AudioContext`; older Safari only exposes the `webkit`-prefixed one.
 */
function getAudioContextCtor(): typeof AudioContext {
  const ctor =
    window.AudioContext ||
    // Safari fallback; not in the lib DOM types, hence the cast.
    (window as unknown as { webkitAudioContext?: typeof AudioContext })
      .webkitAudioContext;
  if (!ctor) {
    throw new Error(
      'Web Audio API is unavailable: no AudioContext in this environment.',
    );
  }
  return ctor;
}

export const decoder: AudioDecoder = {
  async decode(input: AudioFileInput): Promise<PcmAudio> {
    const { data } = input;
    if (!data) {
      throw new Error('Web decoder requires `data` (an ArrayBuffer).');
    }

    const Ctor = getAudioContextCtor();
    const ctx = new Ctor();
    try {
      // `decodeAudioData` may *detach* (neuter) the ArrayBuffer it's given, so
      // we pass a copy via `slice(0)` to keep the caller's buffer reusable.
      const buf = await ctx.decodeAudioData(data.slice(0));

      // Pull out every channel's Float32 PCM. `getChannelData` returns a view
      // backed by the AudioBuffer; `toMono16k` only reads from it, and we close
      // the context only after the synchronous downmix/resample completes.
      const channelData: Float32Array[] = Array.from(
        { length: buf.numberOfChannels },
        (_unused, c) => buf.getChannelData(c),
      );

      const samples = toMono16k(channelData, buf.sampleRate);
      return { sampleRate: 16000, samples };
    } finally {
      // Always release the AudioContext (browsers cap how many can be live).
      // `close()` returns a promise; we don't need to await it before returning.
      void ctx.close();
    }
  },
};
