import { useEffect, useRef, useState, type RefObject } from 'react';

/**
 * useHls — attach an HLS source to a `<video>` element.
 *
 * Strategy:
 *   - If the browser natively plays application/vnd.apple.mpegurl (Safari/iOS),
 *     just set videoEl.src and let the platform handle it. No JS bundle cost.
 *   - Otherwise dynamic-import hls.js (Vite chunk-splits this so the
 *     ~75 KB gzipped library never enters the main bundle), instantiate it,
 *     attach to the video element, and load the manifest.
 *
 * Falls back gracefully: when `manifestUrl` is null or `enabled` is false,
 * the hook is a no-op so the caller can still set `<video src={mp4Url}>`.
 *
 * On a fatal hls.js error, `error` is populated and the caller can switch to
 * the MP4 fallback URL by re-rendering with `manifestUrl=null`.
 */
export interface UseHlsOptions {
  enabled?: boolean;
}

export interface UseHlsResult {
  ready: boolean;
  error: Error | null;
}

export function useHls(
  videoRef: RefObject<HTMLVideoElement | null>,
  manifestUrl: string | null,
  opts: UseHlsOptions = {},
): UseHlsResult {
  const { enabled = true } = opts;
  const [ready, setReady] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  // Track the cleanup function so successive renders don't leak hls.js instances.
  const cleanupRef = useRef<(() => void) | null>(null);

  useEffect(() => {
    setReady(false);
    setError(null);

    // Tear down any previous attachment.
    if (cleanupRef.current) {
      cleanupRef.current();
      cleanupRef.current = null;
    }

    if (!enabled || !manifestUrl) return;

    const video = videoRef.current;
    if (!video) return;

    let cancelled = false;

    // Native HLS path (Safari/iOS).
    if (video.canPlayType('application/vnd.apple.mpegurl')) {
      const onLoaded = () => setReady(true);
      const onErr = () => setError(new Error('Native HLS playback error'));
      video.addEventListener('loadedmetadata', onLoaded);
      video.addEventListener('error', onErr);
      video.src = manifestUrl;
      cleanupRef.current = () => {
        video.removeEventListener('loadedmetadata', onLoaded);
        video.removeEventListener('error', onErr);
        // Don't clear src — caller may want to swap to MP4.
      };
      return () => {
        if (cleanupRef.current) {
          cleanupRef.current();
          cleanupRef.current = null;
        }
      };
    }

    // hls.js path. Dynamic import keeps this out of the main bundle.
    (async () => {
      try {
        const mod = await import('hls.js');
        if (cancelled) return;
        const Hls = mod.default;
        if (!Hls.isSupported()) {
          setError(new Error('HLS not supported in this browser'));
          return;
        }

        const hls = new Hls({
          maxBufferLength: 30,
          maxMaxBufferLength: 60,
          // Treat slow 3G gracefully: don't immediately escalate quality.
          startLevel: -1,
        });
        // Track NETWORK_ERROR recoveries so a permanently-404 manifest gives
        // up instead of looping — this is the signal callers use to trigger
        // their MP4 fallback. 2 retries handles transient network blips.
        let networkRetries = 0;
        const MAX_NETWORK_RETRIES = 2;
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, () => {
          if (!cancelled) setReady(true);
        });
        hls.on(Hls.Events.ERROR, (_evt, data) => {
          if (!data.fatal) return;
          switch (data.type) {
            case Hls.ErrorTypes.NETWORK_ERROR:
              if (networkRetries < MAX_NETWORK_RETRIES) {
                networkRetries++;
                hls.startLoad();
                return;
              }
              setError(new Error(`HLS network error: ${data.details}`));
              hls.destroy();
              cleanupRef.current = null;
              return;
            case Hls.ErrorTypes.MEDIA_ERROR:
              hls.recoverMediaError();
              return;
            default:
              setError(new Error(`HLS fatal error: ${data.details}`));
              hls.destroy();
              cleanupRef.current = null;
          }
        });
        hls.loadSource(manifestUrl);

        cleanupRef.current = () => {
          try {
            hls.destroy();
          } catch {
            /* ignore */
          }
        };
      } catch (err) {
        if (!cancelled) {
          setError(err instanceof Error ? err : new Error('Failed to load hls.js'));
        }
      }
    })();

    return () => {
      cancelled = true;
      if (cleanupRef.current) {
        cleanupRef.current();
        cleanupRef.current = null;
      }
    };
  }, [enabled, manifestUrl, videoRef]);

  return { ready, error };
}
