import type { SerialTaskExecuteContext } from '../handler'
import type { PrivateCustomEventName } from '../types'

import { SerialHandler } from '../handler'
import { createEventBus } from '../utils'

const $bus = createEventBus<PrivateCustomEventName>()

class AudioActuator extends SerialHandler<Array<number>, never> {
  private audioContext: AudioContext = new AudioContext()

  private bufferSource: AudioBufferSourceNode | null = null

  private gainNode: GainNode | null = null

  private volume = 1

  public execute(
    context: SerialTaskExecuteContext<Array<number>, never>,
  ): void {
    if (context.isLastExecute) {
      this.taskCompletedCallback()
      return
    }

    const audioBuffer = this.audioContext.createBuffer(1, context.taskItem.original!.length, 22050)
    if (audioBuffer.copyToChannel) {
      audioBuffer.copyToChannel(new Float32Array(context.taskItem.original! as any), 0, 0)
    }

    this.bufferSource = this.audioContext.createBufferSource()
    this.bufferSource.buffer = audioBuffer

    this.gainNode = this.audioContext.createGain()
    this.bufferSource.connect(this.gainNode)
    this.gainNode.connect(this.audioContext.destination)

    this.gainNode.gain.setValueAtTime(this.volume, this.audioContext.currentTime)

    this.bufferSource.start()
    this.bufferSource.addEventListener('ended', () => {
      this.taskCompletedCallback()
    })
  }

  protected onFinish(): void {
    if (this.bufferSource) {
      this.bufferSource.stop()
      this.bufferSource = null
    }
    if (this.gainNode) {
      this.gainNode.disconnect()
      this.gainNode = null
    }
    if (this.audioContext) {
      this.audioContext.suspend()
      this.audioContext = new AudioContext()
    }
    $bus.emit('_audioActuatorFinish')
  }

  protected onBeforeFirstExecute(): void {
    $bus.emit('_audioActuatorBeforeFirstExecute')
  }

  public setVolume(volume: number): void {
    if (this.gainNode) {
      this.gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime)
    }
    this.volume = volume
  }
}

export default AudioActuator
