package com.hyperling.weather import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.HttpClientEngine import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.encodeURLParameter import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json /** * Repository for fetching weather data from OpenMeteo API. * OpenMeteo is free and does not require an API key for basic usage. */ object WeatherRepository { private const val OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast" private val json = Json { ignoreUnknownKeys = true prettyPrint = false } private lateinit var httpClient: HttpClient fun init(engine: HttpClientEngine) { httpClient = HttpClient(engine) { install(ContentNegotiation) { json(json) } } } /** * Fetch weather data for a location by coordinates. * Returns the parsed OpenMeteo daily forecast response. */ suspend fun fetchWeather(latitude: Double, longitude: Double): OpenMeteoResponse { return httpClient.get(OPEN_METEO_URL) { url { parameters.append("latitude", latitude.toString()) parameters.append("longitude", longitude.toString()) parameters.append("daily", "temperature_2m_max,temperature_2m_min") parameters.append("forecast_days", "7") parameters.append("timezone", "auto") } }.body() } /** * Parse the raw API response into our domain models. */ fun parseDailyWeather(response: OpenMeteoResponse): List { val dates = response.daily.time ?: emptyList() val highs = response.daily.temperature_2m_max ?: emptyList() val lows = response.daily.temperature_2m_min ?: emptyList() return dates.mapIndexed { index, date -> DailyWeather( date = date, tempMaxCelsius = if (index < highs.size) highs[index] else null, tempMinCelsius = if (index < lows.size) lows[index] else null, ) } } /** * Convert a location entry to weather data. */ suspend fun getWeatherForLocation(location: LocationEntry): Result { return try { val response = if (location.zipCode.isNotBlank()) { // OpenMeteo doesn't support zip code lookup directly, // but we can use the lat/long which should be populated fetchWeather(location.latitude, location.longitude) } else { fetchWeather(location.latitude, location.longitude) } val daily = parseDailyWeather(response) Result.success( LocationWeather( locationId = location.id, zipCode = location.zipCode, nickname = location.nickname, latitude = location.latitude, longitude = location.longitude, dailyWeather = daily, lastUpdatedMillis = System.currentTimeMillis(), ) ) } catch (e: Exception) { Result.failure(e) } } }