"use client";

import dynamic from "next/dynamic";
import { memo, MutableRefObject, useEffect, useState } from "react";
import { parseYouTubeId } from "@/utils/youtube";

// react-player v3 wraps each provider (YouTube, Vimeo, ...) behind a custom
// element implementing the standard HTMLMediaElement interface, so its DOM
// events behave like a real <video> — that's what lets a YouTube-sourced
// reel share every bit of the lifecycle logic below with self-hosted MP4s
// (play/pause effect, progress reporting, error handling, cleanup).
//
// Note: next/dynamic's Loadable wrapper is a plain function component (not
// React.forwardRef), so `ref` can't reach the underlying player through it —
// the element is instead captured via onLoadedMetadata's e.currentTarget
// and stored in state (see `mediaEl` below).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ReactPlayer = dynamic(() => import("react-player"), { ssr: false }) as any;

/**
 * Reel video player built on a native <video> element (or, for reels sourced
 * from an external URL like YouTube, react-player standing in for one).
 *
 * Most reels are direct MP4 URLs (some 100 MB+), so the priorities are
 * memory and network control rather than format abstraction:
 *
 * - The page mounts a window of three players (prev / current / next).
 *   Inactive MP4 players use preload="metadata" — only the moov header is
 *   fetched, so switching reels starts fast without downloading video data
 *   upfront. There's no equivalent lightweight preload for an embedded
 *   YouTube player, so those only mount once they become active.
 * - Only the `active` player ever plays; activity is driven by page state,
 *   so exactly one video plays at a time by construction.
 * - On unmount the source is detached and load() is called, which makes the
 *   browser abort any in-flight download and release decode buffers.
 */

interface ReelsPlayerProps {
  url: string;
  /** thumbnail shown until the first frame is ready */
  poster?: string;
  /** whether this reel is the one currently on screen */
  active: boolean;
  /** user play/pause intent — only applies to the active reel */
  isPlaying: boolean;
  muted: boolean;
  /** receives the active reel's <video> element (used by the seek bar) */
  videoRef: MutableRefObject<HTMLVideoElement | null>;
  /** (played fraction, buffered fraction) — fired for the active reel only */
  onProgress?: (progress: number, buffered: number) => void;
}

/** Buffered-ahead fraction of a media element (end of the last range). */
function bufferedFraction(video: HTMLVideoElement): number {
  if (!video.duration || !video.buffered.length) return 0;
  return video.buffered.end(video.buffered.length - 1) / video.duration;
}

