package compose.utils

import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationNodeFactory
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.HoverInteraction
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch


/**
 * An indication that doesn't draw anything.
 */
@Suppress("unused")
data object NoIndication : IndicationNodeFactory {

    override fun create(interactionSource: InteractionSource): DelegatableNode {
        return NoIndicationInstance
    }

    private object NoIndicationInstance : Modifier.Node(), DrawModifierNode {
        override fun ContentDrawScope.draw() {
            drawContent()
        }
    }

}


/**
 * Highlights the item on mouse-hover.
 */
fun Modifier.highlightOnHover(
    shape: Shape = RectangleShape,
    color: Color = Color.Gray,
    alpha: Float = 0.1f
) = composed {
    val interactionSource = remember { MutableInteractionSource() }
    this
        .indication(
            interactionSource = interactionSource,
            indication = highlight(
                shape = shape,
                color = color,
                alpha = alpha,
            )
        )
        .hoverable(
            interactionSource = interactionSource,
        )
}


/**
 * Returns a highlight indication.
 */
@Stable
fun highlight(
    shape: Shape = RectangleShape,
    color: Color = Color.Gray,
    alpha: Float = 0.1f
): Indication {
    return if ((shape == RectangleShape) && (color == Color.Gray) && (alpha == 0.1f))
        DefaultHighlightIndication
    else
        HighlightIndication(shape, color, alpha)
}


/**
 * The [HighlightIndication] with the default arguments.
 */
private val DefaultHighlightIndication = HighlightIndication(RectangleShape, Color.Gray, 0.1f)


/**
 * An indication that highlights the item with the given color and alpha when mouse-hovered on.
 */
private class HighlightIndication(
    private val shape: Shape,
    private val color: Color,
    private val alpha: Float,
): IndicationNodeFactory {

    override fun create(interactionSource: InteractionSource): DelegatableNode {
        return HighlightIndicationNode(
            interactionSource, shape, color, alpha
        )
    }

    override fun equals(other: Any?): Boolean {
        if (other !is HighlightIndication) return false
        if (this === other) return true

        return shape == other.shape && color == other.color && alpha == other.alpha
    }

    override fun hashCode(): Int {
        var result = shape.hashCode()
        result = 31 * result + color.hashCode()
        result = 31 * result + alpha.hashCode()
        return result
    }

}


/**
 * Highlights the item with the given color and alpha.
 */
private class HighlightIndicationNode(
    private val interactionSource: InteractionSource,
    private val shape: Shape,
    private val color: Color,
    private val alpha: Float,
): Modifier.Node(), DrawModifierNode {

    private var visible by mutableStateOf(false)

    override fun onAttach() {
        coroutineScope.launch {
            interactionSource.interactions.collectLatest {
                when (it) {
                    is HoverInteraction.Enter -> visible = true
                    is HoverInteraction.Exit -> visible = false
                }
            }
        }
    }

    override fun ContentDrawScope.draw() {
        drawContent()
        if (visible){
            drawOutline(
                shape.createOutline(size, LayoutDirection.Ltr, density = Density(density)),
                color = color,
                alpha = alpha
            )
        }
    }

}
