import updateVideoProgressById from '../api/actions/updateVideoProgressById';
import { SponsorBlockSegmentType, SponsorBlockType } from '../pages/Video';
import {
  Dispatch,
  Fragment,
  SetStateAction,
  SyntheticEvent,
  useEffect,
  useRef,
  useState,
} from 'react';
import formatTime from '../functions/formatTime';
import { useSearchParams } from 'react-router-dom';
import getApiUrl from '../configuration/getApiUrl';
import { useKeyPress } from '../functions/useKeypressHook';
import { VideoResponseType } from '../api/loader/loadVideoById';

const VIDEO_PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3];

type VideoTag = SyntheticEvent<HTMLVideoElement, Event>;

export type SkippedSegmentType = {
  from: number;
  to: number;
};

export type SponsorSegmentsSkippedType = Record<string, SkippedSegmentType>;

type Subtitle = {
  name: string;
  source: string;
  lang: string;
  media_url: string;
};

type SubtitlesProp = {
  subtitles: Subtitle[];
};

const Subtitles = ({ subtitles }: SubtitlesProp) => {
  return subtitles.map((subtitle: Subtitle) => {
    let label = subtitle.name;

    if (subtitle.source === 'auto') {
      label += ' - auto';
    }

    return (
      <track
        key={subtitle.name}
        label={label}
        kind="subtitles"
        srcLang={subtitle.lang}
        src={`${getApiUrl()}${subtitle.media_url}`}
      />
    );
  });
};

const handleTimeUpdate =
  (
    youtubeId: string,
    watched: boolean,
    sponsorBlock?: SponsorBlockType,
    setSponsorSegmentSkipped?: Dispatch<SetStateAction<SponsorSegmentsSkippedType>>,
    onWatchStateChanged?: (status: boolean) => void,
  ) =>
  async (videoTag: VideoTag) => {
    const currentTime = Number(videoTag.currentTarget.currentTime);

    if (sponsorBlock && sponsorBlock.segments) {
      sponsorBlock.segments.forEach((segment: SponsorBlockSegmentType) => {
        const [from, to] = segment.segment;

        if (currentTime >= from && currentTime <= from + 0.3) {
          videoTag.currentTarget.currentTime = to;

          setSponsorSegmentSkipped?.((segments: SponsorSegmentsSkippedType) => {
            return { ...segments, [segment.UUID]: { from, to } };
          });
        }

        if (currentTime > to + 10) {
          setSponsorSegmentSkipped?.((segments: SponsorSegmentsSkippedType) => {
            return { ...segments, [segment.UUID]: { from: 0, to: 0 } };
          });
        }
      });
    }

    if (currentTime < 10 && currentTime === Number(videoTag.currentTarget.duration)) return;
    if (Number((currentTime % 10).toFixed(1)) <= 0.2) {
      // Check progress every 10 seconds or else progress is checked a few times a second
      const videoProgressResponse = await updateVideoProgressById({
        youtubeId,
        currentProgress: currentTime,
      });

      const { data: videoProgressResponseData } = videoProgressResponse ?? {};

      if (videoProgressResponseData?.watched && watched !== videoProgressResponseData.watched) {
        onWatchStateChanged?.(true);
      }
    }
  };

type VideoPlayerProps = {
  video: VideoResponseType;
  sponsorBlock?: SponsorBlockType;
  embed?: boolean;
  autoplay?: boolean;
  onWatchStateChanged?: (status: boolean) => void;
  onVideoEnd?: () => void;
};

