package com.pixelized.desktop.lwa.ui.composable.scene.camera

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.isCtrlPressed
import androidx.compose.ui.input.pointer.isPrimaryPressed
import androidx.compose.ui.input.pointer.isTertiaryPressed
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import kotlin.math.sign

@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
fun Modifier.onCameraControl(
    sceneSize: IntSize,
    camera: Camera,
): Modifier {
    val offsetDelta = CursorDelta()
    return this
        .onSizeChanged {
            camera.changeSizes(
                sceneSize = sceneSize,
                cameraSize = it,
            )
        }
        .onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
            offsetDelta.handlePositionChange(
                event = event,
            ) { delta ->
                when {
                    event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.pan(
                        delta = delta,
                    )
                }
            }
        }
        .onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
            val change = event.changes.first()
            camera.zoom(
                power = -change.scrollDelta.y.sign * 0.15f,
                target = change.position.round(),
            )
        }
}

private data class CursorDelta(
    var lastDeltaTimestamp: Long = System.currentTimeMillis(),
    var previousPosition: Offset = Offset.Zero,
    var currentPosition: Offset = Offset.Zero,
) {
    inline fun handlePositionChange(
        event: PointerEvent,
        delay: Float = 10f,
        crossinline block: (delta: Offset) -> Unit,
    ) {
        val currentTimestamp = System.currentTimeMillis()
        if (currentTimestamp - lastDeltaTimestamp > delay) {
            lastDeltaTimestamp = currentTimestamp
            previousPosition = currentPosition
            currentPosition = event.changes.first().position
            block(currentPosition - previousPosition)
        }
    }
}