package ca.ksamad.encore.ui

import android.util.LruCache
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.palette.graphics.Palette
import ca.ksamad.encore.playback.SongArt
import coil3.SingletonImageLoader
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.allowHardware
import coil3.size.Size
import coil3.toBitmap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

// Small in-memory cache of extracted accent colours (ARGB ints), keyed by the art's cache key, so
// revisiting a song — or a recomposition — never re-runs Palette.
private val accentCache = LruCache<String, Int>(64)

/**
 * The dominant accent colour of [art]'s cover, used for the now-playing background bleed.
 *
 * Pulls a small bitmap from Coil (served from its memory/disk cache, so no extra network request),
 * runs [Palette] off the main thread, and memoises the result. Returns [fallback] until it resolves,
 * and whenever there's no art or extraction fails — so callers can just use the value directly.
 */
@Composable
fun rememberArtAccentColor(
    art: SongArt?,
    fallback: Color,
): Color {
    val context = LocalContext.current
    return produceState(initialValue = fallback, art) {
        if (art == null) {
            value = fallback
            return@produceState
        }
        val key = "${art.uri}@${art.version.orEmpty()}"
        accentCache.get(key)?.let {
            value = Color(it)
            return@produceState
        }
        val argb =
            withContext(Dispatchers.Default) {
                runCatching {
                    val loader = SingletonImageLoader.get(context)
                    val result =
                        loader.execute(
                            ImageRequest.Builder(context)
                                .data(art)
                                .size(Size(128, 128)) // tiny: Palette downsamples anyway
                                .allowHardware(false) // Palette must read pixels on the CPU
                                .build()
                        )
                    val bitmap =
                        (result as? SuccessResult)?.image?.toBitmap() ?: return@runCatching null
                    val palette = Palette.from(bitmap).maximumColorCount(16).generate()
                    val swatch =
                        palette.dominantSwatch ?: palette.vibrantSwatch ?: palette.mutedSwatch
                    swatch?.let { 0xFF000000.toInt() or it.rgb } // force opaque
                }
                    .getOrNull()
            }
        if (argb != null) {
            accentCache.put(key, argb)
            value = Color(argb)
        } else {
            value = fallback
        }
    }
        .value
}
