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

import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastRoundToInt

@Stable
class Camera(
    private val initialZoom: Float = 2f,
    private val initialOffset: IntOffset = IntOffset.Zero,
) {
    private var _zoom = mutableStateOf(initialZoom)
    val zoom: Float by _zoom

    private var _offset = mutableStateOf(initialOffset)
    val offset: IntOffset by _offset

    private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero)

    private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
    val cameraSize: IntSize get() = _cameraSize
    val cameraSizeZoomed: IntSize by derivedStateOf {
        IntSize(
            width = (cameraSize.width * zoom).fastRoundToInt(),
            height = (cameraSize.height * zoom).fastRoundToInt(),
        )
    }

    fun changeSizes(
        sceneSize: IntSize,
        cameraSize: IntSize,
    ) {
        _cameraSize = cameraSize
        _sceneSize = sceneSize
    }

    fun move(
        offset: IntOffset,
    ) {
        _offset.value = offset
    }

    fun pan(
        delta: Offset,
    ) {
        val value = _offset.value - IntOffset(
            x = (delta.x * zoom).fastRoundToInt(),
            y = (delta.y * zoom).fastRoundToInt(),
        )
        _offset.value = value
    }

    fun zoom(
        power: Float,
        target: IntOffset? = null,
    ) {
        val zoomTarget = _zoom.value * (1f - power)

        val projection = cameraSizeProjection(
            zoomTarget = zoomTarget,
        )
        val targetDelta = Offset(
            x = target?.x?.toFloat()?.div(_cameraSize.width.toFloat()) ?: 0.5f,
            y = target?.y?.toFloat()?.div(_cameraSize.height.toFloat()) ?: 0.5f,
        )
        val offsetTarget = _offset.value + IntOffset(
            x = (targetDelta.x * projection.width).fastRoundToInt(),
            y = (targetDelta.y * projection.height).fastRoundToInt(),
        )

        _zoom.value = zoomTarget
        _offset.value = offsetTarget
    }

    fun resetPosition() {
        _offset.value = initialOffset
    }

    fun resetZoom() {
        _zoom.value = initialZoom
    }

    private fun cameraSizeProjection(zoomTarget: Float): Size = Size(
        width = cameraSize.width * _zoom.value - cameraSize.width * zoomTarget,
        height = cameraSize.height * _zoom.value - cameraSize.height * zoomTarget,
    )
}