import { useState, useEffect } from "react"; import type { Journey } from "@/types"; import { parseHafasTime, hafasDateTime } from "@/lib/hafas-time"; interface HafasJourney { ctxRecon?: string; secL?: Array<{ dep?: { dTimeS?: string; dTimeR?: string; dPlatfS?: string }; arr?: { aTimeS?: string; aTimeR?: string }; jny?: { prodX?: number; stopL?: Array<{ name: string }>; dlySum?: number; isCncl?: boolean; }; chgDurR?: number; }>; } function parseHafasJourneys(json: unknown, hafasDate: string, queryDate: Date): Journey[] { // HAFAS response shape is too complex for a clean TypeScript type; use any for parsing. // eslint-disable-next-line @typescript-eslint/no-explicit-any -- HAFAS response shape is undocumented and deeply nested const data = json as any; const outConL: HafasJourney[] = data?.svcResL?.[0]?.res?.outConL ?? []; return outConL.map((con, i): Journey => { const first = con.secL?.[0]; const last = con.secL?.[con.secL.length - 1]; const dep = first?.dep; const arr = last?.arr; const sD = dep?.dTimeS ? parseHafasTime(hafasDate, dep.dTimeS) : queryDate; const rD = dep?.dTimeR ? parseHafasTime(hafasDate, dep.dTimeR) : sD; const sA = arr?.aTimeS ? parseHafasTime(hafasDate, arr.aTimeS) : sD; const rA = arr?.aTimeR ? parseHafasTime(hafasDate, arr.aTimeR) : sA; const delayMs = rD.getTime() - sD.getTime(); const delay = Math.max(0, Math.round(delayMs / 60000)); const trains = (con.secL ?? []) .filter((s) => s.jny) .map((s) => s.jny?.stopL?.[0]?.name ?? "") .filter(Boolean); const cancelled = (con.secL ?? []).some((s) => s.jny?.isCncl === true); const changes = Math.max(0, (con.secL ?? []).filter((s) => s.jny).length - 1); const platform = first?.dep?.dPlatfS ?? ""; return { id: con.ctxRecon ?? `journey-${i}`, sD, rD, sA, rA, delay, platform, changes, trains, cancelled, }; }); } export function useJourneys(fromStationExtId: string | null, toStationExtId: string | null, date: Date) { const [journeys, setJourneys] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; const fetchJourneys = async () => { if (!fromStationExtId || !toStationExtId || !date) { return; } setLoading(true); setError(null); try { const { date: hafasDate, time: hafasTime } = hafasDateTime(date); const body = { svcReqL: [ { meth: "TripSearch", req: { depLocL: [{ type: "S", extId: fromStationExtId }], arrLocL: [{ type: "S", extId: toStationExtId }], outDate: hafasDate, outTime: hafasTime, numF: 5, }, }, ], }; const response = await fetch("/api/hafas", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) { const errBody = await response.json().catch(() => ({})); throw new Error(errBody.error ?? `HAFAS request failed with status ${response.status}`); } const data = await response.json(); const result = parseHafasJourneys(data, hafasDate, date); if (isMounted) { setJourneys(result); setLoading(false); } } catch (err: unknown) { if (isMounted) { const message = err instanceof Error ? err.message : "Failed to fetch journeys"; setError(message); setLoading(false); } } }; fetchJourneys(); return () => { isMounted = false; }; }, [fromStationExtId, toStationExtId, date]); return { journeys, loading, error }; }