import {useEffect, useState} from 'react';
import {
  audioPlayer,
  AudioPlayerInterface,
  AudioPlayerReplaceAdditionalOptions,
} from '@modules/chats/instances/AudioPlayer';
import {useTrackStateStore} from '@modules/chats/store/zustand/useTrackStateStore';

export interface AudioTrack {
  id: string;
  uri: string;
  title?: string;
  duration?: number;
}

export interface TrackPlayerState {
  isPlaying: boolean;
  isPaused: boolean;
  isLoading: boolean;
  currentTrack: AudioTrack | null;
  currentIndex: number;
  position: number;
  duration: number;
  playbackRate: number;
  error?: string;
}

export type TrackPlayerEvent =
  | 'track-changed'
  | 'playback-state-changed'
  | 'playback-progress-updated'
  | 'player-error'
  | 'playlist-ended';

type EventCallback = (state: TrackPlayerState) => void;

const {resetTrackProgress} = useTrackStateStore.getState();

class TrackPlayerClass {
  private player: AudioPlayerInterface = audioPlayer;
  private playlist: AudioTrack[] = [];
  private currentIndex: number = 0;
  private playbackRate: number = 1.0;
  private eventListeners: Map<TrackPlayerEvent, EventCallback[]> = new Map();
  private statusUpdateInterval: Nullable<NodeJS.Timeout> = null;
  private hasPlaylistEnded: boolean = false;
  private seekDebounceTimer: Nullable<NodeJS.Timeout> = null;

  private state: TrackPlayerState = {
    isPlaying: false,
    isPaused: false,
    isLoading: false,
    currentTrack: null,
    currentIndex: 0,
    position: 0,
    duration: 0,
    playbackRate: 1.0,
  };

  /**
   * Updates the internal state
   */
  private updateState(updates: Partial<TrackPlayerState>): void {
    this.state = {...this.state, ...updates};
  }

  /**
   * Handles errors in a centralized way
   */
  private handleError(message: string, error: unknown): void {
    const errorMessage = error instanceof Error ? error.message : String(error);
    this.updateState({
      error: `${message}: ${errorMessage}`,
    });
    this.emit('player-error');
  }

  /**
   * Sets up the playlist
   */
  setPlaylist(tracks: AudioTrack[], startIndex: number = 0): void {
    try {
      this.playlist = tracks;
      this.currentIndex = Math.max(0, Math.min(startIndex, tracks.length - 1));
      this.hasPlaylistEnded = false;

      if (tracks.length > 0) {
        this.updateState({
          currentTrack: tracks[this.currentIndex],
          currentIndex: this.currentIndex,
        });
      }
    } catch (error) {
      this.handleError('Error setting up playlist', error);
    }
  }

  /**
   * Adds a track to the playlist
   */
  addTrack(track: AudioTrack, index?: number): number {
    if (index !== undefined && index >= 0 && index <= this.playlist.length) {
      this.playlist.splice(index, 0, track);
      if (index <= this.currentIndex) {
        this.currentIndex++;
      }
      return index;
    } else {
      this.playlist.push(track);
      return this.playlist.findIndex(t => t.id === track.id);
    }
  }

  /**
   * Removes a track from the playlist
   */
  removeTrack(index: number): void {
    if (index >= 0 && index < this.playlist.length) {
      this.playlist.splice(index, 1);

      if (index === this.currentIndex) {
        // If it's the current track, load the next or previous one
        if (this.currentIndex >= this.playlist.length) {
          this.currentIndex = Math.max(0, this.playlist.length - 1);
        }
        if (this.playlist.length > 0) {
          this.loadTrack(this.currentIndex);
        }
      } else if (index < this.currentIndex) {
        this.currentIndex--;
      }
    }
  }

  /**
   * Loads a specific track
   */
  private async loadTrack(
    index: number,
    options?: AudioPlayerReplaceAdditionalOptions,
  ): Promise<void> {
    if (index < 0 || index >= this.playlist.length) return;
    try {
      this.updateState({isLoading: true});
      // Cleanup previous player if exists
      if (this.player) {
        this.stopStatusUpdates();
        const track = this.playlist[index];
        const loadedTrack = await this.player.replace({
          uri: track.uri,
          ...options,
        });

        this.currentIndex = index;
        this.updateState({
          currentTrack: track,
          currentIndex: index,
          isLoading: false,
          duration: loadedTrack.duration || 0,
        });
        // start tracking only if auto-play otherwise start in the play method
        if (options?.play) {
          // Start status updates
          this.startStatusUpdates();
        }
      }
    } catch (error) {
      this.updateState({isLoading: false});
      this.handleError('Error loading track', error);
    }
  }

