import * as React from 'react';
import {getFormValues} from '@theoryofnekomata/formxtra';

export interface UseMediaControlsOptions<T extends HTMLMediaElement> {
  controllerRef: React.Ref<T>;
  actionFormKey?: string;
  visualizationMode?: 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 visualizationId = React.useId();
  const formId = React.useId();
  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)) {
      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)) {
      return;
    }
    const { current: mediaController } = ref;
    if (!mediaController) {
      return;
    }

    const { currentTarget: thisElement } = e;
    setIsSeeking(false);
    setCurrentTimeDisplay(thisElement.valueAsNumber);
    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 (!(typeof seekRef === 'object' && seekRef)) {
      return;
    }
    const { current: seek } = seekRef;
    if (!seek) {
      return;
    }
    seek.value = String(currentTime);
  }, [isSeeking, seekRef]);

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

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

    if (!(typeof filenameRef === 'object' && filenameRef)) {
      return;
    }
    const { current: filename } = filenameRef;
    if (!filename) {
      return;
    }

    const downloadLink = window.document.createElement('a');
    downloadLink.download = filename.textContent ?? 'media';
    downloadLink.href = mediaController.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 nativeEvent = e.nativeEvent as unknown as { submitter: HTMLElement };
    const formData = getFormValues(
      e.currentTarget,
      {
        submitter: nativeEvent.submitter,
      }
    );
    const actionName = formData[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)) {
      return;
    }
    const { current: mediaController } = ref;
    if (!mediaController) {
      return;
    }

    const { value } = e.currentTarget;
    mediaController.volume = Number(value);
  }, [ref]);

  React.useEffect(() => {
    if (!(typeof ref === 'object' && ref)) {
      return;
    }
    const { current: mediaController } = ref;
    if (!mediaController) {
      return;
    }

    if (isPlaying) {
      void mediaController.play();
      return
    }

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

  React.useEffect(() => {
    if (!(typeof seekRef === 'object' && seekRef)) {
      return;
    }
    const { current: seek } = seekRef;
    if (!seek) {
      return;
    }

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

  React.useEffect(() => {
    if (!(typeof ref === 'object' && ref)) {
      return;
    }
    const { current: mediaController } = ref;
    if (!mediaController) {
      return;
    }

    if (!(typeof volumeRef === 'object' && volumeRef)) {
      return;
    }
    const { current: volume } = volumeRef;
    if (!volume) {
      return;
    }

    volume.value = String(mediaController.volume);
  }, [ref, volumeRef]);

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