/**
 * Service pour gérer les opérations liées au player
 */

import type { Track } from '../types';

/**
 * Formate le temps en format MM:SS
 */
export function formatTime(seconds: number): string {
  if (isNaN(seconds) || !isFinite(seconds) || seconds < 0) {
    return '0:00';
  }

  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = Math.floor(seconds % 60);
  return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}

/**
 * Calcule le pourcentage de progression
 */
export function calculateProgress(
  currentTime: number,
  duration: number,
): number {
  if (!duration || duration === 0) {
    return 0;
  }
  return (currentTime / duration) * 100;
}

/**
 * Valide qu'une piste est valide
 */
export function isValidTrack(track: Track | null): boolean {
  if (!track) {
    return false;
  }
  return !!(track.id && track.title && track.url);
}

/**
 * Trouve l'index d'une piste dans la queue
 */
export function findTrackIndex(queue: Track[], trackId: string): number {
  return queue.findIndex((track) => track.id === trackId);
}

/**
 * Mélange un tableau de pistes
 */
export function shuffleTracks(tracks: Track[]): Track[] {
  const shuffled = [...tracks];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
}

/**
 * Types pour les callbacks d'événements audio
 */
export type AudioEventCallback = () => void;
export type TimeUpdateCallback = (currentTime: number) => void;
export type DurationChangeCallback = (duration: number) => void;
export type ErrorCallback = (error: Error) => void;

/**
 * Classe pour gérer l'élément audio HTML
 */
export class AudioPlayerService {
  private audioElement: HTMLAudioElement | null = null;
  private timeUpdateCallback: TimeUpdateCallback | null = null;
  private durationChangeCallback: DurationChangeCallback | null = null;
  private endedCallback: AudioEventCallback | null = null;
  private errorCallback: ErrorCallback | null = null;
  private playCallback: AudioEventCallback | null = null;
  private pauseCallback: AudioEventCallback | null = null;

  /**
   * Initialise le service avec un élément audio
   */
  initialize(audioElement: HTMLAudioElement): void {
    this.audioElement = audioElement;
    this.setupEventListeners();
  }

  /**
   * Configure les écouteurs d'événements
   */
  private setupEventListeners(): void {
    if (!this.audioElement) return;

    this.audioElement.addEventListener('timeupdate', this.handleTimeUpdate);
    this.audioElement.addEventListener(
      'loadedmetadata',
      this.handleLoadedMetadata,
    );
    this.audioElement.addEventListener(
      'durationchange',
      this.handleDurationChange,
    );
    this.audioElement.addEventListener('ended', this.handleEnded);
    this.audioElement.addEventListener('error', this.handleError);
    this.audioElement.addEventListener('play', this.handlePlay);
    this.audioElement.addEventListener('pause', this.handlePause);
  }

  /**
   * Nettoie les écouteurs d'événements
   */
  cleanup(): void {
    if (!this.audioElement) return;

    this.audioElement.removeEventListener('timeupdate', this.handleTimeUpdate);
    this.audioElement.removeEventListener(
      'loadedmetadata',
      this.handleLoadedMetadata,
    );
    this.audioElement.removeEventListener(
      'durationchange',
      this.handleDurationChange,
    );
    this.audioElement.removeEventListener('ended', this.handleEnded);
    this.audioElement.removeEventListener('error', this.handleError);
    this.audioElement.removeEventListener('play', this.handlePlay);
    this.audioElement.removeEventListener('pause', this.handlePause);

    this.audioElement = null;
  }

  /**
   * Charge une track dans l'élément audio
   */
  async loadTrack(track: Track | null): Promise<void> {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    if (!track) {
      this.audioElement.src = '';
      return;
    }

    if (!isValidTrack(track)) {
      throw new Error('Invalid track');
    }

    this.audioElement.src = track.url;
    this.audioElement.load();
  }