  /**
   * Starts periodic status updates
   */
  private startStatusUpdates(): void {
    this.stopStatusUpdates();
    this.statusUpdateInterval = setInterval(() => {
      // If seeking, skip update
      if (this.player && !this.seekDebounceTimer) {
        this.updatePlayerState();
      }
    }, 50);
  }

  /**
   * Stops periodic status updates
   */
  private stopStatusUpdates(): void {
    if (this.statusUpdateInterval) {
      clearInterval(this.statusUpdateInterval);
      this.statusUpdateInterval = null;
    }
  }

  /**
   * Updates the player state
   */
  private updatePlayerState() {
    if (!this.player) return;

    const wasPlaying = this.state.isPlaying;
    const wasPaused = this.state.isPaused;
    const previousPosition = this.state.position;

    const {isPlaying, isPaused, playbackRate, duration, position} =
      this.player.getState();

    this.updateState({
      isPlaying,
      isPaused,
      position,
      duration,
      playbackRate,
    });

    // Emit progress update if position changed or if playing
    if (Math.abs(position - previousPosition) >= 0.05 || isPlaying) {
      this.emit('playback-progress-updated');
    }

    // Detect playback state changes
    if (wasPlaying !== isPlaying || wasPaused !== isPaused) {
      this.emit('playback-state-changed');
    }

    if (duration > 0 && position >= duration && !this.hasPlaylistEnded) {
      this.handleTrackEnd();
    }
  }

  /**
   * Handles the end of a track
   */
  private handleTrackEnd(): void {
    // Reset the current track progress on end
    if (this.state.currentTrack?.id) {
      resetTrackProgress(this.state.currentTrack.id);
    }

    // Linear playback - go to next track or end playlist
    if (this.currentIndex < this.playlist.length - 1) {
      this.hasPlaylistEnded = false;
      this.skipToNext();
    } else {
      this.hasPlaylistEnded = true;
      this.emit('playlist-ended');

      // Stop sending updates, we need a small timeout to send last update
      setTimeout(() => {
        this.stopStatusUpdates();
      }, 100);
    }
  }

  /**
   * Plays the audio
   */
  async play(): Promise<void> {
    try {
      if (!this.player && this.playlist.length > 0) {
        this.loadTrack(this.currentIndex);
      }

      if (this.player) {
        this.hasPlaylistEnded = false;
        const promise = this.player.play();
        this.updateState({isPlaying: true, isPaused: false});
        this.emit('playback-state-changed');
        // Wait for the play promise before start requesting player state
        await promise;
        this.startStatusUpdates();
      }
    } catch (error) {
      this.handleError('Error playing audio', error);
    }
  }

  /**
   * Pauses the audio
   */
  pause(): void {
    try {
      if (this.player) {
        this.player.pause();
        this.updateState({isPlaying: false, isPaused: true});
        this.updatePlayerState();
        this.emit('playback-state-changed');
      }
    } catch (error) {
      this.handleError('Error pausing audio', error);
    }
  }

  /**
   * Stops the audio and resets position
   */
  stop() {
    try {
      if (this.player) {
        this.player.pause();
        this.player.seekTo(0);
        this.updateState({isPlaying: false, isPaused: false, position: 0});
        this.emit('playback-state-changed');
      }
    } catch (error) {
      this.handleError('Error stopping audio', error);
    }
  }

  /**
   * Update playback rate on the player from saved state
   */
  syncPlaybackRate() {
    this.player.setPlaybackRate(this.playbackRate);
    this.updateState({playbackRate: this.playbackRate});
  }

  /**
   * Skips to the next track (linear)
   */
  skipToNext(): void {
    const nextIndex = this.currentIndex + 1;
    if (nextIndex < this.playlist.length) {
      this.skipToTrack(nextIndex, true);
    }
  }

  /**
   * Skips to the previous track (linear)
   */
  skipToPrevious(): void {
    const prevIndex = this.currentIndex - 1;
    if (prevIndex >= 0) {
      this.skipToTrack(prevIndex);
    }
  }

  /**
   * Skips to a specific track
   */
  async skipToTrack(
    index: number,
    forcePlay: boolean = false,
    options?: AudioPlayerReplaceAdditionalOptions,
  ): Promise<void> {
    if (index >= 0 && index < this.playlist.length) {
      this.hasPlaylistEnded = false;
      const wasPlaying = this.state.isPlaying;
      await this.loadTrack(index, options);
      this.emit('track-changed');
      if (wasPlaying || forcePlay) {
        this.play();
      }
    }
  }

