package com.applemand.zmanim.core.location

import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class LocationRepository @Inject constructor(
    private val dao: CityDao,
) {
    suspend fun search(query: String, limit: Int = 50): List<CityEntity> {
        val normalized = query.trim().lowercase().ifEmpty { return dao.topPopulous(limit) }
        // If the user typed a full country name ("Israel", "united states"), the FTS
        // index only stores the 2-letter ISO code. Substitute so the query still matches.
        val countryCode = CountryNames.codeFor(normalized)
        val ftsQuery = if (countryCode != null) {
            // Country alias hit — match the 2-letter code as a whole token so we don't
            // prefix-match into unrelated city names (e.g. "il" shouldn't catch Illinois).
            countryCode.lowercase()
        } else {
            normalized.split(Regex("\\s+"))
                .joinToString(" ") { "$it*" }
        }
        return runCatching { dao.searchByName(ftsQuery, limit) }.getOrDefault(emptyList())
    }

    /**
     * Returns the city closest to the given coordinate, or null if the candidate set is empty.
     * Ties broken by population.
     */
    suspend fun nearest(lat: Double, lng: Double): CityEntity? {
        val candidates = dao.candidatesNear(lat, lng)
        if (candidates.isEmpty()) return null
        return candidates
            .sortedWith(
                compareBy<CityEntity> { haversine(lat, lng, it.lat, it.lng) }
                    .thenByDescending { it.population }
            )
            .first()
    }

    suspend fun byId(id: Long): CityEntity? = dao.byId(id)
}

/**
 * Haversine distance in kilometers.  Sufficient precision for ranking; not used for zmanim.
 */
fun haversine(lat1: Double, lng1: Double, lat2: Double, lng2: Double): Double {
    val r = 6371.0
    val dLat = Math.toRadians(lat2 - lat1)
    val dLng = Math.toRadians(lng2 - lng1)
    val a = sin(dLat / 2).let { it * it } +
            cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
            sin(dLng / 2).let { it * it }
    val c = 2 * atan2(sqrt(a), sqrt(1 - a))
    return r * c
}
