import { resample } from "wave-resampler";

export interface AudioRecorderConfig {
  sampleRate?: 8000 | 16000;
}

enum AudioRecorderState {
  Recording = "Recording",
  Stop = "Stop",
  Error = "Error",
}

const DEFAULT_CONFIG: Required<AudioRecorderConfig> = {
  sampleRate: 16000,
};

export class AudioRecorder {
  static isPermissionEnabled = async () => {
    let hasPermission = false;
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

      if (stream) {
        setTimeout(() => {
          stream.getTracks().forEach(track => track.stop());
        });
        hasPermission = true;
      }
    } catch (error) {
      throw new Error(`checkPermissions error:${error}`);
    }

    return hasPermission;
  };

  // event Callback
  onStart: (() => void) | null = null;
  onStop: (() => void) | null = null;
  onError: ((error: Error) => void) | null = null;
  onDataAvailable: ((data: ArrayBuffer) => void) | null = null;
  onFrequency: ((event: { data: Uint8Array }) => void) | null = null;

  // 录音实例
  private audioContext: AudioContext;

  private mediaStream: MediaStream | null = null;

  private mediaSourceNode: MediaStreamAudioSourceNode | null = null;

  private scriptNode: ScriptProcessorNode | null = null;

  // 录音配置
  private config: AudioRecorderConfig = {};

  // 录音器状态
  private _state: AudioRecorderState = AudioRecorderState.Stop;

  get state() {
    return this._state;
  }

  constructor() {
    if (!(window.AudioContext || (window as any).webkitAudioContext)) {
      throw new Error("your browser can not support audio recorder");
    }

    this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
  }

  // 开始录音
  start = async (config?: AudioRecorderConfig) => {
    try {
      Object.assign(this.config, config || {});

      await this.audioContext.resume();
      this.mediaStream = await navigator.mediaDevices.getUserMedia({
        audio: {
          channelCount: 1,
          noiseSuppression: false,
          echoCancellation: false,
        },
      });

      this.scriptProcessorNodeRecord();
      this.setRecordState(AudioRecorderState.Recording);
      this.onStart?.();
    } catch (error: any) {
      this.onError?.(new Error(error?.message || "start record error"));
      this.setRecordState(AudioRecorderState.Error);
    }
  };

  // 停止录音
  stop = () => {
    if (!this.mediaStream) return;

    this.mediaStream?.getTracks()?.forEach?.(track => {
      track.stop();
    });
    this.scriptNode?.disconnect?.();
    this.mediaSourceNode?.disconnect?.();

    this.setRecordState(AudioRecorderState.Stop);
    this.onStop?.();
  };

  analyzeAudio = (options?: { fftSize: number }) => {
    if (!this.mediaStream) return;
    if (!this.mediaSourceNode) return;

    const { fftSize = 512 } = options || {};

    const analyser = this.audioContext.createAnalyser();
    analyser.fftSize = fftSize;

    this.mediaSourceNode.connect(analyser);

    const frequencyData = new Uint8Array(analyser.frequencyBinCount);
    this.getFrequency(analyser, frequencyData);
  };

  // ScriptProcessorNode 获取录音数据
  private scriptProcessorNodeRecord() {
    if (!this.mediaStream) return;
    this.mediaSourceNode = this.audioContext.createMediaStreamSource(this.mediaStream);

    this.scriptNode = this.audioContext.createScriptProcessor(2048, 1, 1);
    this.scriptNode.onaudioprocess = (e: AudioProcessingEvent) => {
      const inputData = e.inputBuffer.getChannelData(0).slice(0);
      const resampleData = resample(
        inputData,
        this.audioContext.sampleRate,
        this.config.sampleRate || DEFAULT_CONFIG.sampleRate
      );

      const inputData16 = new Int16Array(resampleData.length);
      for (let i = 0; i < resampleData.length; i++) {
        // PCM 16-bit
        inputData16[i] = Math.max(-1, Math.min(1, resampleData[i])) * 0x7fff;
      }

      this.onDataAvailable?.(inputData16.buffer);
    };
    this.mediaSourceNode.connect(this.scriptNode);
    this.scriptNode.connect(this.audioContext.destination);
  }

  // 计算音频帧
  private getFrequency(analyser: AnalyserNode, frequencyData: Uint8Array) {
    if (this.state !== AudioRecorderState.Recording) return;

    analyser.getByteFrequencyData(frequencyData);
    this.onFrequency?.({ data: frequencyData });

    window.requestAnimationFrame(() => {
      this.getFrequency(analyser, frequencyData);
    });
  }

  private setRecordState(state: AudioRecorderState) {
    this._state = state;
  }
}
