class GeminiAudioCaptureProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
    this._pending = [];
    this._pendingLength = 0;
    this._targetSampleRate = 16000;
    this._targetFrameLength = 640; // 40ms at 16kHz. Low overhead without feeling chunky.
  }

  _toTargetRate(input) {
    if (sampleRate === this._targetSampleRate) {
      const copy = new Float32Array(input.length);
      copy.set(input);
      return copy;
    }
    const ratio = sampleRate / this._targetSampleRate;
    const outputLength = Math.max(1, Math.round(input.length / ratio));
    const output = new Float32Array(outputLength);
    for (let i = 0; i < outputLength; i += 1) {
      const src = i * ratio;
      const lo = Math.floor(src);
      const hi = Math.min(lo + 1, input.length - 1);
      const frac = src - lo;
      output[i] = input[lo] * (1 - frac) + input[hi] * frac;
    }
    return output;
  }

  process(inputs) {
    const input = inputs[0]?.[0];
    if (!input || input.length === 0) return true;
    const copy = this._toTargetRate(input);
    this._pending.push(copy);
    this._pendingLength += copy.length;

    while (this._pendingLength >= this._targetFrameLength) {
      const frame = new Float32Array(this._targetFrameLength);
      let offset = 0;
      while (offset < frame.length && this._pending.length) {
        const head = this._pending[0];
        const take = Math.min(head.length, frame.length - offset);
        frame.set(head.subarray(0, take), offset);
        offset += take;
        if (take === head.length) {
          this._pending.shift();
        } else {
          this._pending[0] = head.subarray(take);
        }
        this._pendingLength -= take;
      }
      this.port.postMessage({ type: 'audio', data: frame }, [frame.buffer]);
    }
    return true;
  }
}

registerProcessor('gemini-audio-capture-processor', GeminiAudioCaptureProcessor);
