import type { DailyForecast, ForecastSource, HourlyForecast, WeatherForecast } from "@/types/forecast"; function readNumber(value: unknown) { return typeof value === "number" && Number.isFinite(value) ? value : null; } function readString(value: unknown) { return typeof value === "string" && value ? value : null; } function isForecastSource(value: unknown): value is ForecastSource { return value === "imgw-alaro" || value === "open-meteo"; } function normalizeSources(value: unknown): ForecastSource[] { return Array.isArray(value) ? value.filter(isForecastSource) : []; } function normalizeHourlyForecast(value: unknown): HourlyForecast[] { return Array.isArray(value) ? value.flatMap((candidate) => { if (!candidate || typeof candidate !== "object") return []; const row = candidate as Partial; const time = readString(row.time); if (!time) return []; return [{ time, temperature: readNumber(row.temperature), feelsLike: readNumber(row.feelsLike), precipitationProbability: readNumber(row.precipitationProbability), precipitation: readNumber(row.precipitation), weatherCode: readNumber(row.weatherCode), windSpeed: readNumber(row.windSpeed), source: isForecastSource(row.source) ? row.source : "open-meteo", }]; }) : []; } function normalizeDailyForecast(value: unknown): DailyForecast[] { return Array.isArray(value) ? value.flatMap((candidate) => { if (!candidate || typeof candidate !== "object") return []; const row = candidate as Partial; const date = readString(row.date); if (!date) return []; return [{ date, temperatureMax: readNumber(row.temperatureMax), temperatureMin: readNumber(row.temperatureMin), precipitationProbability: readNumber(row.precipitationProbability), precipitation: readNumber(row.precipitation), weatherCode: readNumber(row.weatherCode), sunrise: readString(row.sunrise), sunset: readString(row.sunset), sources: normalizeSources(row.sources), }]; }) : []; } function normalizeForecast(raw: Partial): WeatherForecast { const latitude = Number(raw.latitude); const longitude = Number(raw.longitude); if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) throw new Error("Forecast service returned invalid coordinates."); return { latitude, longitude, timezone: typeof raw.timezone === "string" ? raw.timezone : "Europe/Warsaw", hourly: normalizeHourlyForecast(raw.hourly), daily: normalizeDailyForecast(raw.daily), sources: normalizeSources(raw.sources), }; } export async function fetchForecast(latitude: number, longitude: number, signal?: AbortSignal) { const params = new URLSearchParams({ latitude: String(latitude), longitude: String(longitude) }); const response = await fetch(`/api/forecast?${params}`, { signal }); if (!response.ok) throw new Error("Unable to load forecast."); return normalizeForecast(await response.json() as Partial); }