/**
 * PCM Recorder AudioWorklet.
 *
 * Captures mono Float32 mic samples at the AudioContext's native rate,
 * resamples them to a target sample rate (default 16 kHz) with linear
 * interpolation, then ships PCM16 frames of a fixed duration (default 20 ms)
 * to the main thread via `port.postMessage(ArrayBuffer)`.
 *
 * It also computes a simple RMS level per frame for the UI VU meter so the
 * main thread doesn't have to re-process the audio.
 */

class PcmRecorderProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();

    const opts = (options && options.processorOptions) || {};
    this._targetSampleRate = opts.targetSampleRate || 16000;
    this._frameMs = opts.frameMs || 20;
    this._frameSamples = Math.round(
      (this._targetSampleRate * this._frameMs) / 1000,
    );

    // Resampling state.
    // `ratio` is input samples per output sample.
    this._ratio = sampleRate / this._targetSampleRate;
    this._inputBuffer = new Float32Array(0);
    // Float position in `_inputBuffer` for the next output sample.
    this._inputOffset = 0;

    // Output framing state.
    this._frameBuffer = new Int16Array(this._frameSamples);
    this._frameIndex = 0;

    // VU meter accumulator.
    this._rmsSumSquares = 0;
    this._rmsCount = 0;
  }

  process(inputs) {
    const input = inputs[0];
    if (!input || input.length === 0) return true;
    const channel = input[0];
    if (!channel || channel.length === 0) return true;

    // Append new samples to the input buffer.
    const merged = new Float32Array(this._inputBuffer.length + channel.length);
    merged.set(this._inputBuffer, 0);
    merged.set(channel, this._inputBuffer.length);
    this._inputBuffer = merged;

    const ratio = this._ratio;
    const inLen = this._inputBuffer.length;
    let pos = this._inputOffset;

    while (pos + 1 < inLen) {
      const lo = Math.floor(pos);
      const hi = lo + 1;
      const w = pos - lo;
      const sample =
        this._inputBuffer[lo] * (1 - w) + this._inputBuffer[hi] * w;

      this._rmsSumSquares += sample * sample;
      this._rmsCount += 1;

      let s = sample;
      if (s > 1) s = 1;
      else if (s < -1) s = -1;
      this._frameBuffer[this._frameIndex++] =
        s < 0 ? Math.round(s * 0x8000) : Math.round(s * 0x7fff);

      if (this._frameIndex === this._frameSamples) {
        const frame = new Int16Array(this._frameSamples);
        frame.set(this._frameBuffer);
        const rms =
          this._rmsCount > 0
            ? Math.sqrt(this._rmsSumSquares / this._rmsCount)
            : 0;
        this.port.postMessage(
          { type: "frame", buffer: frame.buffer, rms },
          [frame.buffer],
        );
        this._frameIndex = 0;
        this._rmsSumSquares = 0;
        this._rmsCount = 0;
      }

      pos += ratio;
    }

    // Trim consumed samples from the input buffer; keep at least the last
    // sample we still need to interpolate against on the next call.
    const consumed = Math.floor(pos);
    if (consumed > 0) {
      this._inputBuffer = this._inputBuffer.slice(consumed);
      pos -= consumed;
    }
    this._inputOffset = pos;

    return true;
  }
}

registerProcessor("pcm-recorder", PcmRecorderProcessor);