const VideoPlayer = ({
  video,
  sponsorBlock,
  embed,
  autoplay = false,
  onWatchStateChanged,
  onVideoEnd,
}: VideoPlayerProps) => {
  const videoRef = useRef<HTMLVideoElement | null>(null);

  const [searchParams] = useSearchParams();
  const searchParamVideoProgress = searchParams.get('t');

  const volumeFromStorage = Number(localStorage.getItem('playerVolume') ?? 1);
  const playBackSpeedFromStorage = Number(localStorage.getItem('playerSpeed') || 1);
  const playBackSpeedIndex =
    VIDEO_PLAYBACK_SPEEDS.indexOf(playBackSpeedFromStorage) !== -1
      ? VIDEO_PLAYBACK_SPEEDS.indexOf(playBackSpeedFromStorage)
      : 3;

  const [skippedSegments, setSkippedSegments] = useState<SponsorSegmentsSkippedType>({});
  const [isMuted, setIsMuted] = useState(false);
  const [playbackSpeedIndex, setPlaybackSpeedIndex] = useState(playBackSpeedIndex);
  const [lastSubtitleTack, setLastSubtitleTack] = useState(0);
  const [showHelpDialog, setShowHelpDialog] = useState(false);
  const [showInfoDialog, setShowInfoDialog] = useState(false);
  const [infoDialogContent, setInfoDialogContent] = useState('');

  const questionmarkPressed = useKeyPress('?');
  const mutePressed = useKeyPress('m');
  const fullscreenPressed = useKeyPress('f');
  const subtitlesPressed = useKeyPress('c');
  const increasePlaybackSpeedPressed = useKeyPress('>');
  const decreasePlaybackSpeedPressed = useKeyPress('<');
  const resetPlaybackSpeedPressed = useKeyPress('=');
  const arrowRightPressed = useKeyPress('ArrowRight');
  const arrowLeftPressed = useKeyPress('ArrowLeft');

  const videoId = video.youtube_id;
  const videoUrl = video.media_url;
  const videoThumbUrl = video.vid_thumb_url;
  const watched = video.player.watched;
  const duration = video.player.duration;
  const videoSubtitles = video.subtitles;

  let videoSrcProgress = Number(video.player?.position) > 0 ? Number(video.player?.position) : '';

  if (searchParamVideoProgress !== null) {
    videoSrcProgress = searchParamVideoProgress;
  }

  const infoDialog = (content: string) => {
    setInfoDialogContent(content);
    setShowInfoDialog(true);

    setTimeout(() => {
      setShowInfoDialog(false);
      setInfoDialogContent('');
    }, 500);
  };

  const handleVideoEnd =
    (
      youtubeId: string,
      watched: boolean,
      setSponsorSegmentSkipped?: Dispatch<SetStateAction<SponsorSegmentsSkippedType>>,
    ) =>
    async (videoTag: VideoTag) => {
      const currentTime = Number(videoTag.currentTarget.currentTime);

      const videoProgressResponse = await updateVideoProgressById({
        youtubeId,
        currentProgress: currentTime,
      });

      const { data: videoProgressResponseData } = videoProgressResponse;

      if (videoProgressResponseData?.watched && watched !== videoProgressResponseData.watched) {
        onWatchStateChanged?.(true);
      }

      setSponsorSegmentSkipped?.((segments: SponsorSegmentsSkippedType) => {
        const keys = Object.keys(segments);

        keys.forEach(uuid => {
          segments[uuid] = { from: 0, to: 0 };
        });

        return segments;
      });

      onVideoEnd?.();
    };

  useEffect(() => {
    if (mutePressed) {
      setIsMuted(!isMuted);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mutePressed]);

  useEffect(() => {
    if (increasePlaybackSpeedPressed) {
      const newSpeed = playbackSpeedIndex + 1;

      if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeed]) {
        const speed = VIDEO_PLAYBACK_SPEEDS[newSpeed];
        videoRef.current.playbackRate = speed;

        setPlaybackSpeedIndex(newSpeed);
        infoDialog(`${speed}x`);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [increasePlaybackSpeedPressed]);

  useEffect(() => {
    if (decreasePlaybackSpeedPressed) {
      const newSpeedIndex = playbackSpeedIndex - 1;

      if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeedIndex]) {
        const speed = VIDEO_PLAYBACK_SPEEDS[newSpeedIndex];
        videoRef.current.playbackRate = speed;

        setPlaybackSpeedIndex(newSpeedIndex);
        infoDialog(`${speed}x`);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [decreasePlaybackSpeedPressed]);

  useEffect(() => {
    if (resetPlaybackSpeedPressed) {
      const newSpeedIndex = 3;

      if (videoRef.current && VIDEO_PLAYBACK_SPEEDS[newSpeedIndex]) {
        const speed = VIDEO_PLAYBACK_SPEEDS[newSpeedIndex];
        videoRef.current.playbackRate = speed;

        setPlaybackSpeedIndex(newSpeedIndex);
        infoDialog(`${speed}x`);
      }
    }
  }, [resetPlaybackSpeedPressed]);

  useEffect(() => {
    if (fullscreenPressed) {
      if (videoRef.current && videoRef.current.requestFullscreen && !document.fullscreenElement) {
        videoRef.current.requestFullscreen().catch(e => {
          console.error(e);
          infoDialog('Unable to enter fullscreen');
        });
      } else {
        document.exitFullscreen().catch(e => {
          console.error(e);
          infoDialog('Unable to exit fullscreen');
        });
      }
    }
  }, [fullscreenPressed]);

  useEffect(() => {
    if (subtitlesPressed) {
      if (videoRef.current) {
        const tracks = [...videoRef.current.textTracks];

        if (tracks.length === 0) {
          return;
        }

        const lastIndex = tracks.findIndex(x => x.mode === 'showing');
        const active = tracks[lastIndex];

        if (!active && lastSubtitleTack !== 0) {
          tracks[lastSubtitleTack - 1].mode = 'showing';
        } else {
          if (active) {
            active.mode = 'hidden';

            setLastSubtitleTack(lastIndex + 1);
          }
        }
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [subtitlesPressed]);

  useEffect(() => {
    if (arrowLeftPressed) {
      infoDialog('- 5 seconds');
    }
  }, [arrowLeftPressed]);

  useEffect(() => {
    if (arrowRightPressed) {
      infoDialog('+ 5 seconds');
    }
  }, [arrowRightPressed]);

  useEffect(() => {
    if (questionmarkPressed) {
      if (!showHelpDialog) {
        setTimeout(() => {
          setShowHelpDialog(false);
        }, 3000);
      }

      setShowHelpDialog(!showHelpDialog);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [questionmarkPressed]);

  return (
    <>
      <div id="player" className={embed ? '' : 'player-wrapper'}>
        <div className={embed ? '' : 'video-main'}>
          <video
            ref={videoRef}
            key={`${getApiUrl()}${videoUrl}`}
            poster={`${getApiUrl()}${videoThumbUrl}`}
            onVolumeChange={(videoTag: VideoTag) => {
              localStorage.setItem('playerVolume', videoTag.currentTarget.volume.toString());
            }}
            onRateChange={(videoTag: VideoTag) => {
              localStorage.setItem('playerSpeed', videoTag.currentTarget.playbackRate.toString());
            }}
            onLoadStart={(videoTag: VideoTag) => {
              videoTag.currentTarget.volume = volumeFromStorage;
              videoTag.currentTarget.playbackRate = Number(playBackSpeedFromStorage ?? 1);
            }}
            onTimeUpdate={handleTimeUpdate(
              videoId,
              watched,
              sponsorBlock,
              setSkippedSegments,
              onWatchStateChanged,
            )}
            onPause={async (videoTag: VideoTag) => {
              const currentTime = Number(videoTag.currentTarget.currentTime);

              if (currentTime < 10 || currentTime > duration * 0.95) return;

              await updateVideoProgressById({
                youtubeId: videoId,
                currentProgress: currentTime,
              });
            }}
            onEnded={handleVideoEnd(videoId, watched)}
            autoPlay={autoplay}
            controls
            width="100%"
            playsInline
            id="video-item"
            muted={isMuted}
          >
            <source
              src={`${getApiUrl()}${videoUrl}#t=${videoSrcProgress}`}
              type="video/mp4"
              id="video-source"
            />
            {videoSubtitles && <Subtitles subtitles={videoSubtitles} />}
          </video>
        </div>
      </div>

      <dialog className="video-modal" open={showHelpDialog}>
        <div className="video-modal-text">
          <table className="video-modal-table">
            <tbody>
              <tr>
                <td>Show help</td>
                <td>?</td>
              </tr>
              <tr>
                <td>Toggle mute</td>
                <td>m</td>
              </tr>
              <tr>
                <td>Toggle fullscreen</td>
                <td>f</td>
              </tr>
              <tr>
                <td>Toggle subtitles (if available)</td>
                <td>c</td>
              </tr>
              <tr>
                <td>Increase speed</td>
                <td>&gt;</td>
              </tr>
              <tr>
                <td>Decrease speed</td>
                <td>&lt;</td>
              </tr>
              <tr>
                <td>Reset speed</td>
                <td>=</td>
              </tr>
              <tr>
                <td>Back 5 seconds</td>
                <td>←</td>
              </tr>
              <tr>
                <td>Forward 5 seconds</td>
                <td>→</td>
              </tr>
            </tbody>
          </table>

          <form className="video-modal-form" method="dialog">
            <button>Close</button>
          </form>
        </div>
      </dialog>

      <dialog className="video-modal" open={showInfoDialog}>
        <div className="video-modal-text">{infoDialogContent}</div>
      </dialog>

      <div className="sponsorblock" id="sponsorblock">
        {sponsorBlock?.is_enabled && (
          <>
            {sponsorBlock.segments.length == 0 && (
              <h4>
                This video doesn't have any sponsor segments added. To add a segment go to{' '}
                <u>
                  <a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
                </u>{' '}
                and add a segment using the{' '}
                <u>
                  <a href="https://sponsor.ajay.app/">SponsorBlock</a>
                </u>{' '}
                extension.
              </h4>
            )}
            {sponsorBlock.has_unlocked && (
              <h4>
                This video has unlocked sponsor segments. Go to{' '}
                <u>
                  <a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
                </u>{' '}
                and vote on the segments using the{' '}
                <u>
                  <a href="https://sponsor.ajay.app/">SponsorBlock</a>
                </u>{' '}
                extension.
              </h4>
            )}

            {Object.values(skippedSegments).map(({ from, to }, index) => {
              return (
                <Fragment key={`${from}-${to}-${index}`}>
                  {from !== 0 && to !== 0 && (
                    <h3>
                      Skipped sponsor segment from {formatTime(from)} to {formatTime(to)}.
                    </h3>
                  )}
                </Fragment>
              );
            })}
          </>
        )}
      </div>
    </>
  );
};

export default VideoPlayer;
