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

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
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.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 = Animatable(
        initialValue = initialZoom,
        typeConverter = Float.VectorConverter,
    )
    val zoom: Float get() = _zoom.value

    private var _offset = Animatable(
        initialValue = initialOffset,
        typeConverter = IntOffset.VectorConverter,
    )
    val offset: IntOffset by derivedStateOf {
        _offset.value + IntOffset(
            x = (_sceneSize.width - cameraSizeZoomed.width) / 2,
            y = (_sceneSize.height - cameraSizeZoomed.height) / 2,
        )
    }

    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
    }

    suspend fun handlePanning(
        delta: Offset,
        snap: Boolean,
    ) {
        val value = _offset.value - IntOffset(
            x = (delta.x * zoom).fastRoundToInt(),
            y = (delta.y * zoom).fastRoundToInt(),
        )
        when {
            snap -> _offset.snapTo(targetValue = value)
            else -> _offset.animateTo(targetValue = value)
        }
    }

    suspend fun handleZoom(
        zoomIn: Boolean,
        power: Float,
        snap: Boolean = false,
    ) {
        val value = _zoom.value * when {
            zoomIn -> 1f - power
            else -> 1f + power
        }
        when {
            snap -> _zoom.snapTo(targetValue = value)
            else -> _zoom.animateTo(targetValue = value)
        }
    }

    suspend fun resetPosition(
        snap: Boolean = false,
    ) {
        when (snap) {
            true -> _offset.snapTo(targetValue = initialOffset)
            else -> _offset.animateTo(targetValue = initialOffset)
        }
    }

    suspend fun resetZoom(
        snap: Boolean = false,
    ) {
        when (snap) {
            true -> _zoom.snapTo(targetValue = initialZoom)
            else -> _zoom.animateTo(targetValue = initialZoom)
        }
    }
}