import { useEffect, useMemo, useRef, useState } from "react"; import type { GameProfile, ItadReview } from "@/lib/game/game-profile-types"; import type { Locale } from "@/lib/i18n/locales"; import { describeProfile, resolveGameLocale } from "@/lib/game/game-format"; import { fetchJson } from "@/lib/client-fetch"; export const MAX_RETRIES = 3; export const RETRY_COOLDOWN_MS = 5_000; export interface GameProfileState { profile: GameProfile | null; failed: boolean; localeTag: string; hero: string | null; dbRating: number | null; itadReviews: ItadReview[]; hasDetails: boolean; hasReviews: boolean; partialFail: boolean; metascore: ItadReview | null; opencritic: ItadReview | null; isDeterministicMiss: boolean; canRetry: boolean; setBannerPick: (shot: string | null) => void; retry: () => void; reset: () => void; } export function useGameProfile( applicationId: string, lang: Locale, heroSeed: number, ): GameProfileState { const [profile, setProfile] = useState(null); const [failed, setFailed] = useState(false); const [attempt, setAttempt] = useState(0); const [bannerPick, setBannerPick] = useState(null); const [retries, setRetries] = useState(0); const [cooling, setCooling] = useState(false); const coolingTimer = useRef(undefined); const { localeTag, country } = resolveGameLocale(lang); useEffect(() => { let cancelled = false; async function load(): Promise { const json = await fetchJson( `/api/game/${applicationId}?lang=${lang}&country=${country}`, ); if (!cancelled) { if (json) { setProfile(json); setFailed(false); } else { setFailed(true); } } } void load(); return () => { cancelled = true; }; }, [applicationId, lang, country, attempt]); const partialFail = !!profile && !profile.sources.igdb && !profile.sources.steam && !profile.sources.itad; const isDeterministicMiss = !!profile && !profile.steamId && !profile.sources.igdb && !profile.sources.itad; const hero = useMemo(() => { if (bannerPick) { return bannerPick; } const shots = profile?.screenshots ?? []; if (shots.length > 0) { return shots[heroSeed % shots.length] ?? null; } return profile?.artworkUrl ?? null; }, [profile, heroSeed, bannerPick]); const summary = describeProfile(profile); const canRetry = retries < MAX_RETRIES && !cooling; function retry(): void { if (!canRetry) { return; } setCooling(true); setRetries((n) => n + 1); setFailed(false); setAttempt((n) => n + 1); window.clearTimeout(coolingTimer.current); coolingTimer.current = window.setTimeout(() => setCooling(false), RETRY_COOLDOWN_MS); } function reset(): void { window.clearTimeout(coolingTimer.current); setCooling(false); setRetries(0); setProfile(null); setFailed(false); setAttempt((n) => n + 1); } useEffect(() => () => window.clearTimeout(coolingTimer.current), []); return { profile, failed, localeTag, hero, ...summary, partialFail, isDeterministicMiss, canRetry, setBannerPick, retry, reset, }; }