"use client"; import { useState, useCallback, useRef, useEffect } from "react"; import { fetchArticle, pickTwoArticles, normalizeTitle } from "./wiki"; const BLITZ_DURATION = 120; // 2 minutes export type BlitzPhase = "setup" | "playing" | "won" | "lost"; export function useBlitzGame() { const [phase, setPhase] = useState("setup"); const [html, setHtml] = useState(""); const [title, setTitle] = useState(""); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(null); const [timeLeft, setTimeLeft] = useState(BLITZ_DURATION); const [clicks, setClicks] = useState(0); const [history, setHistory] = useState([]); const [puzzle, setPuzzle] = useState<{ start: string; target: string } | null>(null); const clicksRef = useRef(0); const pathRef = useRef([]); const loadingRef = useRef(false); const endedRef = useRef(false); const intervalRef = useRef | null>(null); const startTimeRef = useRef(0); function stopTimer() { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } } function startTimer() { startTimeRef.current = Date.now(); intervalRef.current = setInterval(() => { const elapsed = (Date.now() - startTimeRef.current) / 1000; const left = Math.max(0, BLITZ_DURATION - elapsed); setTimeLeft(left); if (left <= 0 && !endedRef.current) { endedRef.current = true; stopTimer(); setPhase("lost"); saveGame(false, pathRef.current, clicksRef.current, BLITZ_DURATION); } }, 100); } useEffect(() => () => stopTimer(), []); async function saveGame(won: boolean, path: string[], clicks: number, timeSeconds: number) { if (!puzzle) return; fetch("/api/games", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "blitz", startArticle: puzzle.start, targetArticle: puzzle.target, path, clicks, timeSeconds: BLITZ_DURATION - timeLeft, won, }), }).catch(() => {}); } async function loadArticle(t: string) { setLoadError(null); setLoading(true); loadingRef.current = true; const art = await fetchArticle(t); setLoading(false); loadingRef.current = false; if (!art) { setLoadError(`Impossible de charger "${t}".`); return null; } setHtml(art.html); setTitle(art.title); return art.title; } async function start() { setLoading(true); stopTimer(); clicksRef.current = 0; setClicks(0); pathRef.current = []; setHistory([]); endedRef.current = false; setTimeLeft(BLITZ_DURATION); setLoadError(null); const p = await pickTwoArticles(); setPuzzle(p); const canonical = await loadArticle(p.start); if (!canonical) return; pathRef.current = [canonical]; setHistory([canonical]); setPhase("playing"); startTimer(); } const navigate = useCallback(async (t: string) => { if (loadingRef.current || endedRef.current) return; clicksRef.current += 1; setClicks(clicksRef.current); const canonical = await loadArticle(t); if (!canonical) return; const newPath = [...pathRef.current, canonical]; pathRef.current = newPath; setHistory(newPath); window.scrollTo({ top: 0, behavior: "smooth" }); if (puzzle && normalizeTitle(canonical) === normalizeTitle(puzzle.target)) { endedRef.current = true; stopTimer(); setPhase("won"); saveGame(true, newPath, clicksRef.current, timeLeft); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [puzzle, timeLeft]); function reset() { stopTimer(); endedRef.current = false; clicksRef.current = 0; setClicks(0); pathRef.current = []; setHistory([]); setHtml(""); setTitle(""); setTimeLeft(BLITZ_DURATION); setPuzzle(null); setPhase("setup"); setLoadError(null); } return { phase, puzzle, html, title, loading, loadError, timeLeft, clicks, history, canGoBack: false, // pas de retour en blitz start, navigate, reset, retryLoad: () => title && loadArticle(title), }; }