"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { subscribeVideoStatusRefresh } from "./videoStatusEvents"; interface Status { codes: Set; subtitleCodes: Set; count: number; lastScannedAt: number; rootsScanned: string[]; } interface Ctx extends Status { hasVideo: (code: string | null | undefined) => boolean; hasSubtitle: (code: string | null | undefined) => boolean; refresh: () => Promise; } const empty: Status = { codes: new Set(), subtitleCodes: new Set(), count: 0, lastScannedAt: 0, rootsScanned: [] }; const VideoIdxCtx = createContext(null); export function VideoIndexProvider({ children }: { children: React.ReactNode }) { const [status, setStatus] = useState(empty); const inflightRef = useRef(null); const refresh = useCallback(async () => { // Abort any prior fetch so a slow first request can't clobber a // newer second request's result on settle order. inflightRef.current?.abort(); const ctrl = new AbortController(); inflightRef.current = ctrl; try { const r = await fetch("/api/video-status", { cache: "no-store", signal: ctrl.signal }); if (!r.ok) return; const j = await r.json(); if (ctrl.signal.aborted) return; setStatus({ codes: new Set(Array.isArray(j.codes) ? j.codes : []), subtitleCodes: new Set(Array.isArray(j.subtitleCodes) ? j.subtitleCodes : []), count: j.count ?? 0, lastScannedAt: j.lastScannedAt ?? 0, rootsScanned: Array.isArray(j.rootsScanned) ? j.rootsScanned : [], }); } catch { // Silent — if the endpoint fails or aborts, no badges. No user-facing error. } finally { if (inflightRef.current === ctrl) inflightRef.current = null; } }, []); useEffect(() => { refresh(); return () => { inflightRef.current?.abort(); }; }, [refresh]); useEffect(() => subscribeVideoStatusRefresh(() => { refresh(); }), [refresh]); const value = useMemo(() => ({ ...status, hasVideo: (code) => { if (!code) return false; return status.codes.has(code.toUpperCase()); }, hasSubtitle: (code) => { if (!code) return false; return status.subtitleCodes.has(code.toUpperCase()); }, refresh, }), [status, refresh]); return {children}; } export function useVideoIndex(): Ctx { const ctx = useContext(VideoIdxCtx); if (!ctx) throw new Error("useVideoIndex must be used within VideoIndexProvider"); return ctx; }