  /**
   * Seeks to a specific position
   */
  seekTo(seconds: number) {
    try {
      if (this.player) {
        // Seek to with debounce
        if (this.seekDebounceTimer) {
          clearTimeout(this.seekDebounceTimer);
        }

        this.seekDebounceTimer = setTimeout(() => {
          this.player?.seekTo(seconds);
          clearTimeout(this.seekDebounceTimer!);
          this.seekDebounceTimer = null;
        }, 32); // Debounce for 32ms (~2 frames at 60fps)

        this.updateState({position: seconds});

        // Emit progress update after seeking
        this.emit('playback-progress-updated');
      }
    } catch (error) {
      this.handleError('Error seeking position', error);
    }
  }

  /**
   * Adjusts the playback rate
   */
  setPlaybackRate(rate: number): void {
    this.playbackRate = Math.max(0.5, Math.min(2.0, rate));
    if (this.player) {
      this.player.setPlaybackRate(this.playbackRate);
    }
    this.updateState({playbackRate: this.playbackRate});

    // Emit state change with updated playback rate
    this.emit('playback-state-changed');
  }

  /**
   * Gets the current state
   */
  getState(): TrackPlayerState {
    return {...this.state};
  }

  /**
   * Gets the current playlist
   */
  getPlaylist(): AudioTrack[] {
    return [...this.playlist];
  }

  /**
   * Gets the current track
   */
  getCurrentTrack(): AudioTrack | null {
    return this.playlist[this.currentIndex] || null;
  }

  /**
   * Subscribes to events
   */
  addEventListener(event: TrackPlayerEvent, callback: EventCallback): void {
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, []);
    }
    this.eventListeners.get(event)!.push(callback);
  }

  /**
   * Unsubscribes from events
   */
  removeEventListener(event: TrackPlayerEvent, callback: EventCallback): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      const index = listeners.indexOf(callback);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
  }
  /**
   * Emits an event
   */
  private emit(event: TrackPlayerEvent): void {
    const listeners = this.eventListeners.get(event);
    if (listeners) {
      const currentState = this.getState();
      listeners.forEach(callback => callback(currentState));
    }
  }

  /**
   * Cleans up resources
   */
  clear(): void {
    this.stopStatusUpdates();
    this.player.reset();
    this.playlist = [];
    this.eventListeners.clear();
    this.hasPlaylistEnded = false;

    this.updateState({
      isPlaying: false,
      isPaused: false,
      isLoading: false,
      currentTrack: null,
      currentIndex: 0,
      position: 0,
      duration: 0,
    });
  }
}

// Global player instance
export const TrackPlayer = new TrackPlayerClass();

// Custom hook to use the TrackPlayer in React
export const useTrackPlayer = () => {
  return TrackPlayer;
};

export const useTrackPlayerState = () => {
  const [state, setState] = useState<TrackPlayerState>(TrackPlayer.getState());

  useEffect(() => {
    // Function to update state only if it actually changed
    const updateState = (newState: TrackPlayerState) => {
      setState(prevState => {
        // Deep comparison to avoid unnecessary re-renders
        if (
          prevState.isPlaying === newState.isPlaying &&
          prevState.isPaused === newState.isPaused &&
          prevState.isLoading === newState.isLoading &&
          prevState.currentTrack?.id === newState.currentTrack?.id &&
          prevState.currentIndex === newState.currentIndex &&
          prevState.position === newState.position &&
          prevState.duration === newState.duration &&
          prevState.playbackRate === newState.playbackRate &&
          prevState.error === newState.error
        ) {
          return prevState; // No change, return same reference
        }
        return newState;
      });
    };

    // Register listeners
    TrackPlayer.addEventListener('track-changed', updateState);
    TrackPlayer.addEventListener('playback-state-changed', updateState);
    TrackPlayer.addEventListener('playback-progress-updated', updateState);
    TrackPlayer.addEventListener('player-error', updateState);
    TrackPlayer.addEventListener('playlist-ended', updateState);

    // Cleanup listeners
    return () => {
      TrackPlayer.removeEventListener('track-changed', updateState);
      TrackPlayer.removeEventListener('playback-state-changed', updateState);
      TrackPlayer.removeEventListener('playback-progress-updated', updateState);
      TrackPlayer.removeEventListener('player-error', updateState);
      TrackPlayer.removeEventListener('playlist-ended', updateState);
    };
  }, []);

  return state;
};

// Simplified hook to listen to specific events
export const useTrackPlayerEvent = (
  event: TrackPlayerEvent,
  callback: EventCallback,
) => {
  useEffect(() => {
    TrackPlayer.addEventListener(event, callback);
    return () => {
      TrackPlayer.removeEventListener(event, callback);
    };
  }, [event, callback]);
};

export default TrackPlayer;
