import { useCallback, useEffect, useRef } from "react"; import { api } from "./api"; /** The shared feed-priority flow used by both the channel manager and the channel detail page: * an optimistic bump/set with a debounced server flush (~400ms after the last notch) so a * press-and-hold sends ONE request, not one per step, and pending flushes fire on unmount so a * last-moment bump isn't lost. The cache-shape-specific bits are injected (the manager maps over a * ManagedChannel[]; the detail patches one ChannelDetail), so the two call sites can't drift — the * same drift the sibling `useChannelTagActions` hook prevents for tags. * * Timers are keyed by channel id, so a pending flush survives the caller switching channels: it * fires later and reads the right cache via `read(id)`, rather than needing the caller to flush on * every id change. `read`/`optimistic`/`onSuccess`/`onError` are read through a ref so the returned * callbacks stay stable — the unmount flush then runs only on unmount, never on every render. */ export function useChannelPriority(opts: { /** Current priority for this channel from whichever cache the caller owns (undefined if absent). */ read: (channelId: string) => number | undefined; /** Apply the optimistic priority to the caller's cache. */ optimistic: (channelId: string, next: number) => void; /** Called after the server write lands (e.g. invalidate sibling caches). Optional. */ onSuccess?: (channelId: string) => void; /** Called if the server write fails — revert by refetching the owning cache. */ onError: (channelId: string) => void; /** Debounce window in ms; defaults to 400. */ delay?: number; }) { const timers = useRef>>(new Map()); const optsRef = useRef(opts); optsRef.current = opts; const flush = useCallback((channelId: string) => { const latest = optsRef.current.read(channelId); if (latest == null) return; api .updateChannel(channelId, { priority: latest }) .then(() => optsRef.current.onSuccess?.(channelId)) .catch(() => optsRef.current.onError(channelId)); }, []); // Set an absolute priority (used by both the arrows — via bumpPriority — and the inline edit), // optimistically + a debounced flush. const setPriority = useCallback( (channelId: string, next: number) => { optsRef.current.optimistic(channelId, next); const pending = timers.current.get(channelId); if (pending) clearTimeout(pending); timers.current.set( channelId, setTimeout(() => { timers.current.delete(channelId); flush(channelId); }, optsRef.current.delay ?? 400), ); }, [flush], ); // Read the LATEST value from the cache each step (not a render closure) so a hold-repeat // accumulates correctly. const bumpPriority = useCallback( (channelId: string, delta: number) => { setPriority(channelId, (optsRef.current.read(channelId) ?? 0) + delta); }, [setPriority], ); useEffect(() => { const pending = timers.current; return () => { pending.forEach((timer, channelId) => { clearTimeout(timer); flush(channelId); }); pending.clear(); }; }, [flush]); return { setPriority, bumpPriority }; }