import { useCallback, useRef, useState } from "react"; import { getAllSourceCodes, getDefaultFetchUrl, } from "../adapters/bestiary-index-adapter.js"; const BATCH_SIZE = 6; export interface BulkImportState { readonly status: "idle" | "loading" | "complete" | "partial-failure"; readonly total: number; readonly completed: number; readonly failed: number; } const IDLE_STATE: BulkImportState = { status: "idle", total: 0, completed: 0, failed: 0, }; interface BulkImportHook { state: BulkImportState; startImport: ( baseUrl: string, fetchAndCacheSource: (sourceCode: string, url: string) => Promise, isSourceCached: (sourceCode: string) => Promise, refreshCache: () => Promise, ) => void; reset: () => void; } export function useBulkImport(): BulkImportHook { const [state, setState] = useState(IDLE_STATE); const countersRef = useRef({ completed: 0, failed: 0 }); const startImport = useCallback( ( baseUrl: string, fetchAndCacheSource: (sourceCode: string, url: string) => Promise, isSourceCached: (sourceCode: string) => Promise, refreshCache: () => Promise, ) => { const allCodes = getAllSourceCodes(); const total = allCodes.length; countersRef.current = { completed: 0, failed: 0 }; setState({ status: "loading", total, completed: 0, failed: 0 }); (async () => { const cacheChecks = await Promise.all( allCodes.map(async (code) => ({ code, cached: await isSourceCached(code), })), ); const alreadyCached = cacheChecks.filter((c) => c.cached).length; const uncached = cacheChecks.filter((c) => !c.cached); countersRef.current.completed = alreadyCached; if (uncached.length === 0) { setState({ status: "complete", total, completed: total, failed: 0, }); return; } setState((s) => ({ ...s, completed: alreadyCached })); for (let i = 0; i < uncached.length; i += BATCH_SIZE) { const batch = uncached.slice(i, i + BATCH_SIZE); await Promise.allSettled( batch.map(async ({ code }) => { const url = getDefaultFetchUrl(code, baseUrl); try { await fetchAndCacheSource(code, url); countersRef.current.completed++; } catch (err) { countersRef.current.failed++; console.warn( `[bulk-import] FAILED ${code} (${url}):`, err instanceof Error ? err.message : err, ); } setState({ status: "loading", total, completed: countersRef.current.completed, failed: countersRef.current.failed, }); }), ); } await refreshCache(); const { completed, failed } = countersRef.current; setState({ status: failed > 0 ? "partial-failure" : "complete", total, completed, failed, }); })(); }, [], ); const reset = useCallback(() => { setState(IDLE_STATE); }, []); return { state, startImport, reset }; }