import { useEffect, useRef, useState } from "react";
import type { ClipOnDatabase, FrontendId } from "./clip-state-reducer";
import { cn } from "@/lib/utils";
import { clipZoomCssStyle } from "@/features/videos/clip-zoom";
import {
  BEAT_DURATION,
  FINAL_VIDEO_PADDING,
  PREVIEW_AUDIO_BOOST_DB,
} from "./constants";
import { useAudioBoost } from "./use-audio-boost";
import type { RunningState } from "./video-state-reducer";

const PRELOAD_PLAY_AMOUNT = 0.1;

export const PreloadableClip = (props: {
  playbackRate: number;
  clip: ClipOnDatabase;
  onFinish: () => void;
  aggressivePreload: boolean;
  onPreloadComplete: () => void;
  hidden: boolean;
  state: RunningState;
  onUpdateCurrentTime: (time: number) => void;
  profile: string | undefined;
  scrubSeekTime: number | undefined;
}) => {
  const [preloadState, setPreloadState] = useState<"preloading" | "finished">(
    "preloading"
  );
  const ref = useRef<HTMLVideoElement>(null);
  useAudioBoost(ref, PREVIEW_AUDIO_BOOST_DB);

  const preloadFrom = props.clip.sourceStartTime - PRELOAD_PLAY_AMOUNT;
  const preloadTo = props.clip.sourceStartTime;
  const modifiedEndTime = props.clip.sourceEndTime - 0.06;

  const isPlaying = !props.hidden && props.state === "playing";

  useEffect(() => {
    if (!ref.current) {
      return;
    }
    ref.current.playbackRate = props.playbackRate;
  }, [props.playbackRate, ref.current]);

  useEffect(() => {
    if (!ref.current || props.hidden || props.scrubSeekTime === undefined) {
      return;
    }
    ref.current.pause();
    ref.current.currentTime = props.scrubSeekTime;
  }, [props.scrubSeekTime, props.hidden, ref.current]);

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

    if (preloadState === "preloading" && props.aggressivePreload) {
      ref.current.muted = true;
      ref.current.play();
      return;
    }

    if (props.hidden || !props.aggressivePreload) {
      ref.current.pause();
      ref.current.currentTime = props.clip.sourceStartTime;
      ref.current.muted = false;
      return;
    }

    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [
    props.hidden,
    ref.current,
    props.state,
    preloadState,
    props.aggressivePreload,
  ]);

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

    if (!isPlaying && preloadState === "finished") {
      return;
    }
    let animationId: number | null = null;

    const checkCurrentTime = () => {
      const currentTime = ref.current!.currentTime;

      if (preloadState === "preloading") {
        if (currentTime >= preloadTo) {
          setPreloadState("finished");
          ref.current?.pause();
          ref.current!.muted = false;
          ref.current!.currentTime = preloadTo;
          props.onPreloadComplete();
        }
      } else if (currentTime >= modifiedEndTime) {
        ref.current!.pause();
        props.onFinish();
        ref.current!.currentTime = props.clip.sourceStartTime;
        return;
      }

      props.onUpdateCurrentTime(currentTime - props.clip.sourceStartTime);

      animationId = requestAnimationFrame(checkCurrentTime);
    };

    animationId = requestAnimationFrame(checkCurrentTime);

    return () => {
      if (animationId !== null) {
        cancelAnimationFrame(animationId);
      }
    };
  }, [
    ref.current,
    isPlaying,
    preloadState,
    modifiedEndTime,
    props.clip.sourceStartTime,
    preloadTo,
    props.onUpdateCurrentTime,
  ]);

  return (
    <video
      key={props.clip.frontendId}
      src={`/view-video?videoPath=${props.clip.videoFilename}#t=${preloadFrom},${modifiedEndTime}`}
      className={cn(
        "w-full",
        props.hidden && "hidden",
        props.profile === "TikTok" && "w-92 aspect-[9/16]"
      )}
      // The preview half of the Clip Zoom contract. These two properties are
      // formatted from the same rect the export's ffmpeg crop is built from
      // (see features/videos/clip-zoom), so what plays here is what ships.
      style={clipZoomCssStyle(props.clip.zoomType) ?? undefined}
      ref={ref}
    />
  );
};

export const PreloadableClipManager = (props: {
  playbackRate: number;
  clips: ClipOnDatabase[];
  finalClipId: string | undefined;
  clipsToAggressivelyPreload: string[];
  state: RunningState;
  currentClipId: FrontendId | undefined;
  currentClipProfile: string | undefined;
  onClipFinished: () => void;
  onUpdateCurrentTime: (time: number) => void;
  scrubSeekTime: number | undefined;
}) => {
  return (
    <div className="">
      {props.clips.map((clip) => {
        const isCurrentlyPlaying = clip.frontendId === props.currentClipId;

        const onFinish = () => {
          if (!isCurrentlyPlaying) {
            return;
          }

          if (clip.pauseType === "long") {
            setTimeout(() => {
              props.onClipFinished();
            }, BEAT_DURATION * 1000);
          } else {
            props.onClipFinished();
          }
        };

        const isFinalClip = clip.frontendId === props.finalClipId;

        const modifiedClip = isFinalClip
          ? { ...clip, sourceEndTime: clip.sourceEndTime + FINAL_VIDEO_PADDING }
          : clip;

        return (
          <div key={clip.frontendId}>
            <PreloadableClip
              playbackRate={props.playbackRate}
              clip={modifiedClip}
              key={clip.frontendId}
              onFinish={onFinish}
              aggressivePreload={props.clipsToAggressivelyPreload.includes(
                clip.frontendId
              )}
              hidden={!isCurrentlyPlaying}
              state={props.state}
              profile={props.currentClipProfile}
              onUpdateCurrentTime={(time) => {
                if (isCurrentlyPlaying) {
                  props.onUpdateCurrentTime(time);
                }
              }}
              onPreloadComplete={() => {}}
              scrubSeekTime={
                isCurrentlyPlaying ? props.scrubSeekTime : undefined
              }
            />
          </div>
        );
      })}
    </div>
  );
};
