package com.pixelized.desktop.lwa.ui.shaders

import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
import androidx.compose.foundation.border
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.mikepenz.hypnoticcanvas.NonAndroidRuntimeEffect
import com.mikepenz.hypnoticcanvas.shaders.Shader
import com.mikepenz.hypnoticcanvas.utils.round

@Composable
fun Modifier.shaderContent(
    shader: Shader,
    speed: Float = 1f,
    fallback: () -> Brush = {
        Brush.horizontalGradient(listOf(Color.Transparent, Color.Transparent))
    },
): Modifier {
    val runtimeEffect = remember(shader) { NonAndroidRuntimeEffect(shader) }
    var size: Size by remember { mutableStateOf(Size(-1f, -1f)) }
    val speedModifier = shader.speedModifier

    val time by if (runtimeEffect.supported) {
        var startMillis = remember(shader) { -1L }
        produceState(0f, speedModifier) {
            while (true) {
                withInfiniteAnimationFrameMillis {
                    if (startMillis < 0) startMillis = it
                    value = ((it - startMillis) / 16.6f) / 10f
                }
            }
        }
    } else {
        mutableStateOf(-1f)
    }

    return this then Modifier
        .onGloballyPositioned {
            size = Size(it.size.width.toFloat(), it.size.height.toFloat())
        }
        .drawWithContent {
            drawContent()
            // set uniforms for the shaders
            runtimeEffect.update(
                shader = shader,
                time = (time * speed * speedModifier).round(3),
                width = size.width,
                height = size.height
            )
            if (runtimeEffect.ready) {
                drawRect(brush = runtimeEffect.build(), blendMode = BlendMode.SrcAtop)
            } else {
                drawRect(brush = fallback())
            }
        }
}

@Composable
fun Modifier.shaderBorder(
    width: Dp = 1.dp,
    shape: Shape = RectangleShape,
    shader: Shader,
    speed: Float = 1f,
    fallback: () -> Brush = {
        Brush.horizontalGradient(listOf(Color.Transparent, Color.Transparent))
    },
): Modifier {
    val runtimeEffect = remember(shader) { NonAndroidRuntimeEffect(shader) }
    var size: Size by remember { mutableStateOf(Size(-1f, -1f)) }
    val speedModifier = shader.speedModifier

    val time by if (runtimeEffect.supported) {
        var startMillis = remember(shader) { -1L }
        produceState(0f, speedModifier) {
            while (true) {
                withInfiniteAnimationFrameMillis {
                    if (startMillis < 0) startMillis = it
                    value = ((it - startMillis) / 16.6f) / 10f
                }
            }
        }
    } else {
        mutableStateOf(-1f)
    }

    return this then Modifier
        .onGloballyPositioned {
            size = Size(it.size.width.toFloat(), it.size.height.toFloat())
        }
        .border(
            width = width,
            shape = shape,
            brush = run {
                // set uniforms for the shaders
                runtimeEffect.update(
                    shader = shader,
                    time = (time * speed * speedModifier).round(3),
                    width = size.width,
                    height = size.height
                )
                if (runtimeEffect.ready) {
                    runtimeEffect.build()
                } else {
                    fallback()
                }
            },
        )
}
