import { useCallback, useEffect, useRef, useState } from "react";

/** WebKit exposes HLS alternate audio renditions here; the DOM lib omits it. */
interface AudioTrackLike {
  id: string;
  label: string;
  language: string;
  enabled: boolean;
}
interface AudioTrackListLike {
  length: number;
  [index: number]: AudioTrackLike;
  addEventListener?: (t: string, fn: () => void) => void;
  removeEventListener?: (t: string, fn: () => void) => void;
}
type VideoWithTracks = HTMLVideoElement & { audioTracks?: AudioTrackListLike };

function listAudio(v: HTMLVideoElement | null): AudioTrackLike[] {
  const list = (v as VideoWithTracks | null)?.audioTracks;
  if (!list) return [];
  return Array.from({ length: list.length }, (_, i) => list[i]);
}

function listSubs(v: HTMLVideoElement | null): TextTrack[] {
  if (!v) return [];
  return Array.from(v.textTracks).filter(
    (t) => t.kind === "subtitles" || t.kind === "captions",
  );
}

interface Props {
  videoRef: React.RefObject<HTMLVideoElement | null>;
  /** Element to fullscreen — the stage, so letterboxing travels with it. */
  stageRef: React.RefObject<HTMLDivElement | null>;
  /** Nudges the auto-hide timer whenever the user does something. */
  onActivity?: () => void;
  /** Preferred subtitle language, or "off" to start with none. */
  subLang: string;
}

const SKIP_S = 10;

function clock(seconds: number): string {
  if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = Math.floor(seconds % 60);
  const mm = h > 0 ? String(m).padStart(2, "0") : String(m);
  return `${h > 0 ? `${h}:` : ""}${mm}:${String(s).padStart(2, "0")}`;
}

const btn =
  "grid size-10 shrink-0 place-items-center rounded-lg text-white/90 cursor-pointer " +
  "hover:bg-white/15 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed";

/**
 * Our own transport bar.
 *
 * WebKit's native controls put fullscreen, Picture-in-Picture and volume as
 * overlay buttons in the top corners of the video and give no way to move them.
 * Owning the bar is the only way to get every control into one strip along the
 * bottom, so the native ones are switched off entirely.
 */
