/**
 * Drives synced playback across the time-based `<video>` elements of the quad
 * viewer. The master video (videoRefs[masterIndex]) is the clock; every other
 * non-null video is nudged to follow it when it drifts. Fully null-safe — refs
 * may be null and videos may be absent.
 *
 * VTK-free: this hook touches only the DOM `<video>` API.
 */
import { useCallback, useEffect, useRef, useState } from 'react';
import { clampTime } from './playbackModel';

/** Seconds of drift tolerated before a follower is re-synced to the master clock. */
const DRIFT_TOLERANCE = 0.15;

export interface PlaybackSync {
  currentTime: number;
  duration: number;
  playing: boolean;
  rate: number;
  play: () => void;
  pause: () => void;
  togglePlay: () => void;
  seek: (t: number) => void;
  setRate: (r: number) => void;
}

export function usePlaybackSync(
  videoRefs: React.RefObject<HTMLVideoElement>[],
  masterIndex = 0,
  /**
   * Optional value that, when it changes, forces the master-clock listeners to
   * re-subscribe — e.g. pass the master video's URL so listeners attach once the
   * master `<video>` appears (or its source changes) after the initial mount.
   */
  resubscribeKey?: unknown,
): PlaybackSync {
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [rate, setRateState] = useState(1);

  // Keep a live handle to the refs array without re-subscribing the master
  // listeners every render (the array identity changes each render).
  const refsRef = useRef(videoRefs);
  refsRef.current = videoRefs;
  const durationRef = useRef(0);
  durationRef.current = duration;

  const forEachVideo = useCallback(
    (fn: (v: HTMLVideoElement) => void) => {
      for (const ref of refsRef.current) {
        const v = ref?.current;
        if (v) fn(v);
      }
    },
    [],
  );

  const master = videoRefs[masterIndex];

  // Subscribe to the master video's clock. `timeupdate` advances state and nudges
  // any follower that has drifted; `loadedmetadata` captures the duration.
  useEffect(() => {
    const m = master?.current;
    if (!m) return;

    const onTimeUpdate = () => {
      const t = m.currentTime;
      setCurrentTime(t);
      // Re-sync followers without triggering their own timeupdate feedback loops:
      // only assign when drift exceeds the tolerance.
      for (const ref of refsRef.current) {
        const v = ref?.current;
        if (!v || v === m) continue;
        if (Math.abs(v.currentTime - t) > DRIFT_TOLERANCE) {
          v.currentTime = t;
        }
      }
    };
    const onLoadedMetadata = () => {
      setDuration(Number.isFinite(m.duration) ? m.duration : 0);
    };
    const onEnded = () => setPlaying(false);

    m.addEventListener('timeupdate', onTimeUpdate);
    m.addEventListener('loadedmetadata', onLoadedMetadata);
    m.addEventListener('ended', onEnded);

    // The metadata event may have already fired before this effect attached.
    if (Number.isFinite(m.duration) && m.duration > 0) {
      setDuration(m.duration);
    }

    return () => {
      m.removeEventListener('timeupdate', onTimeUpdate);
      m.removeEventListener('loadedmetadata', onLoadedMetadata);
      m.removeEventListener('ended', onEnded);
    };
  }, [master, resubscribeKey]);

  const play = useCallback(() => {
    forEachVideo((v) => {
      // play() returns a promise that can reject (autoplay policy) — swallow it.
      void v.play?.()?.catch?.(() => undefined);
    });
    setPlaying(true);
  }, [forEachVideo]);

  const pause = useCallback(() => {
    forEachVideo((v) => v.pause?.());
    setPlaying(false);
  }, [forEachVideo]);

  const togglePlay = useCallback(() => {
    if (playing) pause();
    else play();
  }, [playing, play, pause]);

  const seek = useCallback(
    (t: number) => {
      const clamped = clampTime(t, durationRef.current);
      forEachVideo((v) => {
        v.currentTime = clamped;
      });
      setCurrentTime(clamped);
    },
    [forEachVideo],
  );

  const setRate = useCallback(
    (r: number) => {
      forEachVideo((v) => {
        v.playbackRate = r;
      });
      setRateState(r);
    },
    [forEachVideo],
  );

  return { currentTime, duration, playing, rate, play, pause, togglePlay, seek, setRate };
}
