import { useEffect, useRef, useState } from 'react'; import type { Scan } from '../api'; export interface ScanTimerResult { /** Seconds elapsed since the scan started processing. */ elapsedSeconds: number; /** * Estimated seconds remaining, or null when not enough data yet * (fewer than 5 routes scanned or elapsed time is 0). */ remainingSeconds: number | null; /** True while the estimate is still too early to be reliable. */ isEstimating: boolean; } const MIN_ROUTES_FOR_ESTIMATE = 5; function calcElapsed(startedAt: string): number { return Math.max(0, (Date.now() - new Date(startedAt).getTime()) / 1000); } function calcRemaining( elapsed: number, routesScanned: number, totalRoutes: number, ): number | null { if (elapsed <= 0 || routesScanned < MIN_ROUTES_FOR_ESTIMATE || totalRoutes <= 0) { return null; } const rate = routesScanned / elapsed; // routes per second const remaining = (totalRoutes - routesScanned) / rate; return Math.max(0, remaining); } export function useScanTimer(scan: Scan | null): ScanTimerResult { const [elapsedSeconds, setElapsedSeconds] = useState(0); const [remainingSeconds, setRemainingSeconds] = useState(null); const intervalRef = useRef | undefined>(undefined); useEffect(() => { if (!scan) return; // For completed / failed scans with both timestamps: compute static duration. if ( (scan.status === 'completed' || scan.status === 'failed') && scan.started_at && scan.completed_at ) { const duration = Math.max( 0, (new Date(scan.completed_at).getTime() - new Date(scan.started_at).getTime()) / 1000, ); setElapsedSeconds(duration); setRemainingSeconds(0); return; } // For running scans with a start time: run a live 1-second timer. if (scan.status === 'running' && scan.started_at) { const tick = () => { const elapsed = calcElapsed(scan.started_at!); const remaining = calcRemaining(elapsed, scan.routes_scanned, scan.total_routes); setElapsedSeconds(elapsed); setRemainingSeconds(remaining); }; tick(); // run immediately intervalRef.current = setInterval(tick, 1000); return () => { if (intervalRef.current !== undefined) { clearInterval(intervalRef.current); intervalRef.current = undefined; } }; } // Pending or no started_at: reset setElapsedSeconds(0); setRemainingSeconds(null); }, [scan?.status, scan?.started_at, scan?.completed_at, scan?.routes_scanned, scan?.total_routes]); const isEstimating = scan?.status === 'running' && (scan.routes_scanned < MIN_ROUTES_FOR_ESTIMATE || scan.total_routes <= 0); return { elapsedSeconds, remainingSeconds, isEstimating }; }