import { makeAutoObservable } from 'mobx';
import { Recorder } from './models/Recorder';
import Microphone from './models/Microphone';
import Visualizer from './models/Visualizer';
import AudioSource from './models/AudioSource';
import Player from './models/Player';

/**
 * A library for recording audio, playing audio, and visualizing audio.
 */
export default class AudioX {
  /**
   * The recorder object
   */
  recorder: Recorder;
  /**
   * The microphone object
   */
  microphone: Microphone;
  /**
   * The audio source object
   */
  audioSource: AudioSource;
  /**
   * The recorded audio blob
   */
  recordedAudioBlob: Blob | undefined;
  /**
   * The recorded audio url
   */
  recordedAudioUrl: string;
  /**
   * The visualizer object
   */
  visualizer: Visualizer;
  /**
   * The player object
   */
  player: Player;
  /**
   * The voice memos
   */
  voiceMemos: any[];

  constructor() {
    this.recorder = new Recorder(this.onRecordingComplete);
    this.microphone = new Microphone();
    this.audioSource = new AudioSource();
    this.visualizer = new Visualizer();
    this.player = new Player();
    this.voiceMemos = [];
    if (typeof window != 'undefined') {
      this.recordedAudioBlob = new Blob();
    } else {
      this.recordedAudioBlob = undefined;
    }
    this.recordedAudioUrl = '';
    makeAutoObservable(this);
  }

  get currentVoiceMemos() {
    return this.voiceMemos;
  }

  get audioUrl() {
    return this.recordedAudioUrl;
  }

  setRecordedAudioUrl = (url: string) => {
    this.recordedAudioUrl = url;
  };

  onRecordingComplete = async (audio: Blob) => {
    this.recordedAudioBlob = audio;
    const url = URL.createObjectURL(audio);
    this.setRecordedAudioUrl(url);
    const player = new Player();
    player.setBlob(audio);
    player.loadMedia(url);
    this.voiceMemos = [...this.voiceMemos, { url, player }];
  };

  startRecording = async (showLiveFeedback?: boolean) => {
    const started = await this.microphone.startStream();
    if (started && this.microphone.streamingStream) {
      if (this.microphone.isReady) {
        await this.audioSource.initWithStream(
          this.microphone.streamingStream,
          showLiveFeedback
        );

        this.recorder.init(this.microphone.streamingStream);
        this.recorder.start();
        const canvas = document.getElementById(
          'visualizer'
        ) as HTMLCanvasElement;
        if (canvas) {
          this.visualizer.setCanvas(canvas);
          if (this.audioSource.analyzer) {
            this.visualizer.visualizeFreqBar(this.audioSource.analyzer);
          }
        }
      }
    }
  };

  stopRecording = () => {
    this.recorder.stop();
    this.microphone.pauseStreaming();
    this.visualizer.turnOff();
  };
}

const audioX = new AudioX();
/**
 * An observable hook for the audioX object.
 * Note: This is a singleton object and should be used with caution.
 * This will be a global object and will be shared across the application.
 * If you need to use different instances of the audioX object, you should create a new instance instead.
 * @returns The audioX object
 */
export const useAudioX = () => audioX;