export default function PlayerControls({ videoRef, stageRef, onActivity, subLang }: Props) {
  const [playing, setPlaying] = useState(false);
  const [time, setTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [volume, setVolume] = useState(1);
  const [muted, setMuted] = useState(false);
  const [pip, setPip] = useState(false);
  const [full, setFull] = useState(false);
  const [menu, setMenu] = useState(false);
  const [audio, setAudio] = useState<AudioTrackLike[]>([]);
  const [subs, setSubs] = useState<TextTrack[]>([]);
  const [, bump] = useState(0);
  const menuRef = useRef<HTMLDivElement>(null);

  // Tracks arrive with the manifest, after metadata rather than on mount.
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const read = () => {
      setAudio(listAudio(v));
      setSubs(listSubs(v));
    };
    read();

    // Subtitles follow the language chosen in Settings and nothing else.
    // WebKit will otherwise switch on whatever matches the system language,
    // which is the same unwanted auto-selection as a dubbed audio track.
    const applyPreference = () => {
      const tracks = Array.from(v.textTracks);
      const wanted =
        subLang === "off"
          ? undefined
          : tracks.find((t) =>
              (t.language || "").toLowerCase().startsWith(subLang.toLowerCase()),
            );
      for (const t of tracks) t.mode = t === wanted ? "showing" : "disabled";
      read();
    };
    applyPreference();
    v.addEventListener("loadedmetadata", applyPreference);
    // HLS subtitle renditions arrive after metadata, so re-apply as they land.
    v.textTracks.addEventListener?.("addtrack", applyPreference);

    v.addEventListener("loadedmetadata", read);
    const at = (v as VideoWithTracks).audioTracks;
    at?.addEventListener?.("addtrack", read);
    v.textTracks.addEventListener?.("addtrack", read);
    // Manifests can take a moment to surface renditions.
    const id = setInterval(read, 1000);
    const stop = setTimeout(() => clearInterval(id), 8000);
    return () => {
      v.removeEventListener("loadedmetadata", applyPreference);
      v.textTracks.removeEventListener?.("addtrack", applyPreference);
      v.removeEventListener("loadedmetadata", read);
      at?.removeEventListener?.("addtrack", read);
      v.textTracks.removeEventListener?.("addtrack", read);
      clearInterval(id);
      clearTimeout(stop);
    };
  }, [videoRef, subLang]);

  // Close the menu on any outside click.
  useEffect(() => {
    if (!menu) return;
    const onDown = (e: MouseEvent) => {
      if (!menuRef.current?.contains(e.target as Node)) setMenu(false);
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [menu]);

  const chooseAudio = (i: number) => {
    const v = videoRef.current;
    const list = (v as VideoWithTracks | null)?.audioTracks;
    if (!list) return;
    // Exactly one enabled, or WebKit mixes them.
    for (let k = 0; k < list.length; k++) list[k].enabled = k === i;
    bump((n) => n + 1);
    onActivity?.();
  };

  const chooseSub = (track: TextTrack | null) => {
    const v = videoRef.current;
    if (!v) return;
    for (const t of Array.from(v.textTracks)) {
      t.mode = t === track ? "showing" : "disabled";
    }
    bump((n) => n + 1);
    onActivity?.();
  };

  // Mirror the element's state rather than assuming ours is authoritative —
  // playback can change from the keyboard, the system, or the video ending.
  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const sync = () => {
      setPlaying(!v.paused);
      setTime(v.currentTime);
      setDuration(Number.isFinite(v.duration) ? v.duration : 0);
      setVolume(v.volume);
      setMuted(v.muted);
    };
    const onPip = () => setPip(!!document.pictureInPictureElement);
    const onFull = () => setFull(!!document.fullscreenElement);
    sync();
    for (const e of ["play", "pause", "timeupdate", "durationchange", "volumechange", "loadedmetadata", "ended"]) {
      v.addEventListener(e, sync);
    }
    v.addEventListener("enterpictureinpicture", onPip);
    v.addEventListener("leavepictureinpicture", onPip);
    document.addEventListener("fullscreenchange", onFull);
    return () => {
      for (const e of ["play", "pause", "timeupdate", "durationchange", "volumechange", "loadedmetadata", "ended"]) {
        v.removeEventListener(e, sync);
      }
      v.removeEventListener("enterpictureinpicture", onPip);
      v.removeEventListener("leavepictureinpicture", onPip);
      document.removeEventListener("fullscreenchange", onFull);
    };
  }, [videoRef]);

  const act = useCallback(
    (fn: (v: HTMLVideoElement) => void) => () => {
      const v = videoRef.current;
      if (v) fn(v);
      onActivity?.();
    },
    [videoRef, onActivity],
  );

  const togglePlay = act((v) => {
    if (v.paused) void v.play().catch(() => {});
    else v.pause();
  });

  const togglePip = useCallback(async () => {
    const v = videoRef.current;
    if (!v) return;
    onActivity?.();
    try {
      if (document.pictureInPictureElement) await document.exitPictureInPicture();
      else await v.requestPictureInPicture();
    } catch {
      /* not available for this source */
    }
  }, [videoRef, onActivity]);

  const toggleFull = useCallback(async () => {
    onActivity?.();
    try {
      if (document.fullscreenElement) await document.exitFullscreen();
      else await stageRef.current?.requestFullscreen();
    } catch {
      /* element fullscreen unavailable */
    }
  }, [stageRef, onActivity]);

  const pct = duration > 0 ? (time / duration) * 100 : 0;

  return (
    <div
      // Clicks here must not reach the video's own play/pause handler.
      onClick={(e) => e.stopPropagation()}
      className="absolute inset-x-0 bottom-0 z-20 flex items-center gap-2.5 bg-gradient-to-t
                 from-slate-950/90 via-slate-950/65 to-transparent px-5 pb-4 pt-10"
    >
      <button onClick={togglePlay} className={btn} title={playing ? "Pause (space)" : "Play (space)"}>
        {playing ? (
          <svg viewBox="0 0 24 24" className="size-5" fill="currentColor">
            <rect x="6" y="5" width="4" height="14" rx="1" />
            <rect x="14" y="5" width="4" height="14" rx="1" />
          </svg>
        ) : (
          <svg viewBox="0 0 24 24" className="size-5" fill="currentColor">
            <path d="M8 5.5v13l11-6.5z" />
          </svg>
        )}
      </button>

      <button
        onClick={act((v) => (v.currentTime = Math.max(0, v.currentTime - SKIP_S)))}
        className={btn}
        title={`Back ${SKIP_S}s`}
      >
        <svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2">
          <path strokeLinecap="round" strokeLinejoin="round" d="M11 17l-5-5 5-5M18 17l-5-5 5-5" />
        </svg>
      </button>
      <button
        onClick={act((v) => (v.currentTime = Math.min(v.duration || 0, v.currentTime + SKIP_S)))}
        className={btn}
        title={`Forward ${SKIP_S}s`}
      >
        <svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2">
          <path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5-5 5M6 7l5 5-5 5" />
        </svg>
      </button>

      <span className="shrink-0 font-mono text-[12px] tabular-nums text-white/85">{clock(time)}</span>

      <input
        type="range"
        min={0}
        max={duration || 0}
        step={0.1}
        value={time}
        onChange={(e) => {
          const v = videoRef.current;
          if (v) v.currentTime = Number(e.target.value);
          onActivity?.();
        }}
        aria-label="Seek"
        className="h-1.5 min-w-0 flex-1 cursor-pointer appearance-none rounded-full bg-white/25 accent-sky-500"
        style={{
          background: `linear-gradient(to right, var(--color-sky-500) ${pct}%, rgba(255,255,255,0.25) ${pct}%)`,
        }}
      />

      <span className="shrink-0 font-mono text-[12px] tabular-nums text-white/85">{clock(duration)}</span>

      <button
        onClick={act((v) => (v.muted = !v.muted))}
        className={btn}
        title={muted || volume === 0 ? "Unmute" : "Mute"}
      >
        <svg viewBox="0 0 24 24" className="size-5" fill="currentColor">
          <path d="M4 9v6h4l5 4V5L8 9H4z" />
          {muted || volume === 0 ? (
            <path d="M16 9l5 6M21 9l-5 6" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" />
          ) : (
            <path
              d="M16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"
              stroke="currentColor"
              strokeWidth="2"
              fill="none"
              strokeLinecap="round"
            />
          )}
        </svg>
      </button>
      <input
        type="range"
        min={0}
        max={1}
        step={0.02}
        value={muted ? 0 : volume}
        onChange={(e) => {
          const v = videoRef.current;
          if (!v) return;
          v.volume = Number(e.target.value);
          v.muted = Number(e.target.value) === 0;
          onActivity?.();
        }}
        aria-label="Volume"
        className="h-1.5 w-24 shrink-0 cursor-pointer appearance-none rounded-full accent-sky-500"
        style={{
          background: `linear-gradient(to right, rgba(255,255,255,0.85) ${
            (muted ? 0 : volume) * 100
          }%, rgba(255,255,255,0.25) ${(muted ? 0 : volume) * 100}%)`,
        }}
      />

      {(audio.length > 1 || subs.length > 0) && (
        <div ref={menuRef} className="relative shrink-0">
          <button
            onClick={() => { setMenu((m) => !m); onActivity?.(); }}
            className={btn}
            title="Audio and subtitles"
          >
            <svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2">
              <rect x="3" y="5" width="18" height="14" rx="2" />
              <path strokeLinecap="round" d="M7 15h4M14 15h3" />
            </svg>
          </button>

          {menu && (
            <div
              className="absolute bottom-full right-0 mb-2 max-h-72 w-60 overflow-y-auto rounded-lg
                         border border-slate-700 bg-slate-900/95 p-1 shadow-2xl backdrop-blur"
            >
              {audio.length > 1 && (
                <>
                  <div className="px-2 py-1.5 text-[10px] font-bold uppercase tracking-widest text-slate-400">
                    Audio
                  </div>
                  {audio.map((t, i) => (
                    <button
                      key={t.id || `${t.language}-${i}`}
                      onClick={() => chooseAudio(i)}
                      className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left
                                  text-[12px] cursor-pointer hover:bg-slate-800 ${
                                    t.enabled ? "text-white" : "text-slate-300"
                                  }`}
                    >
                      <span className="w-3 shrink-0 text-sky-400">{t.enabled ? "✓" : ""}</span>
                      <span className="truncate">{t.label || t.language || `Track ${i + 1}`}</span>
                    </button>
                  ))}
                </>
              )}

              {subs.length > 0 && (
                <>
                  <div className="mt-1 px-2 py-1.5 text-[10px] font-bold uppercase tracking-widest text-slate-400">
                    Subtitles
                  </div>
                  <button
                    onClick={() => chooseSub(null)}
                    className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left
                                text-[12px] cursor-pointer hover:bg-slate-800 ${
                                  subs.every((t) => t.mode !== "showing")
                                    ? "text-white"
                                    : "text-slate-300"
                                }`}
                  >
                    <span className="w-3 shrink-0 text-sky-400">
                      {subs.every((t) => t.mode !== "showing") ? "✓" : ""}
                    </span>
                    Off
                  </button>
                  {subs.map((t, i) => (
                    <button
                      key={t.id || `${t.language}-${i}`}
                      onClick={() => chooseSub(t)}
                      className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left
                                  text-[12px] cursor-pointer hover:bg-slate-800 ${
                                    t.mode === "showing" ? "text-white" : "text-slate-300"
                                  }`}
                    >
                      <span className="w-3 shrink-0 text-sky-400">
                        {t.mode === "showing" ? "✓" : ""}
                      </span>
                      <span className="truncate">{t.label || t.language || `Subtitles ${i + 1}`}</span>
                    </button>
                  ))}
                </>
              )}
            </div>
          )}
        </div>
      )}

      <button onClick={togglePip} className={btn} title={pip ? "Leave Picture in Picture" : "Picture in Picture"}>
        <svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2">
          <rect x="3" y="5" width="18" height="14" rx="2" />
          <rect x="12" y="11" width="7" height="6" rx="1" fill="currentColor" stroke="none" />
        </svg>
      </button>

      <button onClick={toggleFull} className={btn} title={full ? "Leave full screen (f)" : "Full screen (f)"}>
        <svg viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth="2">
          {full ? (
            <path strokeLinecap="round" strokeLinejoin="round" d="M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" />
          ) : (
            <path strokeLinecap="round" strokeLinejoin="round" d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" />
          )}
        </svg>
      </button>
    </div>
  );
}
