import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { fetchEngineStatus, startEngine, type EngineStatusResponse } from "../api"; const POLL_INTERVAL_MS = 10000; const DISCONNECTED_UNREACHABLE_STATUS: EngineStatusResponse = { connected: false, starting: false, canStart: false, reason: "unreachable", }; export interface UseEngineStatusResult { status: EngineStatusResponse | null; loading: boolean; error: string | null; canStart: boolean; starting: boolean; refetch: () => Promise; start: () => Promise; } function getErrorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } /* * FNXC:EngineStatusBanner 2026-06-22-00:00: * The banner must never leave the board silently inert. Poll while the current project is disconnected, stop once connected to avoid steady-state traffic, and fold local Start engine clicks into `starting` so the button cannot be double-triggered before the server reports its transient starting state. */ export function useEngineStatus(projectId?: string): UseEngineStatusResult { const [status, setStatus] = useState(null); const [loading, setLoading] = useState(Boolean(projectId)); const [error, setError] = useState(null); const [startInFlight, setStartInFlight] = useState(false); const requestIdRef = useRef(0); const refetch = useCallback(async () => { const requestId = requestIdRef.current + 1; requestIdRef.current = requestId; if (!projectId) { setStatus(null); setLoading(false); setStartInFlight(false); return; } setLoading(true); try { const nextStatus = await fetchEngineStatus(projectId); if (requestIdRef.current !== requestId) return; setStatus(nextStatus); if (nextStatus.connected) { setStartInFlight(false); } } catch { if (requestIdRef.current !== requestId) return; setStatus({ ...DISCONNECTED_UNREACHABLE_STATUS, projectId }); } finally { if (requestIdRef.current === requestId) { setLoading(false); } } }, [projectId]); const start = useCallback(async () => { if (!projectId) return; setStartInFlight(true); setError(null); try { const startedStatus = await startEngine(projectId); setStatus(startedStatus); await refetch(); } catch (err) { setError(getErrorMessage(err)); } finally { setStartInFlight(false); } }, [projectId, refetch]); useEffect(() => { setStatus(null); setError(null); setStartInFlight(false); void refetch(); }, [refetch]); useEffect(() => { if (!projectId || status?.connected) return; const interval = window.setInterval(() => { void refetch(); }, POLL_INTERVAL_MS); return () => { window.clearInterval(interval); }; }, [projectId, refetch, status?.connected]); return useMemo(() => ({ status, loading, error, canStart: Boolean(status?.canStart), starting: Boolean(status?.starting || startInFlight), refetch, start, }), [error, loading, refetch, start, startInFlight, status]); }