import { toNumber } from "@/lib/weather-utils"; import type { ImgwCurrentWeather, RawImgwHybridWeatherResponse, RawImgwHybridWeatherRow } from "@/types/imgw-current"; function toCelsius(value: unknown) { const temperature = toNumber(value); if (temperature === null) return null; return temperature > 150 ? temperature - 273.15 : temperature; } function toHectopascals(value: unknown) { const pressure = toNumber(value); if (pressure === null) return null; return pressure > 2_000 ? pressure / 100 : pressure; } function normalizeDate(value: unknown) { if (typeof value !== "string") return null; const date = new Date(value); return Number.isNaN(date.getTime()) ? null : date.toISOString(); } function getWeatherCode(iconCode: unknown) { if (typeof iconCode !== "string") return null; const match = iconCode.match(/z(\d{2})/i); return match ? Number(match[1]) : null; } function getCondition(weatherCode: number | null, rainfall10m: number | null, snowfall10m: number | null) { if (weatherCode !== null && weatherCode >= 95) return "thunderstorm" as const; if ((snowfall10m ?? 0) > 0) return "snow" as const; if ((rainfall10m ?? 0) > 0) return "rain" as const; return null; } function hasNumericValue(value: unknown) { return toNumber(value) !== null; } function isFullWeatherRow(candidate: RawImgwHybridWeatherRow) { return hasNumericValue(candidate.Temperature) && hasNumericValue(candidate.Chill) && hasNumericValue(candidate.Humidity) && hasNumericValue(candidate.Wind_Speed) && hasNumericValue(candidate.PressureMSL); } function hasPrecipitationValue(candidate: RawImgwHybridWeatherRow) { return hasNumericValue(candidate.Precipitation10m) || hasNumericValue(candidate.Rain10m) || hasNumericValue(candidate.Snow10m); } export function normalizeImgwCurrentWeather(payload: RawImgwHybridWeatherResponse): ImgwCurrentWeather | null { if (!payload.data?.Valid || !Array.isArray(payload.data.Data)) return null; const rows = payload.data.Data .filter((candidate): candidate is RawImgwHybridWeatherRow => { if (!candidate || typeof candidate !== "object") return false; return (candidate.Type === "Type_Ten_Minutes" || candidate.Type === "Type_Hour") && normalizeDate(candidate.Date) !== null; }) .sort((left, right) => String(left.Date).localeCompare(String(right.Date))); const fullRow = rows.find((candidate) => candidate.Type === "Type_Ten_Minutes" && isFullWeatherRow(candidate)) ?? rows.find((candidate) => candidate.Type === "Type_Hour" && isFullWeatherRow(candidate)); const precipitationRow = fullRow && hasPrecipitationValue(fullRow) ? fullRow : rows.find((candidate) => candidate.Type === "Type_Ten_Minutes" && hasPrecipitationValue(candidate)); const row = fullRow ?? precipitationRow; if (!row) return null; const measuredAt = normalizeDate(row.Date); if (!measuredAt) return null; const precipitationSource = precipitationRow ?? row; const rainfall10m = toNumber(precipitationSource.Rain10m); const snowfall10m = toNumber(precipitationSource.Snow10m); const weatherCode = getWeatherCode(row.Icon10); return { coverage: fullRow?.Type === "Type_Ten_Minutes" ? "full" : fullRow ? "hourly" : "precipitation-only", measuredAt, temperature: toCelsius(row.Temperature), feelsLike: toCelsius(row.Chill), windSpeed: toNumber(row.Wind_Speed), windDirection: toNumber(row.Wind_Dir), humidity: toNumber(row.Humidity), pressure: toHectopascals(row.PressureMSL), precipitation10m: toNumber(precipitationSource.Precipitation10m), rainfall10m, snowfall10m, cloudCover: toNumber(row.Cloud), weatherCode, condition: getCondition(weatherCode, rainfall10m, snowfall10m), }; } export async function fetchImgwCurrentWeather(latitude: number, longitude: number, signal?: AbortSignal) { const params = new URLSearchParams({ latitude: String(latitude), longitude: String(longitude) }); const response = await fetch(`/api/imgw-current?${params}`, { signal }); if (!response.ok) throw new Error("Nie udało się pobrać bieżącej analizy IMGW Hybrid."); return normalizeImgwCurrentWeather(await response.json() as RawImgwHybridWeatherResponse); }