import { forwardRef, useEffect, useRef, useState } from "react";
import Hls from "hls.js";
import { Loader2 } from "lucide-react";

export interface SubtitleTrack {
  id: number;
  name: string;
  lang?: string;
}

interface VideoPlayerProps {
  src?: string;
  poster?: string;
  className?: string;
  /** Seconds into the current item to seek on load (broadcast sync). */
  initialOffset?: number;
  /** Active subtitle track index, or -1 to disable. */
  subtitleTrack?: number;
  muted?: boolean;
  /** Force direct-file mode (skips hls.js even for .m3u8 URLs). */
  streamingProtocol?: "hls" | "direct_file";
  onStreamError?: () => void;
  onSubtitleTracksChange?: (tracks: SubtitleTrack[]) => void;
  /** Called when the browser blocks autoplay and user interaction is required. */
  onNeedsInteraction?: () => void;
  /** Called when the video element fires its ended event. */
  onEnded?: () => void;
}

const VideoPlayer = forwardRef<HTMLVideoElement, VideoPlayerProps>(
  (
    {
      src,
      poster,
      className,
      initialOffset = 0,
      subtitleTrack = -1,
      muted = false,
      streamingProtocol,
      onStreamError,
      onSubtitleTracksChange,
      onNeedsInteraction,
      onEnded,
    },
    ref,
  ) => {
    const internalRef = useRef<HTMLVideoElement | null>(null);
    const hlsRef = useRef<Hls | null>(null);
    const [isBuffering, setIsBuffering] = useState(true);
    const onEndedRef = useRef(onEnded);
    onEndedRef.current = onEnded;
    const mutedRef = useRef(muted);
    mutedRef.current = muted;

    useEffect(() => {
      if (internalRef.current) internalRef.current.muted = muted;
    }, [muted]);

    const setRef = (el: HTMLVideoElement | null) => {
      internalRef.current = el;
      if (typeof ref === "function") ref(el);
      else if (ref) ref.current = el;
    };

    // Apply subtitle track changes without tearing down the HLS instance
    useEffect(() => {
      if (hlsRef.current) {
        hlsRef.current.subtitleTrack = subtitleTrack;
      }
    }, [subtitleTrack]);

    useEffect(() => {
      const video = internalRef.current;
      if (!video || !src) return;

      hlsRef.current?.destroy();
      hlsRef.current = null;
      onSubtitleTracksChange?.([]);
      setIsBuffering(true);

      const isHls = streamingProtocol !== "direct_file" && src.includes(".m3u8");

      if (isHls && Hls.isSupported()) {
        const hls = new Hls({
          startPosition: initialOffset > 0 ? initialOffset : -1,
          maxMaxBufferLength: 30,
        });
        hlsRef.current = hls;

        hls.on(Hls.Events.MANIFEST_PARSED, () => {
          video.muted = mutedRef.current;
          video.play().catch(() => onNeedsInteraction?.());
        });

        hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (_event, data) => {
          onSubtitleTracksChange?.(
            data.subtitleTracks.map((t) => ({
              id: t.id,
              name: t.name,
              lang: t.lang,
            })),
          );
        });

        hls.on(Hls.Events.ERROR, (_event, data) => {
          if (data.fatal) onStreamError?.();
        });

        hls.loadSource(src);
        hls.attachMedia(video);
      } else if (isHls && video.canPlayType("application/vnd.apple.mpegurl")) {
        // Native HLS (Safari)
        video.src = src;
        video.addEventListener(
          "loadedmetadata",
          () => {
            if (initialOffset > 0) video.currentTime = initialOffset;
            video.play().catch(() => onNeedsInteraction?.());
          },
          { once: true },
        );
      } else {
        // Plain MP4 / direct file: seek to offset after metadata loads.
        video.src = src;
        video.addEventListener(
          "loadedmetadata",
          () => {
            if (initialOffset > 0) video.currentTime = initialOffset;
            video.muted = mutedRef.current;
            video.play().catch(() => onNeedsInteraction?.());
          },
          { once: true },
        );
        video.load();
      }

      return () => {
        hlsRef.current?.destroy();
        hlsRef.current = null;
      };
      // initialOffset intentionally excluded: only seek when src changes (new slot/mount)
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [src]);

    return (
      <div className={`relative h-full w-full bg-black ${className ?? ""}`}>
        <video
          ref={setRef}
          poster={poster}
          playsInline
          onPlaying={() => setIsBuffering(false)}
          onWaiting={() => setIsBuffering(true)}
          onError={onStreamError}
          onEnded={() => onEndedRef.current?.()}
          className="h-full w-full object-contain"
          x-webkit-airplay="allow"
        />

        {/* Buffering spinner — shown until frames are actually rendering */}
        {isBuffering && (
          <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
            <Loader2 className="h-10 w-10 animate-spin text-zinc-500" />
          </div>
        )}
      </div>
    );
  },
);

VideoPlayer.displayName = "VideoPlayer";

export { VideoPlayer };
export type { VideoPlayerProps };
