package core

/**
 * Represents a user interest with associated keywords and a relevance weight.
 *
 * @param name Human-readable label for the interest (e.g. "gastronomia").
 * @param keywords Terms used to match against POI category, name and tags.
 * @param weight Multiplier applied to the raw score for this interest. Defaults to 1.0.
 */
data class Interest(
    val name: String,
    val keywords: List<String>,
    val weight: Double = 1.0
)

/**
 * A POI enriched with a relevance score and the list of interests it matched.
 *
 * @param id Unique identifier from the source data.
 * @param lat Latitude in decimal degrees.
 * @param lon Longitude in decimal degrees.
 * @param name Human-readable name of the place.
 * @param category OSM-style category string (e.g. "restaurant", "museum").
 * @param tags Arbitrary key-value metadata from the source (OSM tags, etc.).
 * @param score Aggregated relevance score (sum of per-interest scores × weights).
 * @param matchedInterests Names of the interests that contributed to the score.
 */
data class ScoredPOI(
    val id: Long,
    val lat: Double,
    val lon: Double,
    val name: String,
    val category: String,
    val tags: Map<String, String>,
    val score: Double,
    val matchedInterests: List<String>
)

/**
 * Filters and ranks a list of POIs according to a user interest profile.
 *
 * Scoring per interest (weights applied to each sub-score):
 *   - Category match : +0.5 if `poi["category"]` equals any keyword (exact, case-insensitive)
 *   - Name match     : +0.3 if any keyword is a substring of `poi["name"]` (case-insensitive)
 *   - Tags match     : +0.2 if any keyword appears in any tag value (case-insensitive)
 *
 * The per-interest score is multiplied by [Interest.weight] before being added to the total.
 * Multiple interests accumulate. Only POIs with total score > 0 are returned.
 *
 * @param pois Raw POI maps from parsed JSON. Expected keys: "id" (Long), "lat" (Double),
 *             "lon" (Double), "name" (String), "category" (String), "tags" (Map<String,String>).
 * @param interests User interest profile to match against.
 * @param maxResults Maximum number of results to return. Defaults to 10.
 * @return Top [maxResults] POIs with score > 0, sorted by score descending.
 *         Empty list if no POI matches any interest.
 */
fun matchPoisToInterests(
    pois: List<Map<String, Any>>,
    interests: List<Interest>,
    maxResults: Int = 10
): List<ScoredPOI> {
    val scored = mutableListOf<ScoredPOI>()

    for (poi in pois) {
        val category = (poi["category"] as? String ?: "").lowercase()
        val name = (poi["name"] as? String ?: "").lowercase()

        @Suppress("UNCHECKED_CAST")
        val tags = (poi["tags"] as? Map<String, String>) ?: emptyMap()
        val tagValues = tags.values.joinToString(" ").lowercase()

        var totalScore = 0.0
        val matched = mutableListOf<String>()

        for (interest in interests) {
            val keywords = interest.keywords.map { it.lowercase() }
            var interestScore = 0.0

            // Category match: 0.5 if poi category equals any keyword (exact)
            if (keywords.any { it == category }) {
                interestScore += 0.5
            }

            // Name match: 0.3 if any keyword is a substring of poi name
            if (keywords.any { it in name }) {
                interestScore += 0.3
            }

            // Tags match: 0.2 if any keyword appears in any tag value
            if (keywords.any { it in tagValues }) {
                interestScore += 0.2
            }

            if (interestScore > 0.0) {
                totalScore += interestScore * interest.weight
                matched.add(interest.name)
            }
        }

        if (totalScore > 0.0) {
            scored.add(
                ScoredPOI(
                    id = (poi["id"] as? Long) ?: (poi["id"] as? Int)?.toLong() ?: 0L,
                    lat = (poi["lat"] as? Double) ?: 0.0,
                    lon = (poi["lon"] as? Double) ?: 0.0,
                    name = poi["name"] as? String ?: "",
                    category = poi["category"] as? String ?: "",
                    tags = tags,
                    score = Math.round(totalScore * 1_000_000.0) / 1_000_000.0,
                    matchedInterests = matched.toList()
                )
            )
        }
    }

    return scored
        .sortedByDescending { it.score }
        .take(maxResults)
}
