/**
 * Mouse event handling utilities.
 */

package compose.input

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.contextMenuOpenDetector
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.awtEventOrNull
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.*


/**
 * Mouse buttons.
 */
enum class MouseButton(private val pointerButton: PointerButton) {


    Left(PointerButton.Primary),
    Middle(PointerButton.Tertiary),
    Right(PointerButton.Secondary);


    /**
     * Returns whether the given event includes this button.
     */
    @OptIn(ExperimentalComposeUiApi::class)
    fun matches(event: PointerEvent): Boolean {
        return event.button == pointerButton
    }


}


/**
 * Mouse click counts.
 */
@Suppress("unused")
enum class ClickCount(internal val count: Int){


    /**
     * A click will be recognized on every mouse-press.
     */
    EACH(0),


    /**
     * Only the first in a series of rapid clicks will be recognized.
     */
    SINGLE(1),


    /**
     * Only the second in a series of clicks will be recognized.
     */
    DOUBLE(2),


    /**
     * Only the third in a series of clicks will be recognized.
     */
    TRIPLE(3);


    /**
     * Returns whether the given [PointerEvent] matches this [ClickCount].
     */
    internal fun matches(event: PointerEvent): Boolean {
        if (this == EACH)
            return true

        val awtEvent = event.awtEventOrNull

        // The AWT event is null when running a test, and we want to support that case
        return if (awtEvent == null) {
            (this == SINGLE).also {
                if (!it)
                    println("WARNING: Only single-click detection is supported without an AWT event")
            }
        } else
            awtEvent.clickCount % count == 0
    }


}


/**
 * A modifier for listening to mouse-press events.
 * This is different from the standard click detection in that it recognizes and reports the event on mouse-press,
 * rather than mouse-release + delay. This allows responding to events much sooner, but prevents more than one type of
 * event from being recognized on the same element. For example, two clicks in rapid succession will produce both a
 * single and a double click event.
 */
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.onMousePress(
    button: MouseButton = MouseButton.Left,
    keyboardModifier: KeyboardModifierMatcher = KeyboardModifierMatcher.None,
    clickCount: ClickCount = ClickCount.SINGLE,
    pass: PointerEventPass = PointerEventPass.Main,
    consumeEvent: Boolean = false,
    onPress: (PointerEvent) -> Unit,
) : Modifier {

    return this.onPointerEvent(PointerEventType.Press, pass = pass) { event ->
        val awtEvent = event.awtEventOrNull
        if (awtEvent?.isConsumed == true)
            return@onPointerEvent

        if (button.matches(event) && keyboardModifier.matches(event) && clickCount.matches(event)) {
            onPress.invoke(event)
            if (consumeEvent) {
                awtEvent?.consume()
                event.changes.forEach { it.consume() }
            }
        }
    }
}


/**
 * A modifier for detecting the gesture that opens a context menu.
 */
@OptIn(ExperimentalFoundationApi::class)
fun Modifier.onOpenContextMenuGesture(
    enabled: Boolean = true,
    action: (Offset) -> Unit
) = contextMenuOpenDetector(key = action, enabled = enabled, onOpen = action)
