import * as React from 'react';

export interface UseMediaControlsOptions<T extends HTMLMediaElement> {
  controllerRef: React.Ref<T>;
  actionFormKey?: string;
}

export const useMediaControls = <T extends HTMLMediaElement>({
  controllerRef: forwardedRef,
  actionFormKey = 'action' as const,
}: UseMediaControlsOptions<T>) => {
  const defaultRef = React.useRef<T>(null);
  const ref = forwardedRef ?? defaultRef;
  const seekRef = React.useRef<HTMLInputElement>(null);
  const volumeRef = React.useRef<HTMLInputElement>(null);
  const filenameRef = React.useRef<HTMLElement>(null);
  const [isPlaying, setIsPlaying] = React.useState(false);
  const [isSeeking, setIsSeeking] = React.useState(false);
  const [currentTimeDisplay, setCurrentTimeDisplay] = React.useState<number>();
  const [seekTimeDisplay, setSeekTimeDisplay] = React.useState<number>();
  const [durationDisplay, setDurationDisplay] = React.useState<number>();
  const [isSeekTimeCountingDown, setIsSeekTimeCountingDown] = React.useState(false);

  const refreshControls: React.ReactEventHandler<T> = React.useCallback((e) => {
    const { currentTarget: mediaController } = e;

    setCurrentTimeDisplay(mediaController.currentTime);
    setDurationDisplay(mediaController.duration);
  }, []);

  const togglePlayback = React.useCallback(() => {
    setIsPlaying((p) => !p);
  }, []);

  const startSeek: React.MouseEventHandler<HTMLInputElement> = React.useCallback(() => {
    setIsSeeking(true);
  }, []);

  const doSetSeek = React.useCallback((thisElement: HTMLInputElement, mediaController: HTMLMediaElement) => {
    mediaController.currentTime = thisElement.valueAsNumber;
  }, []);

  const setSeek: React.ChangeEventHandler<HTMLInputElement> = React.useCallback((e) => {
    if (!(typeof ref === 'object' && ref !== null)) {
      return;
    }

    const { current: mediaController } = ref;
    if (!mediaController) {
      return;
    }

    const { currentTarget: thisElement } = e;
    setSeekTimeDisplay(thisElement.valueAsNumber);

    if (isSeeking) {
      return;
    }

    doSetSeek(thisElement, mediaController);
  }, [ref, isSeeking, doSetSeek]);

  const endSeek: React.MouseEventHandler<HTMLInputElement> = React.useCallback((e) => {
    if (!(typeof ref === 'object' && ref !== null)) {
      return;
    }

    const { current: mediaController } = ref;

    if (!mediaController) {
      return;
    }

    const { currentTarget: thisElement } = e;
    setIsSeeking(false);
    doSetSeek(thisElement, mediaController);
  }, [ref, doSetSeek]);

  const reset: React.ReactEventHandler<T> = React.useCallback((e) => {
    const videoElement = e.currentTarget;
    setIsPlaying(false);
    videoElement.currentTime = 0;
  }, []);

  const updateSeekFromPlayback: React.ReactEventHandler<T> = React.useCallback((e) => {
    if (isSeeking) {
      return;
    }

    const videoElement = e.currentTarget;
    const currentTime = videoElement.currentTime;
    setCurrentTimeDisplay(currentTime);

    if (!seekRef.current) {
      return;
    }
    const { current: seek } = seekRef;
    if (!seek) {
      return;
    }
    seek.value = String(currentTime);
  }, [isSeeking]);

  const toggleSeekTimeCountMode = React.useCallback(() => {
    setIsSeekTimeCountingDown((b) => !b);
  }, []);

  const download = React.useCallback(() => {
    if (!(typeof ref === 'object' && ref?.current !== null)) {
      return;
    }

    if (!(typeof filenameRef === 'object' && filenameRef?.current !== null)) {
      return;
    }

    const downloadLink = window.document.createElement('a');
    downloadLink.download = filenameRef.current.textContent ?? 'image';
    downloadLink.href = ref.current.currentSrc;
    downloadLink.addEventListener('click', () => {
      downloadLink.remove();
    });
    downloadLink.click();
  }, [ref, filenameRef]);

  const actions = React.useMemo(() => ({
    togglePlayback,
    toggleSeekTimeCountMode,
    download,
  }), [togglePlayback, toggleSeekTimeCountMode, download]);

  const handleAction: React.FormEventHandler<HTMLFormElement> = React.useCallback((e) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget, (e.nativeEvent as unknown as { submitter: HTMLElement }).submitter);
    const actionName = formData.get(actionFormKey) as keyof typeof actions;
    const { [actionName]: actionFunction } = actions;
    actionFunction?.();
  }, [actions, actionFormKey]);

  const adjustVolume: React.ChangeEventHandler<HTMLInputElement> = React.useCallback((e) => {
    if (!(typeof ref === 'object' && ref !== null)) {
      return;
    }

    if (!ref.current) {
      return;
    }
    const { value } = e.currentTarget;
    ref.current.volume = Number(value);
  }, [ref]);

  React.useEffect(() => {
    if (!(typeof ref === 'object' && ref !== null)) {
      return;
    }

    if (!ref.current) {
      return;
    }

    if (isPlaying) {
      void ref.current.play();
      return
    }

    ref.current.pause();
  }, [isPlaying, ref]);

  React.useEffect(() => {
    if (!seekRef.current) {
      return;
    }

    const { current: seek } = seekRef;
    if (!seek) {
      return;
    }

    seek.value = String(currentTimeDisplay);
  }, [currentTimeDisplay]);

  React.useEffect(() => {
    if (!seekRef.current) {
      return;
    }

    const { current: seek } = seekRef;
    if (!seek) {
      return;
    }

    seek.max = String(durationDisplay);
  }, [durationDisplay]);

  React.useEffect(() => {
    if (!volumeRef.current) {
      return;
    }

    if (!(typeof ref === 'object' && ref !== null)) {
      return;
    }

    if (!ref.current) {
      return;
    }

    const { current: mediaController } = ref;
    const { current: volume } = volumeRef;
    volume.value = String(mediaController.volume);
  }, [ref]);

  return React.useMemo(() => ({
    seekRef,
    volumeRef,
    isPlaying,
    refreshControls,
    adjustVolume,
    reset,
    startSeek,
    endSeek,
    setSeek,
    updateSeekFromPlayback,
    durationDisplay,
    currentTimeDisplay,
    seekTimeDisplay,
    isSeeking,
    isSeekTimeCountingDown,
    mediaControllerRef: ref,
    handleAction,
    filenameRef,
  }), [
    refreshControls,
    isPlaying,
    isSeeking,
    adjustVolume,
    reset,
    startSeek,
    endSeek,
    setSeek,
    updateSeekFromPlayback,
    durationDisplay,
    currentTimeDisplay,
    seekTimeDisplay,
    isSeekTimeCountingDown,
    ref,
    handleAction,
    filenameRef,
  ]);
};
