import { AudioEventCallback, AudioEventName } from './events/types';
import type {
  AnalyserOptions,
  AudioBufferSourceOptions,
  AudioRecorderCallbackOptions,
  AudioRecorderFileOptions,
  AutomationEventData,
  BaseAudioBufferSourceOptions,
  BiquadFilterOptions,
  BiquadFilterType,
  ChannelCountMode,
  ChannelInterpretation,
  ConstantSourceOptions,
  ConvolverOptions,
  ContextState,
  DelayOptions,
  FileInfo,
  GainOptions,
  IIRFilterOptions,
  OscillatorOptions,
  OscillatorType,
  OverSampleType,
  Result,
  StereoPannerOptions,
  StreamerOptions,
  WaveShaperOptions,
  AudioFileSourceOptions,
} from './types';

// IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer

export type WorkletNodeCallback = (
  audioData: Array<ArrayBuffer>,
  channelCount: number
) => void;

export type WorkletSourceNodeCallback = (
  audioData: Array<ArrayBuffer>,
  framesToProcess: number,
  currentTime: number,
  startOffset: number
) => void;

export type WorkletProcessingNodeCallback = (
  inputData: Array<ArrayBuffer>,
  outputData: Array<ArrayBuffer>,
  framesToProcess: number,
  currentTime: number
) => void;

export type ShareableWorkletCallback =
  | WorkletNodeCallback
  | WorkletSourceNodeCallback
  | WorkletProcessingNodeCallback;

export interface IBaseAudioContext {
  readonly destination: IAudioDestinationNode;
  readonly state: ContextState;
  readonly sampleRate: number;
  readonly currentTime: number;
  readonly decoder: IAudioDecoder;

  createRecorderAdapter(): IRecorderAdapterNode;
  createWorkletSourceNode(
    shareableWorklet: ShareableWorkletCallback,
    shouldUseUiRuntime: boolean
  ): IWorkletSourceNode;
  createWorkletNode(
    shareableWorklet: ShareableWorkletCallback,
    shouldUseUiRuntime: boolean,
    bufferLength: number,
    inputChannelCount: number
  ): IWorkletNode;
  createWorkletProcessingNode(
    shareableWorklet: ShareableWorkletCallback,
    shouldUseUiRuntime: boolean
  ): IWorkletProcessingNode;
  createOscillator(oscillatorOptions: OscillatorOptions): IOscillatorNode;
  createConstantSource(
    constantSourceOptions: ConstantSourceOptions
  ): IConstantSourceNode;
  createGain(gainOptions: GainOptions): IGainNode;
  createStereoPanner(
    stereoPannerOptions: StereoPannerOptions
  ): IStereoPannerNode;
  createBiquadFilter: (
    biquadFilterOptions: BiquadFilterOptions
  ) => IBiquadFilterNode;
  createBufferSource: (
    audioBufferSourceOptions: AudioBufferSourceOptions
  ) => IAudioBufferSourceNode;
  createDelay(delayOptions: DelayOptions): IDelayNode;
  createIIRFilter: (IIRFilterOptions: IIRFilterOptions) => IIIRFilterNode;
  createBufferQueueSource: (
    audioBufferQueueSourceOptions: BaseAudioBufferSourceOptions
  ) => IAudioBufferQueueSourceNode;
  createPeriodicWave: (
    real: Float32Array,
    imag: Float32Array,
    disableNormalization: boolean
  ) => IPeriodicWave;
  createAnalyser: (analyserOptions: AnalyserOptions) => IAnalyserNode;
  createConvolver: (convolverOptions: ConvolverOptions) => IConvolverNode;
  createStreamer: (streamerOptions: StreamerOptions) => IStreamerNode | null; // null when FFmpeg is not enabled
  createWaveShaper: (waveShaperOptions: WaveShaperOptions) => IWaveShaperNode;
  createFileSource: (
    audioFileOptions: AudioFileSourceOptions
  ) => IAudioFileSourceNode | null; // null when FFmpeg is not enabled, but needed
}

export interface IAudioContext extends IBaseAudioContext {
  createMediaElementSource: (
    mediaElement: IAudioFileSourceNode
  ) => IMediaElementAudioSourceNode;
  close(): Promise<void>;
  resume(): Promise<void>;
  suspend(): Promise<void>;
}

