import { useState, useRef, useEffect, SyntheticEvent } from 'react';

import { useAudio } from 'src/contexts/AudioContext';
import { formatToSecondsDurationAudio } from 'src/utils/attachments';
import { isFirefox } from 'src/utils/platform';

type UseAudioPlaybackParams = {
  src: string;
  step?: number;
  duration?: number;
  messageId?: string | number;
  onPlayError?: (error: unknown) => void;
  onTimeChangeError?: () => void;
};

export const useAudioPlayback = (params: UseAudioPlaybackParams) => {
  const {
    src,
    step = 0.2,
    duration,
    messageId,
    onPlayError = () => null,
    onTimeChangeError = () => null,
  } = params;

  const [currentTime, setCurrentTime] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [playbackRate, setPlaybackRate] = useState(1);
  const [hasBeenPlayed, setHasBeenPlayed] = useState(false);
  const [elementDurationSeconds, setElementDurationSeconds] = useState(0);

  const audioRef = useRef<HTMLAudioElement>(null);
  const {
    setAudioPlaying,
    audioPlayingId,
    isPlaying: isSomeAudioPlaying,
  } = useAudio();

  const safeDuration = duration ?? 0;
  const propDurationSeconds = formatToSecondsDurationAudio(safeDuration);
  const durationSeconds =
    propDurationSeconds > 0 ? propDurationSeconds : elementDurationSeconds;

  useEffect(() => {
    if (messageId == null) return;
    if (isSomeAudioPlaying && audioPlayingId !== messageId) {
      pauseAudio();
    }
    if (audioPlayingId !== messageId) {
      setHasBeenPlayed(false);
    }
  }, [isSomeAudioPlaying, audioPlayingId, hasBeenPlayed]);

  useEffect(() => {
    if (isFirefox() && audioRef?.current?.duration) {
      setIsLoading(false);
    }
  }, [audioRef?.current?.duration]);

  const pauseAudio = () => {
    setIsPlaying(false);
    audioRef.current?.pause();
  };

  const playAudio = async () => {
    try {
      setIsPlaying(true);
      if (messageId != null) {
        setAudioPlaying(messageId);
      }
      await audioRef.current?.play();
    } catch (error) {
      setIsPlaying(false);
      onPlayError(error);
    }
  };

  const togglePlay = (e: SyntheticEvent) => {
    e.stopPropagation();
    setHasBeenPlayed(true);

    const audio = audioRef.current;
    if (!audio) return;

    if (!isPlaying && audio.src) {
      playAudio();
    } else if (!audio.paused) {
      pauseAudio();
    }
  };

  const handleEnded = () => {
    setIsPlaying(false);
  };

  const handleTimeUpdate = () => {
    if (!audioRef?.current) return;
    const audio = audioRef.current;
    if (Number.isFinite(audio.currentTime)) {
      setCurrentTime(audio.currentTime);
    }
  };

  const setJumpTime = (time: number): void => {
    const audio = audioRef.current;
    if (!audio) return;

    const { readyState, duration: prevDuration, currentTime: prevTime } = audio;

    if (
      readyState === audio.HAVE_NOTHING ||
      readyState === audio.HAVE_METADATA ||
      !Number.isFinite(prevDuration) ||
      !Number.isFinite(prevTime)
    ) {
      return onTimeChangeError();
    }

    audio.currentTime = prevTime + time / 1000;
  };

  const handleClickRewind = (): void => {
    setJumpTime(-step * 1000);
  };

  const handleClickForward = (): void => {
    setJumpTime(step * 1000);
  };

  const handleKeyDown = (event: React.KeyboardEvent) => {
    switch (event.key) {
      case ' ':
        event.preventDefault();
        togglePlay(event);
        break;
      case 'ArrowLeft':
        handleClickRewind();
        break;
      case 'ArrowRight':
        handleClickForward();
        break;
      default:
        break;
    }
  };

  const handleSlideChange = (_event: Event, newValue: number | number[]) => {
    const value = Array.isArray(newValue) ? newValue[0] : newValue;
    if (audioRef.current) {
      audioRef.current.currentTime = value;
    }
    setCurrentTime(value);
  };

  const getCurrentTime = (): number => {
    if (!audioRef?.current?.src) return 0;
    return isPlaying ? currentTime : durationSeconds;
  };

  const handleLoadStart = () => {
    setIsLoading(true);
  };

  const handleCanPlayThrough = () => {
    setIsLoading(false);
  };

  const handleLoadedMetadata = () => {
    const audioDuration = audioRef.current?.duration;
    if (audioDuration && Number.isFinite(audioDuration)) {
      setElementDurationSeconds(audioDuration);
    }
  };

  const handleRateChange = (newRate: number) => {
    if (audioRef.current) {
      setPlaybackRate(newRate);
      audioRef.current.playbackRate = newRate;
    }
  };

  return {
    audioRef,
    currentTime,
    isPlaying,
    isLoading,
    playbackRate,
    hasBeenPlayed,
    safeDuration,
    durationSeconds,
    togglePlay,
    handleEnded,
    handleTimeUpdate,
    handleSlideChange,
    handleKeyDown,
    handleLoadStart,
    handleCanPlayThrough,
    handleLoadedMetadata,
    handleRateChange,
    getCurrentTime,
  } as const;
};
