import { useEffect, useState } from 'react' const REGISTRY = new Map() const ROOT_MARGIN = '800px 0px' const IDLE_TIMEOUT = 3000 export function __resetDeferredSimilarAiCache() { for (const entry of REGISTRY.values()) { entry.controller?.abort() } REGISTRY.clear() } function scheduleIdle(callback) { if (typeof window.requestIdleCallback === 'function') { const handle = window.requestIdleCallback(callback, { timeout: IDLE_TIMEOUT }) return () => window.cancelIdleCallback?.(handle) } const handle = window.setTimeout(callback, IDLE_TIMEOUT) return () => window.clearTimeout(handle) } export function useDeferredSimilarAi(artworkId, anchorRef) { const [state, setState] = useState(() => ({ id: artworkId, items: REGISTRY.get(Number(artworkId || 0))?.items || [], })) const [loading, setLoading] = useState(false) const [error, setError] = useState(false) useEffect(() => { let cancelled = false const id = Number(artworkId || 0) const controller = new AbortController() let attachedEntry = null let triggered = false const currentEntry = REGISTRY.get(id) const cached = currentEntry?.status === 'completed' ? currentEntry.items : null setState({ id, items: cached || [] }) setLoading(false) setError(false) if (!id || cached) { return () => controller.abort() } let stopIdle = () => {} let observer = null let observationEstablished = false const detach = () => { if (!attachedEntry) return attachedEntry.subscribers -= 1 const entry = attachedEntry attachedEntry = null if (entry.subscribers === 0 && entry.status === 'in-flight') { entry.controller.abort() } } const attach = (entry) => { if (cancelled || attachedEntry === entry) return attachedEntry = entry entry.subscribers += 1 setLoading(true) entry.promise .then((nextItems) => { if (!cancelled) setState({ id, items: nextItems }) }) .catch((requestError) => { if (!cancelled && requestError?.name !== 'AbortError') { setState({ id, items: [] }) setError(true) } }) .finally(() => { if (!cancelled) setLoading(false) if (attachedEntry === entry) { attachedEntry = null entry.subscribers -= 1 } }) } const load = async () => { if (triggered || cancelled) return triggered = true observer?.disconnect() stopIdle() let entry = REGISTRY.get(id) if (entry?.status === 'completed') { if (!cancelled) setState({ id, items: entry.items }) return } if (entry?.status !== 'in-flight' || entry.controller.signal.aborted) { const requestController = new AbortController() entry = { status: 'in-flight', controller: requestController, promise: null, subscribers: 0, } REGISTRY.set(id, entry) entry.promise = Promise.resolve() .then(() => fetch(`/api/art/${id}/similar-ai`, { credentials: 'same-origin', signal: requestController.signal, })) .then((response) => { if (!response.ok) throw new Error('similar fetch failed') return response.json() }) .then((payload) => { const nextItems = Array.isArray(payload?.data) ? payload.data : [] if (REGISTRY.get(id) === entry) { REGISTRY.set(id, { status: 'completed', items: nextItems }) } return nextItems }) .catch((requestError) => { if (REGISTRY.get(id) === entry) REGISTRY.delete(id) throw requestError }) entry.promise.catch(() => {}) } attach(entry) } if (typeof window.IntersectionObserver === 'function' && anchorRef?.current) { try { observer = new window.IntersectionObserver((entries) => { if (entries.some((entry) => entry.isIntersecting)) load() }, { rootMargin: ROOT_MARGIN }) observer.observe(anchorRef.current) observationEstablished = true } catch { observer?.disconnect() observer = null } } if (!observationEstablished) { stopIdle = scheduleIdle(load) } return () => { cancelled = true observer?.disconnect() stopIdle() detach() controller.abort() } }, [artworkId, anchorRef]) return { items: Number(state.id || 0) === Number(artworkId || 0) ? state.items : [], loading: Number(state.id || 0) === Number(artworkId || 0) ? loading : false, error: Number(state.id || 0) === Number(artworkId || 0) ? error : false, } }