export const ReelsPlayer = memo(function ReelsPlayer({
  url,
  poster,
  active,
  isPlaying,
  muted,
  videoRef,
  onProgress,
}: ReelsPlayerProps) {
  // Source of truth for the underlying media element. A callback ref for
  // the native <video> (fires synchronously on mount, same as an object
  // ref); populated via onLoadedMetadata for the ReactPlayer/YouTube branch,
  // where a forwarded ref doesn't work (see note above the dynamic import).
  const [mediaEl, setMediaEl] = useState<HTMLVideoElement | null>(null);
  const [ready, setReady] = useState(false);
  const [buffering, setBuffering] = useState(false);
  const [errorMsg, setErrorMsg] = useState<string | null>(null);

  const ytId = parseYouTubeId(url);
  // Inactive neighbors stay unmounted for YouTube reels — there's no cheap
  // "metadata only" preload for an embedded iframe like there is for MP4.
  const mountPlayer = !ytId || active;

  // Reset loading state whenever the reel changes.
  useEffect(() => {
    setReady(false);
    setBuffering(false);
    setErrorMsg(null);
  }, [url]);

  /* Playback follows active/isPlaying; inactive players are always paused. */
  useEffect(() => {
    const v = mediaEl;
    if (!v) return;
    if (active) {
      videoRef.current = v;
      if (isPlaying) v.play().catch(() => {});
      else v.pause();
    } else {
      v.pause();
    }
  }, [active, isPlaying, mediaEl, videoRef]);

  /* Pause when the tab goes to the background; resume when it returns. */
  useEffect(() => {
    if (!active) return;
    const onVisibility = () => {
      const v = mediaEl;
      if (!v) return;
      if (document.hidden) v.pause();
      else if (isPlaying) v.play().catch(() => {});
    };
    document.addEventListener("visibilitychange", onVisibility);
    return () =>
      document.removeEventListener("visibilitychange", onVisibility);
  }, [active, isPlaying, mediaEl]);

  /* Unmount (or element swap): detach the source so the browser aborts any
     in-flight download and frees decode buffers — critical when files run
     100 MB+. Depends on mediaEl so it always targets whichever element was
     actually current, rather than whatever was mounted on first render. */
  useEffect(() => {
    return () => {
      if (!mediaEl) return;
      mediaEl.pause();
      mediaEl.removeAttribute("src");
      mediaEl.load();
    };
  }, [mediaEl]);

  const showSpinner = active && (!ready || buffering) && !errorMsg;

  const handleError = (e: React.SyntheticEvent<HTMLVideoElement>) => {
    // MediaError 3 (decode) / 4 (src not supported) mean the device can't
    // play this codec — e.g. an HEVC upload on an Android phone. Everything
    // else (including a YouTube embed failure) is treated as a load failure.
    const code = e.currentTarget.error?.code;
    setErrorMsg(
      code === 3 || code === 4
        ? "This video format isn't supported on your device"
        : "Couldn't load this video",
    );
    setBuffering(false);
  };

  const handleProgressEvent = (e: React.SyntheticEvent<HTMLVideoElement>) => {
    // Native progress event — fires as data downloads, even while paused
    if (!active) return;
    const v = e.currentTarget;
    if (v.duration) {
      onProgress?.(v.currentTime / v.duration, bufferedFraction(v));
    }
  };

  const handleTimeUpdate = (e: React.SyntheticEvent<HTMLVideoElement>) => {
    if (!active) return;
    const v = e.currentTarget;
    const progress = v.currentTime / v.duration;
    if (!isNaN(progress)) onProgress?.(progress, bufferedFraction(v));
    // Time is advancing → frames are unambiguously rendering, so the
    // player can't still be "loading" no matter what other events fired.
    setReady(true);
    setBuffering(false);
  };

  return (
    <div
      className="relative w-full h-full overflow-hidden"
      style={{ background: "#e9e9ec" }}
    >
      {!mountPlayer ? (
        poster && (
          // eslint-disable-next-line @next/next/no-img-element
          <img src={poster} alt="" className="w-full h-full object-cover" />
        )
      ) : ytId ? (
        <ReactPlayer
          src={url}
          poster={poster}
          muted={muted}
          playsInline
          width="100%"
          height="100%"
          style={{ position: "absolute", inset: 0 }}
          onLoadedMetadata={(e: React.SyntheticEvent<HTMLVideoElement>) =>
            setMediaEl(e.currentTarget)
          }
          onLoadedData={(e: React.SyntheticEvent<HTMLVideoElement>) => {
            setReady(true);
            setMediaEl(e.currentTarget);
          }}
          onWaiting={() => setBuffering(true)}
          onPlaying={() => setBuffering(false)}
          onError={handleError}
          onProgress={handleProgressEvent}
          onTimeUpdate={handleTimeUpdate}
        />
      ) : (
        <video
          ref={setMediaEl}
          src={url}
          poster={poster}
          muted={muted}
          playsInline
          preload={active ? "auto" : "metadata"}
          className="w-full h-full object-cover"
          onLoadedData={() => setReady(true)}
          onWaiting={() => setBuffering(true)}
          onPlaying={() => setBuffering(false)}
          onError={handleError}
          onProgress={handleProgressEvent}
          onTimeUpdate={handleTimeUpdate}
        />
      )}

      {showSpinner && (
        <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
          <div
            className="w-10 h-10 rounded-full border-2 animate-spin"
            style={{
              borderColor: "rgba(var(--accent-rgb),0.25)",
              borderTopColor: "var(--accent)",
            }}
          />
        </div>
      )}

      {errorMsg && (
        <div className="absolute inset-0 flex items-center justify-center pointer-events-none px-6">
          <p
            className="text-xs font-medium text-center"
            style={{ color: "#8a8a90" }}
          >
            {errorMsg}
          </p>
        </div>
      )}
    </div>
  );
});
