import React, {
  FC,
  PropsWithChildren,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from 'react';
import TrackPlayer, {
  AudioTrack,
  TrackPlayerState,
  useTrackPlayerState,
} from '@modules/chats/instances/TrackPlayer';
import {useTrackStateStore} from '@modules/chats/store/zustand/useTrackStateStore';

import {AudioPlayerContextProps} from './types';

const AudioPlayerContext = createContext<AudioPlayerContextProps>(
  {} as AudioPlayerContextProps,
);

export const useAudioTrackPlayer = () => {
  return useContext(AudioPlayerContext);
};

const {setProgress, getProgress, resetStore} = useTrackStateStore.getState();

export const AudioPlayerProvider: FC<PropsWithChildren> = ({children}) => {
  const [currentId, setCurrentId] = useState<string>('');
  const [rate, setRate] = useState(1);
  const state = useTrackPlayerState();

  const play = useCallback(async (audioTrack: AudioTrack) => {
    const currentQueue = TrackPlayer.getPlaylist();
    const existingIndex = currentQueue.findIndex(t => t.id === audioTrack.id);

    const savedProgress = getProgress(audioTrack.id);

    // If the saved progress is less than 0.5 seconds from the end, reset to start
    const shouldReset = savedProgress.duration - savedProgress.position < 0.5;

    await TrackPlayer.skipToTrack(existingIndex, false, {
      position: shouldReset ? 0 : savedProgress.position || 0,
    });

    TrackPlayer.play();
    setCurrentId(audioTrack.id);
  }, []);

  const pause = useCallback(() => {
    TrackPlayer.pause();
  }, []);

  const seekTo = useCallback(
    async (position: number, params?: {id: string; duration: number}) => {
      const currentTrack = TrackPlayer.getCurrentTrack();
      if (!currentTrack) {
        return;
      }

      // If the track isn't current, just update the progress and skip seeking
      if (params?.id && params.id !== currentTrack.id) {
        setProgress(params.id, {
          position: position,
          duration: params.duration,
        });
        return;
      }

      await TrackPlayer.seekTo(position);
    },
    [],
  );

  const toggleRate = useCallback(() => {
    setRate(prev => {
      const newValue = prev === 2 ? 1 : prev + 0.5;
      TrackPlayer.setPlaybackRate(newValue);
      return newValue;
    });
  }, []);

  useEffect(() => {
    setRate(TrackPlayer.getState().playbackRate || 1);
    TrackPlayer.syncPlaybackRate();
  }, []);

  const onTrackChangedEvent = useCallback(
    ({currentTrack}: TrackPlayerState) => {
      if (currentTrack) {
        setCurrentId(currentTrack.id);
      } else {
        setCurrentId('');
      }
    },
    [],
  );

  const onProgressUpdatedEvent = useCallback(
    ({position, duration, currentTrack}: TrackPlayerState) => {
      if (position > 0 && currentTrack) {
        setProgress(currentTrack.id, {
          position: position,
          duration: duration,
        });
      }
    },
    [],
  );
  useEffect(() => {
    TrackPlayer.addEventListener(
      'playback-progress-updated',
      onProgressUpdatedEvent,
    );
    TrackPlayer.addEventListener('playback-state-changed', onTrackChangedEvent);

    return () => {
      TrackPlayer.removeEventListener(
        'playback-progress-updated',
        onProgressUpdatedEvent,
      );
      TrackPlayer.removeEventListener(
        'playback-state-changed',
        onTrackChangedEvent,
      );
    };
  }, [onProgressUpdatedEvent, onTrackChangedEvent]);

  useEffect(() => {
    return () => {
      // try/catch prevent Sentry reports
      // In some cases, TrackPlayer may not be initialized yet
      try {
        TrackPlayer.stop();
        TrackPlayer.clear();
      } catch {
        // do nothing
      }
      setCurrentId('');
      resetStore();
    };
  }, []);

  const value = useMemo(() => {
    return {
      play,
      currentId,
      toggleRate,
      rate,
      pause,
      state,
      seekTo,
      initializePlaylist: TrackPlayer.setPlaylist.bind(TrackPlayer),
      addTrack: TrackPlayer.addTrack.bind(TrackPlayer),
    };
  }, [play, currentId, toggleRate, rate, pause, state, seekTo]);

  return (
    <AudioPlayerContext.Provider value={value}>
      {children}
    </AudioPlayerContext.Provider>
  );
};

export type {ProgressItem} from './types';