  /**
   * Joue l'audio
   */
  async play(): Promise<void> {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    try {
      await this.audioElement.play();
    } catch (error) {
      throw new Error(`Failed to play audio: ${error}`);
    }
  }

  /**
   * Met en pause l'audio
   */
  pause(): void {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    this.audioElement.pause();
  }

  /**
   * Arrête l'audio et remet à zéro
   */
  stop(): void {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    this.audioElement.pause();
    this.audioElement.currentTime = 0;
  }

  /**
   * Va à une position spécifique
   */
  seek(time: number): void {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    const duration = this.audioElement.duration || 0;
    const clampedTime = Math.max(0, Math.min(time, duration));
    this.audioElement.currentTime = clampedTime;
  }

  /**
   * Définit le volume (0-1)
   */
  setVolume(volume: number): void {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    const clampedVolume = Math.max(0, Math.min(1, volume));
    this.audioElement.volume = clampedVolume;
  }

  /**
   * Active ou désactive le mute
   */
  setMuted(muted: boolean): void {
    if (!this.audioElement) {
      throw new Error('Audio element not initialized');
    }

    this.audioElement.muted = muted;
  }

  /**
   * Obtient le temps actuel
   */
  getCurrentTime(): number {
    if (!this.audioElement) {
      return 0;
    }
    return this.audioElement.currentTime || 0;
  }

  /**
   * Obtient la durée
   */
  getDuration(): number {
    if (!this.audioElement) {
      return 0;
    }
    return this.audioElement.duration || 0;
  }

  /**
   * Obtient le volume
   */
  getVolume(): number {
    if (!this.audioElement) {
      return 1;
    }
    return this.audioElement.volume;
  }

  /**
   * Vérifie si l'audio est en cours de lecture
   */
  isPlaying(): boolean {
    if (!this.audioElement) {
      return false;
    }
    return !this.audioElement.paused && !this.audioElement.ended;
  }

  /**
   * Vérifie si l'audio est en mute
   */
  isMuted(): boolean {
    if (!this.audioElement) {
      return false;
    }
    return this.audioElement.muted;
  }

  // Handlers d'événements
  private handleTimeUpdate = (): void => {
    if (this.timeUpdateCallback && this.audioElement) {
      this.timeUpdateCallback(this.audioElement.currentTime);
    }
  };

  private handleLoadedMetadata = (): void => {
    if (this.durationChangeCallback && this.audioElement) {
      this.durationChangeCallback(this.audioElement.duration);
    }
  };

  private handleDurationChange = (): void => {
    if (this.durationChangeCallback && this.audioElement) {
      this.durationChangeCallback(this.audioElement.duration);
    }
  };

  private handleEnded = (): void => {
    if (this.endedCallback) {
      this.endedCallback();
    }
  };

  private handleError = (): void => {
    if (this.errorCallback && this.audioElement) {
      const error = new Error(
        this.audioElement.error?.message || 'Unknown audio error',
      );
      this.errorCallback(error);
    }
  };

  private handlePlay = (): void => {
    if (this.playCallback) {
      this.playCallback();
    }
  };

  private handlePause = (): void => {
    if (this.pauseCallback) {
      this.pauseCallback();
    }
  };

  // Méthodes pour enregistrer les callbacks
  onTimeUpdate(callback: TimeUpdateCallback | null): void {
    this.timeUpdateCallback = callback;
  }

  onDurationChange(callback: DurationChangeCallback | null): void {
    this.durationChangeCallback = callback;
  }

  onEnded(callback: AudioEventCallback | null): void {
    this.endedCallback = callback;
  }

  onError(callback: ErrorCallback | null): void {
    this.errorCallback = callback;
  }

  onPlay(callback: AudioEventCallback | null): void {
    this.playCallback = callback;
  }

  onPause(callback: AudioEventCallback | null): void {
    this.pauseCallback = callback;
  }
}

/**
 * Instance singleton du service audio
 */
export const audioPlayerService = new AudioPlayerService();
