package dev.kaizen.app.ui.theme

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.colorspace.ColorSpaces
import kotlin.math.cbrt
import kotlin.math.pow

object Oklab {

    private fun toLinear(c: Double): Double =
        if (c <= 0.04045) c / 12.92 else ((c + 0.055) / 1.055).pow(2.4)

    private fun fromLinear(c: Double): Float {
        val cl = c.coerceIn(0.0, 1.0)
        return if (cl <= 0.0031308) (cl * 12.92).toFloat()
        else (1.055 * cl.pow(1.0 / 2.4) - 0.055).toFloat()
    }

    data class Lab(val l: Float, val a: Float, val b: Float)

    fun Color.toOklab(): Lab {
        val lr = toLinear(red.toDouble())
        val lg = toLinear(green.toDouble())
        val lb = toLinear(blue.toDouble())

        val l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb
        val m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb
        val s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb

        val lc = cbrt(l)
        val mc = cbrt(m)
        val sc = cbrt(s)

        return Lab(
            l = (0.2104542553 * lc + 0.7936177850 * mc - 0.0040720468 * sc).toFloat(),
            a = (1.9779984951 * lc - 2.4285922050 * mc + 0.4505937099 * sc).toFloat(),
            b = (0.0259040371 * lc + 0.7827717662 * mc - 0.8086757660 * sc).toFloat(),
        )
    }

    fun Lab.toColor(): Color {
        val ld = l.toDouble()
        val ad = a.toDouble()
        val bd = b.toDouble()

        val lp = ld + 0.3963377774 * ad + 0.2158037573 * bd
        val mp = ld - 0.1055613458 * ad - 0.0638541728 * bd
        val sp = ld - 0.0894841775 * ad - 1.2914855480 * bd

        val lc = lp * lp * lp
        val mc = mp * mp * mp
        val sc = sp * sp * sp

        val r = +4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc
        val g = -1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc
        val b = -0.0041960863 * lc - 0.7034186147 * mc + 1.7076147010 * sc

        return Color(red = fromLinear(r), green = fromLinear(g), blue = fromLinear(b), alpha = 1.0f)
    }

    fun Lab.toColor(targetColorSpace: androidx.compose.ui.graphics.colorspace.ColorSpace): Color {
        val base = toColor()
        return Color(red = base.red, green = base.green, blue = base.blue, alpha = 1.0f,
            colorSpace = targetColorSpace)
    }

    fun interpolate(from: Color, to: Color, fraction: Float): Color {
        val f = from.toOklab()
        val t = to.toOklab()

        val lab = Lab(
            l = f.l + (t.l - f.l) * fraction,
            a = f.a + (t.a - f.a) * fraction,
            b = f.b + (t.b - f.b) * fraction,
        )

        val useP3 = from.colorSpace == ColorSpaces.DisplayP3 || to.colorSpace == ColorSpaces.DisplayP3
        val color = if (useP3) lab.toColor(ColorSpaces.DisplayP3) else lab.toColor()
        return color.copy(alpha = from.alpha + (to.alpha - from.alpha) * fraction)
    }

    fun generateGradientStops(from: Color, to: Color, steps: Int = 12): List<Color> {
        return List(steps) { i ->
            interpolate(from, to, i.toFloat() / (steps - 1).toFloat())
        }
    }
}
