import type { HydroStation, RawHydroStation, RawSynopStation, RawWarning, SynopStation, WeatherMood, WeatherWarning, WarningKind, } from "@/types/imgw"; const polishLocale = "pl-PL"; export function toNumber(value: unknown): number | null { if (typeof value === "number") return Number.isFinite(value) ? value : null; if (typeof value !== "string" || value.trim() === "") return null; const parsed = Number(value.replace(",", ".")); return Number.isFinite(parsed) ? parsed : null; } function normalizeDate(value?: string | null): string | null { if (!value?.trim()) return null; const isoCandidate = value.includes("T") ? value : value.replace(" ", "T"); const date = new Date(isoCandidate); return Number.isNaN(date.getTime()) ? null : date.toISOString(); } function synopMeasuredAt(date?: string | null, hour?: string | null) { if (!date?.trim() || !hour?.trim()) return null; return normalizeDate(`${date}T${hour.padStart(2, "0")}:00:00Z`); } export function normalizeSynopStation(raw: RawSynopStation): SynopStation | null { if (!raw.id_stacji?.trim() || !raw.stacja?.trim()) return null; return { id: raw.id_stacji, name: raw.stacja, measuredAt: synopMeasuredAt(raw.data_pomiaru, raw.godzina_pomiaru), temperature: toNumber(raw.temperatura), windSpeed: toNumber(raw.predkosc_wiatru), windDirection: toNumber(raw.kierunek_wiatru), humidity: toNumber(raw.wilgotnosc_wzgledna), rainfall: toNumber(raw.suma_opadu), pressure: toNumber(raw.cisnienie), }; } export function normalizeHydroStation(raw: RawHydroStation): HydroStation | null { if (!raw.id_stacji?.trim() || !raw.stacja?.trim()) return null; return { id: raw.id_stacji, name: raw.stacja, river: raw.rzeka?.trim() || null, province: raw.wojewodztwo?.trim() || null, longitude: toNumber(raw.lon), latitude: toNumber(raw.lat), waterLevel: toNumber(raw.stan_wody), waterLevelMeasuredAt: normalizeDate(raw.stan_wody_data_pomiaru), waterTemperature: toNumber(raw.temperatura_wody), waterTemperatureMeasuredAt: normalizeDate(raw.temperatura_wody_data_pomiaru), flow: toNumber(raw.przeplyw), flowMeasuredAt: normalizeDate(raw.przeplyw_data), icePhenomenon: toNumber(raw.zjawisko_lodowe), overgrowthPhenomenon: toNumber(raw.zjawisko_zarastania), }; } export function normalizeWarning(raw: RawWarning, kind: WarningKind, index: number): WeatherWarning { const describedAreas = (raw.obszary ?? []) .map((area) => area.opis?.trim() || area.wojewodztwo?.trim()) .filter((area): area is string => Boolean(area)); const areas = describedAreas.length ? describedAreas : (raw.teryt ?? []).map((code) => `TERYT ${code}`); const title = raw.zdarzenie?.trim() || raw.nazwa_zdarzenia?.trim() || (kind === "meteo" ? "Ostrzeżenie meteorologiczne" : "Ostrzeżenie hydrologiczne"); return { id: `${kind}-${raw.id ?? raw.numer ?? index}-${raw.data_od ?? raw.obowiazuje_od ?? "unknown"}`, kind, level: toNumber(raw["stopień"] ?? raw.stopien), title, description: raw.przebieg?.trim() || raw.tresc?.trim() || null, comment: raw.komentarz?.trim() || null, validFrom: normalizeDate(raw.data_od ?? raw.obowiazuje_od), validTo: raw.data_do?.startsWith("9999-") ? null : normalizeDate(raw.data_do ?? raw.obowiazuje_do), publishedAt: normalizeDate(raw.opublikowano), probability: toNumber(raw.prawdopodobienstwo), areas, office: raw.biuro?.trim() || null, }; } export function formatTemperature(value: number | null) { return value === null ? "Brak danych" : `${Math.round(value)}°`; } export function formatPressure(value: number | null) { return value === null ? "Brak danych" : `${value.toFixed(1)} hPa`; } export function formatHumidity(value: number | null) { return value === null ? "Brak danych" : `${Math.round(value)}%`; } export function formatWind(speed: number | null, direction?: number | null) { if (speed === null) return "Brak danych"; const directionLabel = direction === null || direction === undefined ? "" : ` ${getWindDirection(direction)}`; return `${speed.toFixed(1)} m/s${directionLabel}`; } export function formatRainfall(value: number | null) { return value === null ? "Brak danych" : `${value.toFixed(value < 1 ? 2 : 1)} mm`; } export function formatWaterLevel(value: number | null) { return value === null ? "Brak danych" : `${Math.round(value)} cm`; } export function formatFlow(value: number | null) { return value === null ? "Brak danych" : `${value.toFixed(2)} m³/s`; } export function formatDateTime(value: string | null, fallback = "Brak danych") { if (!value) return fallback; const date = new Date(value); if (Number.isNaN(date.getTime())) return fallback; return new Intl.DateTimeFormat(polishLocale, { day: "numeric", month: "long", hour: "2-digit", minute: "2-digit", }).format(date); } export function calculateFeelsLike(temperature: number | null, humidity: number | null, windSpeed: number | null) { if (temperature === null) return null; if (temperature <= 10 && windSpeed !== null && windSpeed > 1.34) { const windKmh = windSpeed * 3.6; return 13.12 + 0.6215 * temperature - 11.37 * windKmh ** 0.16 + 0.3965 * temperature * windKmh ** 0.16; } if (temperature >= 27 && humidity !== null) { const c1 = -8.78469475556; const c2 = 1.61139411; const c3 = 2.33854883889; const c4 = -0.14611605; const c5 = -0.012308094; const c6 = -0.0164248277778; const c7 = 0.002211732; const c8 = 0.00072546; const c9 = -0.000003582; return c1 + c2 * temperature + c3 * humidity + c4 * temperature * humidity + c5 * temperature ** 2 + c6 * humidity ** 2 + c7 * temperature ** 2 * humidity + c8 * temperature * humidity ** 2 + c9 * temperature ** 2 * humidity ** 2; } return temperature; } export function getWindDirection(degrees: number) { const labels = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]; return labels[Math.round((((degrees % 360) + 360) % 360) / 45) % 8]; } export function getWeatherMoodFromData(station: SynopStation, date = new Date()): WeatherMood { const hour = date.getHours(); if (hour < 6 || hour >= 21) return "night"; if ((station.rainfall ?? 0) >= 0.1) return "rain"; if ((station.windSpeed ?? 0) >= 8) return "wind"; if ((station.temperature ?? 15) <= 3) return "cold"; if ((station.temperature ?? 15) >= 20) return "clear"; return "mild"; } export function getWeatherDescription(station: SynopStation) { if ((station.rainfall ?? 0) >= 5) return "Wyraźne opady"; if ((station.rainfall ?? 0) >= 0.1) return "Opady"; if ((station.windSpeed ?? 0) >= 8) return "Silny wiatr"; if ((station.humidity ?? 0) >= 90) return "Wilgotno"; return "Spokojne warunki"; } export function moodGradient(mood: WeatherMood) { return { clear: "from-sky-500 via-blue-500 to-indigo-700", rain: "from-slate-500 via-slate-600 to-indigo-900", wind: "from-cyan-600 via-slate-500 to-blue-900", cold: "from-cyan-400 via-blue-500 to-indigo-800", night: "from-slate-800 via-indigo-950 to-slate-950", mild: "from-sky-500 via-cyan-600 to-blue-800", }[mood]; }