export interface IOfflineAudioContext extends IBaseAudioContext {
  resume(): Promise<void>;
  suspend(suspendTime: number): Promise<void>;
  startRendering(): Promise<IAudioBuffer>;
}

export interface IAudioNode {
  readonly context: BaseAudioContext;
  readonly numberOfInputs: number;
  readonly numberOfOutputs: number;
  readonly channelCount: number;
  readonly channelCountMode: ChannelCountMode;
  readonly channelInterpretation: ChannelInterpretation;

  connect: (destination: IAudioNode | IAudioParam) => void;
  disconnect: (destination?: IAudioNode | IAudioParam) => void;
}

export interface IDelayNode extends IAudioNode {
  readonly delayTime: IAudioParam;
  maxDelayTime: number;
}

export interface IGainNode extends IAudioNode {
  readonly gain: IAudioParam;
}

export interface IStereoPannerNode extends IAudioNode {
  readonly pan: IAudioParam;
}

export interface IBiquadFilterNode extends IAudioNode {
  readonly frequency: IAudioParam;
  readonly detune: IAudioParam;
  readonly Q: IAudioParam;
  readonly gain: IAudioParam;
  type: BiquadFilterType;

  getFrequencyResponse(
    frequencyArray: Float32Array,
    magResponseOutput: Float32Array,
    phaseResponseOutput: Float32Array
  ): void;
}

export interface IIIRFilterNode extends IAudioNode {
  getFrequencyResponse(
    frequencyArray: Float32Array,
    magResponseOutput: Float32Array,
    phaseResponseOutput: Float32Array
  ): void;
}

export interface IAudioDestinationNode extends IAudioNode {}

export interface IAudioScheduledSourceNode extends IAudioNode {
  start(when: number): void;
  stop: (when: number) => void;

  // passing subscriptionId(uint_64 in cpp, string in js) to the cpp
  onEnded: string;
}

export interface IAudioBufferBaseSourceNode extends IAudioScheduledSourceNode {
  detune: IAudioParam;
  playbackRate: IAudioParam;

  getInputLatency: () => number;
  getOutputLatency: () => number;

  // passing subscriptionId(uint_64 in cpp, string in js) to the cpp
  onPositionChanged: string;
  // set how often the onPositionChanged event is called
  onPositionChangedInterval: number;
}

export interface IOscillatorNode extends IAudioScheduledSourceNode {
  readonly frequency: IAudioParam;
  readonly detune: IAudioParam;
  type: OscillatorType;

  setPeriodicWave(periodicWave: IPeriodicWave): void;
}

export interface IStreamerNode extends IAudioNode {}

export interface IConstantSourceNode extends IAudioScheduledSourceNode {
  readonly offset: IAudioParam;
}

export interface IAudioBufferSourceNode extends IAudioBufferBaseSourceNode {
  buffer: IAudioBuffer | null;
  loop: boolean;
  loopSkip: boolean;
  loopStart: number;
  loopEnd: number;

  start: (when?: number, offset?: number, duration?: number) => void;
  setBuffer: (audioBuffer: IAudioBuffer | null) => void;

  // passing subscriptionId(uint_64 in cpp, string in js) to the cpp
  onLoopEnded: string;
}

export interface IAudioBufferQueueSourceNode extends IAudioBufferBaseSourceNode {
  dequeueBuffer: (bufferId: number) => void;
  clearBuffers: () => void;

  // returns bufferId
  enqueueBuffer: (audioBuffer: IAudioBuffer) => string;
  start: (when?: number, offset?: number) => void;
  pause: () => void;

  // passing subscriptionId(uint_64 in cpp, string in js) to the cpp
  onBufferEnded: string;
}

export interface IAudioFileSourceNode extends IAudioScheduledSourceNode {
  volume?: number;
  playbackRate: number;
  preservesPitch: boolean;
  loop: boolean;
  readonly currentTime: number;
  readonly duration: number;
  readonly routedThroughMediaElement: boolean;
  pause: () => void;
  seekToTime: (seconds: number) => void;

  // passing subscriptionId(uint_64 in cpp, string in js) to the cpp
  onPositionChanged: string;
}

export interface IMediaElementAudioSourceNode extends IAudioNode {}

export interface IConvolverNode extends IAudioNode {
  readonly buffer: IAudioBuffer | null;
  normalize: boolean;

  setBuffer: (audioBuffer: IAudioBuffer | null) => void;
}

