/* eslint-disable no-console */

/**
 * Sound types that can be played during a call
 */
export enum CallSoundType {
  MicMute = 'mic_mute',
  MicUnmute = 'mic_unmute',
  Deafen = 'deafen',
  Undeafen = 'undeafen',
  CallJoined = 'call_joined',
  CallDepart = 'call_depart',
  CallDialing = 'call_dialing',
}

const SOUND_FILES: Record<CallSoundType, string> = {
  [CallSoundType.MicMute]: 'Mic_Mute.ogg',
  [CallSoundType.MicUnmute]: 'Mic_Unmuted.ogg',
  [CallSoundType.Deafen]: 'Speakers_Deafen.ogg',
  [CallSoundType.Undeafen]: 'Speakers_Undeafen.ogg',
  [CallSoundType.CallJoined]: 'Call_Joined.ogg',
  [CallSoundType.CallDepart]: 'Call_Depart.ogg',
  [CallSoundType.CallDialing]: 'Call_Dialing.ogg',
};

/**
 * Resolves the base URL for sounds.
 * In Electron production, sounds are in resources/sound/ as plain files.
 * In dev or browser, sounds are served from public/sound/.
 */
async function resolveSoundBaseUrl(): Promise<string> {
  const electronAudio = (window as any).electron?.audio;
  if (electronAudio?.getSoundBaseUrl) {
    const base: string | null = await electronAudio.getSoundBaseUrl();
    if (base) return base;
  }
  return './sound';
}

/**
 * Manages call-related sound effects.
 * Handles playing local sounds and coordinating with remote participants.
 */
export class CallSounds {
  private audioElements: Map<CallSoundType, HTMLAudioElement> = new Map();

  private dialingLoopElement: HTMLAudioElement | null = null;

  private isDeafened = false;

  constructor() {
    resolveSoundBaseUrl().then((base) => this.preloadSounds(base));
  }

  /**
   * Preloads all call sounds for instant playback
   */
  private preloadSounds(base: string): void {
    Object.entries(SOUND_FILES).forEach(([type, file]) => {
      const audio = new Audio(`${base}/${file}`);
      audio.preload = 'auto';
      this.audioElements.set(type as CallSoundType, audio);
    });
  }

  /**
   * Plays a call sound locally
   * @param soundType - The type of sound to play
   * @param loop - Whether to loop the sound (e.g., for dialing)
   */
  playSound(soundType: CallSoundType, loop = false): void {
    // Don't play sounds if deafened (except deafen/undeafen sounds for feedback)
    if (this.isDeafened && 
        soundType !== CallSoundType.Deafen && 
        soundType !== CallSoundType.Undeafen) {
      return;
    }

    const audio = this.audioElements.get(soundType);
    if (!audio) {
      console.warn(`Sound not found: ${soundType}`);
      return;
    }

    // Stop any currently playing instance and reset
    audio.currentTime = 0;
    audio.loop = loop;

    // Track looping dialing sound separately
    if (soundType === CallSoundType.CallDialing && loop) {
      this.dialingLoopElement = audio;
    }

    audio.play().catch((err) => {
      console.warn(`Failed to play sound ${soundType}:`, err);
    });
  }

  /**
   * Stops the dialing loop sound if playing
   */
  stopDialingLoop(): void {
    if (this.dialingLoopElement) {
      this.dialingLoopElement.pause();
      this.dialingLoopElement.currentTime = 0;
      this.dialingLoopElement.loop = false;
      this.dialingLoopElement = null;
    }
  }

  /**
   * Sets the deafened state - affects whether sounds are played
   * @param deafened - Whether the user is deafened
   */
  setDeafened(deafened: boolean): void {
    this.isDeafened = deafened;
  }

  /**
   * Gets whether sounds are currently deafened
   */
  getIsDeafened(): boolean {
    return this.isDeafened;
  }

  /**
   * Cleans up audio resources
   */
  dispose(): void {
    this.stopDialingLoop();
    this.audioElements.forEach((audio) => {
      audio.pause();
    });
    this.audioElements.clear();
  }
}

/**
 * Singleton instance of CallSounds for app-wide use
 */
let callSoundsInstance: CallSounds | null = null;

/**
 * Gets or creates the CallSounds singleton instance
 */
export function getCallSounds(): CallSounds {
  if (!callSoundsInstance) {
    callSoundsInstance = new CallSounds();
  }
  return callSoundsInstance;
}

/**
 * Disposes the CallSounds singleton
 */
export function disposeCallSounds(): void {
  if (callSoundsInstance) {
    callSoundsInstance.dispose();
    callSoundsInstance = null;
  }
}
