package com.ponzischeme89.memby.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.tv.material3.Text
import com.ponzischeme89.memby.R
import com.ponzischeme89.memby.ServiceLocator
import com.ponzischeme89.memby.data.Settings
import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.MediaRating
import com.ponzischeme89.memby.data.model.displayable
import com.ponzischeme89.memby.data.model.formattedScore
import com.ponzischeme89.memby.data.model.ratingDisplayLimit
import com.ponzischeme89.memby.data.model.wordmark
import com.ponzischeme89.memby.ui.theme.MembyMutedText
import com.ponzischeme89.memby.ui.theme.MembyOnSurface
import com.ponzischeme89.memby.ui.theme.MembyRatingsSurface
import kotlin.math.roundToInt

private val RatingStripHeight = 34.dp
private val RatingMarkHeight = 19.dp
private val RatingMarkHeightCompact = 16.dp

/** One non-focusable, layout-stable presentation for ratings everywhere in the app. */
@Composable
fun RatingsStrip(
    ratings: List<MediaRating>,
    visible: Boolean,
    modifier: Modifier = Modifier,
    reserveSpace: Boolean = false,
    compact: Boolean = false,
) {
    if (!visible) return
    val valid = remember(ratings) { ratings.displayable() }
    if (valid.isEmpty()) {
        if (reserveSpace) Spacer(modifier.height(RatingStripHeight))
        return
    }
    BoxWithConstraints(
        modifier = modifier
            .then(if (reserveSpace) Modifier.height(RatingStripHeight) else Modifier)
            .fillMaxWidth()
            .clipToBounds()
            .testTag("ratings-strip"),
        contentAlignment = Alignment.CenterStart,
    ) {
        // The responsive limit is based on the usable width inside the capsule.
        val contentWidth = (maxWidth.value.roundToInt() - 24).coerceAtLeast(0)
        val shown = valid.take(ratingDisplayLimit(contentWidth))
        Row(
            modifier = Modifier
                .clip(RoundedCornerShape(percent = 50))
                .background(MembyRatingsSurface)
                .padding(horizontal = 12.dp, vertical = 6.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(if (compact) 8.dp else 13.dp),
        ) {
            shown.forEach { rating ->
                Row(
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    RatingMark(rating, compact)
                    Text(
                        rating.formattedScore(),
                        color = MembyOnSurface,
                        fontSize = if (compact) 13.sp else 15.sp,
                        fontWeight = FontWeight.SemiBold,
                        maxLines = 1,
                    )
                }
            }
        }
    }
}

/**
 * Lazy item adapter.
 *
 * A card whose payload already carries ratings draws them with the row, costing nothing:
 * the gateway attaches what it has stored to every item it sends. Only a title it has
 * never looked up falls back to the dedicated request, which cards make on focus and
 * heroes and detail pages make immediately.
 */
@Composable
fun ItemRatingsStrip(
    item: BaseItem,
    load: Boolean,
    modifier: Modifier = Modifier,
    reserveSpace: Boolean = true,
    compact: Boolean = false,
) {
    val settings by ServiceLocator.repository.settingsFlow.collectAsState(initial = Settings.EMPTY)
    var ratings by remember(item.id) { mutableStateOf(item.membyRatings) }
    // A focused home item is replaced in-place by its full Emby details. Include the
    // carried ratings in the key so an item can pick them up from the settled response
    // without requiring the viewer to move focus away and back.
    LaunchedEffect(item.id, item.membyRatings, load, settings.showRatingsStrip) {
        if (item.membyRatings.isNotEmpty()) {
            ratings = item.membyRatings
            return@LaunchedEffect
        }
        if (load && settings.showRatingsStrip) ratings = ServiceLocator.repository.getRatings(item)
    }
    RatingsStrip(
        ratings = ratings,
        visible = settings.showRatingsStrip,
        modifier = modifier,
        reserveSpace = reserveSpace,
        compact = compact,
    )
}

/**
 * A provider is shown by its own mark where one exists and by its wordmark where it does
 * not — the strip has to stay readable for a source no build anticipated, and the score
 * beside a bare wordmark says nothing about where it came from.
 */
@Composable
private fun RatingMark(rating: MediaRating, compact: Boolean) {
    val icon = ratingIcon(rating.source)
    if (icon != null) {
        val height = (if (compact) RatingMarkHeightCompact else RatingMarkHeight) *
            markScale(rating.source)
        Image(
            painter = painterResource(icon),
            contentDescription = rating.wordmark(),
            contentScale = ContentScale.Fit,
            modifier = Modifier.height(height),
        )
    } else {
        Text(
            rating.wordmark(),
            color = providerColor(rating.source),
            fontSize = if (compact) 12.sp else 14.sp,
            fontWeight = FontWeight.Bold,
            maxLines = 1,
            overflow = TextOverflow.Clip,
        )
    }
}

/**
 * Only the critics' tomato is drawn as a tomato: the audience score is a different
 * measurement and wearing the same mark would claim it was the same number.
 */
private fun ratingIcon(source: String): Int? = when (source.lowercase()) {
    "tomatoes" -> R.drawable.ic_rating_tomatoes
    "metacritic" -> R.drawable.ic_rating_metacritic
    "imdb" -> R.drawable.ic_rating_imdb
    "letterboxd" -> R.drawable.ic_rating_letterboxd
    "tmdb" -> R.drawable.ic_rating_tmdb
    else -> null
}

/**
 * Marks are set to a common height, which is the wrong measure for artwork that stacks
 * its type on two lines: TMDb's lettering ends up half the size of everyone else's. The
 * scale brings its letters back to the same cap height rather than its box.
 */
private fun markScale(source: String): Float =
    if (source.lowercase() == "tmdb") 1.25f else 1f

private fun providerColor(source: String): Color = when (source.lowercase()) {
    "imdb" -> Color(0xFFF5C518)
    "tomatoes", "audience" -> Color(0xFFFA5252)
    "metacritic" -> Color(0xFFFFCC34)
    "letterboxd" -> Color(0xFF40BCF4)
    "tmdb" -> Color(0xFF90CEA1)
    "trakt" -> Color(0xFFED1C24)
    "mal", "anilist", "anidb", "kitsu" -> Color(0xFF74A8FF)
    else -> MembyMutedText
}