export interface IAudioBuffer {
  readonly length: number;
  readonly duration: number;
  readonly sampleRate: number;
  readonly numberOfChannels: number;

  getChannelData(channel: number): Float32Array<ArrayBuffer>;
  copyFromChannel(
    destination: Float32Array<ArrayBuffer>,
    channelNumber: number,
    startInChannel: number
  ): void;
  copyToChannel(
    source: Float32Array<ArrayBuffer>,
    channelNumber: number,
    startInChannel: number
  ): void;
}

export interface IAudioParam {
  value: number;
  defaultValue: number;
  minValue: number;
  maxValue: number;

  setValueAtTime: (value: number, startTime: number) => void;
  linearRampToValueAtTime: (value: number, endTime: number) => void;
  exponentialRampToValueAtTime: (value: number, endTime: number) => void;
  setTargetAtTime: (
    target: number,
    startTime: number,
    timeConstant: number
  ) => void;
  setValueCurveAtTime: (
    values: Float32Array,
    startTime: number,
    duration: number
  ) => void;
  cancelScheduledValues: (cancelTime: number) => void;
  cancelAndHoldAtTime: (cancelTime: number) => void;
  checkCurveExclusion: (eventData: AutomationEventData) => Result<void>;
}

export interface IPeriodicWave {}

export interface IAnalyserNode extends IAudioNode {
  fftSize: number;
  readonly frequencyBinCount: number;
  minDecibels: number;
  maxDecibels: number;
  smoothingTimeConstant: number;

  getFloatFrequencyData: (array: Float32Array) => void;
  getByteFrequencyData: (array: Uint8Array) => void;
  getFloatTimeDomainData: (array: Float32Array) => void;
  getByteTimeDomainData: (array: Uint8Array) => void;
}

export interface IRecorderAdapterNode extends IAudioNode {}

export interface IWorkletNode extends IAudioNode {}

export interface IWorkletSourceNode extends IAudioScheduledSourceNode {}

export interface IWorkletProcessingNode extends IAudioNode {}

export interface IWaveShaperNode extends IAudioNode {
  readonly curve: Float32Array | null;
  oversample: OverSampleType;

  setCurve(curve: Float32Array | null): void;
}
export interface IAudioRecorderCallbackOptions extends AudioRecorderCallbackOptions {
  callbackId: string;
}

export interface IAudioRecorder {
  // default recorder methods
  start: (fileNameOverride?: string) => Promise<Result<{}>>;
  stop: () => Promise<Result<FileInfo>>;
  isRecording: () => boolean;
  isPaused: () => boolean;

  enableFileOutput: (options: AudioRecorderFileOptions) => Result<{}>;
  disableFileOutput: () => void;

  // pause and resume methods for file recording
  pause: () => void;
  resume: () => void;

  // Graph integration methods
  connect: (node: IRecorderAdapterNode) => void;
  disconnect: () => void;

  setOnAudioReady: (options: IAudioRecorderCallbackOptions) => Result<void>;
  clearOnAudioReady: () => void;

  setOnError: (options: { callbackId: string }) => void;
  clearOnError: () => void;

  getCurrentDuration: () => number;
  getFilePath: () => string | null;
}

export interface IAudioDecoder {
  decodeWithMemoryBlock: (
    arrayBuffer: ArrayBuffer,
    sampleRate?: number
  ) => Promise<IAudioBuffer>;
  decodeWithFilePath: (
    sourcePath: string,
    sampleRate?: number
  ) => Promise<IAudioBuffer>;
  decodeWithPCMInBase64: (
    b64: string,
    inputSampleRate: number,
    inputChannelCount: number,
    interleaved?: boolean
  ) => Promise<IAudioBuffer>;
}

export interface IAudioFileUtils {
  concatAudioFiles: (
    inputPaths: string[],
    outputPath: string
  ) => Promise<string>;
  probeDuration: (
    input: ArrayBuffer | string,
    sampleRate?: number,
    headers?: Record<string, string>
  ) => Promise<number | null>;
}

export interface IAudioEventEmitter {
  addAudioEventListener<Name extends AudioEventName>(
    name: Name,
    callback: AudioEventCallback<Name>
  ): string;
  removeAudioEventListener<Name extends AudioEventName>(
    name: Name,
    subscriptionId: string
  ): void;
}
