class StreamProcessor extends AudioWorkletProcessor {
  constructor() {
    super();

    this.bufferSize = 8192 * 128; // Max buffer size for each channel
    this.writeIndex = 0;
    this.readIndex = 0;
    this.usedSpace = 0;
    this.fatalError = 0;
    this.started = 1;

    // Parallel circular buffers, one for each channel
    this.buffers = [];

    // Create a circular buffer for each channel (default: 2 channels)
    for (let i = 0; i < 2; i++) {
        this.buffers.push(new Float32Array(this.bufferSize)); // Buffer size can be adjusted
    }
    // Listen for messages from the main thread to receive audio data
    this.port.onmessage = (event) => {
      const { audioData } = event.data;
      if (audioData && audioData.length > 0) {
        this.writeToBuffer(audioData);
      }
    };
  }

  process(inputList, outputList, parameters) {
    if(this.fatalError) {
        return false;
    }

    const output = outputList[0];
    const channelCount = Math.min(this.buffers.length, output.length);
    const frameSize = output[0].length;

    // Can't write a full frame yet, buffer for later. Return silence for now.
    if (frameSize > this.usedSpace) {
        for (let channel = 0; channel < channelCount; channel++) {
            output[channel].fill(0);
        }
        return true;
    }

    if(!this.started){
        if(this.usedSpace>=this.bufferSize/2){
            this.started = true;
        }
        return true;
    }

    let write1 = frameSize;
    let write2 = 0;
    if ( this.readIndex > this.writeIndex) {
        write1 = Math.min(frameSize, this.bufferSize - this.readIndex);
        write2 = (write1==frameSize)?0:(frameSize-write1);
    }

    for (let channel = 0; channel < channelCount; channel++) {
        output[channel].set(this.buffers[channel].subarray(this.readIndex, this.readIndex + write1));
        if(write2 > 0 ){
            output[channel].set(this.buffers[channel].subarray(0,write2), write1);
        }
    }

    // Update the write index and the used space
    this.readIndex = (this.readIndex + frameSize) % this.bufferSize;
    this.usedSpace -= frameSize;

    return true;
  }

  // Function to copy audio data into the circular buffer for a given channel
  writeToBuffer(audioData) {
    if (audioData.length === 0)
        return;

    const spaceNeeded = audioData[0].length;
    if( spaceNeeded > this.bufferSize ){
        this.port.postMessage({ type: 'error', message: "Data exceeds size of buffers allocated in stream-processor.js" });
        this.fatalError = 1;
    }
    const remainingSpace = this.bufferSize - this.usedSpace;

    // Can't write out a frame. Shouldn't happen, but whatever. Just drop it
    if ( remainingSpace < spaceNeeded ) {
        return;
    }

    const channelCount = Math.min(this.buffers.length, audioData.length);

    let write1 = spaceNeeded;
    let write2 = 0;
    if(this.writeIndex >= this.readIndex){
        write1 = Math.min(spaceNeeded, this.bufferSize - this.writeIndex);
        write2 = (write1 == spaceNeeded)?0:(spaceNeeded - write1);
    }
    if( write2 == 0 ){
        for (let channel = 0; channel < channelCount; channel++) {
            this.buffers[channel].set(audioData[channel], this.writeIndex);
        }
    } else {
        for (let channel = 0; channel < channelCount; channel++) {
            this.buffers[channel].set(audioData[channel].subarray(0, write1), this.writeIndex);
            this.buffers[channel].set(audioData[channel].subarray(write1), 0);
        }
    }
    // Update the write index and the used space
    this.writeIndex = (this.writeIndex + spaceNeeded) % this.bufferSize;
    this.usedSpace += spaceNeeded;
  }
}

// Register the worklet processor
registerProcessor('stream-processor', StreamProcessor);
