import { useState, useEffect, useRef, useCallback } from 'react'; import type { WienerLinienStop, WienerLinienDeparture } from '@timetoleave/core'; import { api } from '../services/api'; interface DepartureRow { stopId: string; lineName: string; direction: string; minutes: number; } const DEBOUNCE_MS = 400; const REFRESH_INTERVAL_MS = 60_000; function transformDeparture(dep: WienerLinienDeparture): DepartureRow { const minutes = Math.max(0, Math.round((dep.departureTime - Date.now()) / 60_000)); return { stopId: dep.stopId, lineName: dep.line.name, direction: dep.direction, minutes, }; } /** * Fetch nearby WienerLinien stops and their departures. * Mirrors the web app's useWienerLinien hook, adapted for mobile API client. */ export function useWienerLinien( lat: number | undefined, lng: number | undefined, radius?: number, ) { const [stops, setStops] = useState([]); const [departures, setDepartures] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const stopIdsRef = useRef([]); const abortRef = useRef(null); const cancelledRef = useRef(false); // Effect for fetching stops and initial departures useEffect(() => { cancelledRef.current = false; const resetState = () => { setStops([]); setDepartures([]); setError(null); setLoading(false); }; if (lat === undefined || lng === undefined) { resetState(); return; } const debounceTimer = setTimeout(async () => { if (cancelledRef.current) return; abortRef.current?.abort(); const abortController = new AbortController(); abortRef.current = abortController; setLoading(true); setError(null); try { // Fetch nearby stops const stopsList = await api.findNearbyStops(lat, lng, radius ?? 500); if (cancelledRef.current) return; setStops(stopsList); setLoading(false); const ids = stopsList.map((s) => s.id); stopIdsRef.current = ids; // Chain monitor fetch for departures if (ids.length > 0) { try { // Fetch departures for each stop - note: mobile API client doesn't have // a direct monitor endpoint, so we skip this for now // The web app uses an internal API route for this } catch { // Silently ignore departure fetch errors } } } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return; if (cancelledRef.current) return; setError(err instanceof Error ? err.message : 'An unexpected error occurred'); setLoading(false); } }, DEBOUNCE_MS); return () => { cancelledRef.current = true; clearTimeout(debounceTimer); abortRef.current?.abort(); abortRef.current = null; }; }, [lat, lng, radius]); // Effect for periodic departures refresh useEffect(() => { if (stops.length === 0) return; const intervalId = setInterval(async () => { const currentIds = stopIdsRef.current; if (currentIds.length === 0) return; // Refresh logic would go here if we had the monitor API // For now, this is a placeholder for future implementation }, REFRESH_INTERVAL_MS); return () => clearInterval(intervalId); }, [stops.length]); return { stops, departures, loading, error }; }