package de.arxsilex.maptools.converter.desktop

import androidx.compose.ui.geometry.Offset
import de.arxsilex.maptools.converter.core.CircleSelection
import de.arxsilex.maptools.converter.core.GeoBounds
import kotlin.math.abs
import kotlin.math.cos
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class MapCanvasProjectionTest {
    private val bounds = GeoBounds(west = 7.8, south = 50.5, east = 8.8, north = 51.1)
    private val projection = MapCanvasProjection(bounds)

    @Test
    fun `geographic circle remains round on a wide canvas`() {
        val circle = CircleSelection(centerX = 0.5f, centerY = 0.5f, radiusKilometers = 2.0)
        val transform = MapCanvasTransform(projection, MapViewport.Full, 1_600f, 900f)

        val screenBounds = circleScreenGeometry(circle, bounds, transform).bounds

        assertTrue(abs(screenBounds.width - screenBounds.height) < 1.5f)
    }

    @Test
    fun `circle larger than source bounds keeps its full round preview geometry`() {
        val circle = CircleSelection(centerX = 0.15f, centerY = 0.5f, radiusKilometers = 55.0)
        val transform = MapCanvasTransform(projection, MapViewport.Full, 1_600f, 900f)

        val clippedBounds = transform.normalizedAreaToScreenRect(circle.normalizedEnvelope(bounds))
        val geometry = circleScreenGeometry(circle, bounds, transform)
        val aspectRatio = geometry.bounds.width / geometry.bounds.height
        val center = transform.normalizedToScreen(Offset(circle.centerX, circle.centerY))

        assertTrue(aspectRatio in 0.96f..1.04f)
        assertTrue(geometry.bounds.left < clippedBounds.left)
        assertTrue(geometry.bounds.top < clippedBounds.top)
        assertTrue(geometry.bounds.bottom > clippedBounds.bottom)
        assertTrue(abs(geometry.bounds.center.x - center.x) < geometry.bounds.width * 0.02f)
        assertTrue(abs(clippedBounds.center.x - center.x) > geometry.bounds.width * 0.1f)
        assertTrue(geometry.contains(center))
    }

    @Test
    fun `circle resize keeps radius when drag starts on bounding box corner`() {
        val circle = CircleSelection(centerX = 0.5f, centerY = 0.5f, radiusKilometers = 20.0)
        val corner = Offset(0.65f, 0.65f)

        assertEquals(
            circle.radiusKilometers,
            resizedCircleRadius(bounds, circle, corner, corner),
            1e-9,
        )
    }

    @Test
    fun `circle resize scales relative to initial handle distance`() {
        val circle = CircleSelection(centerX = 0.5f, centerY = 0.5f, radiusKilometers = 20.0)
        val start = Offset(0.55f, 0.5f)
        val end = Offset(0.60f, 0.5f)

        assertEquals(
            40.0,
            resizedCircleRadius(bounds, circle, start, end),
            0.05,
        )
    }

    @Test
    fun `screen and normalized coordinates round trip`() {
        val transform = MapCanvasTransform(projection, MapViewport.Full, 1_600f, 900f)
        val normalized = Offset(0.35f, 0.62f)

        val restored = transform.screenToNormalized(transform.normalizedToScreen(normalized))

        assertEquals(normalized.x, restored.x, 0.000_01f)
        assertEquals(normalized.y, restored.y, 0.000_01f)
    }

    @Test
    fun `equal local distances use equal screen scale`() {
        val transform = MapCanvasTransform(projection, MapViewport.Full, 1_600f, 900f)
        val centerLatitude = 50.8
        val centerLongitude = 8.3
        val latitudeDelta = 1.0 / 111.32
        val longitudeDelta = 1.0 / (111.32 * cos(Math.toRadians(centerLatitude)))
        val center = transform.geoToScreen(centerLongitude, centerLatitude)
        val north = transform.geoToScreen(centerLongitude, centerLatitude + latitudeDelta)
        val east = transform.geoToScreen(centerLongitude + longitudeDelta, centerLatitude)

        assertTrue(abs((north - center).getDistance() - (east - center).getDistance()) < 0.5f)
    }

    @Test
    fun `positive map translation pans viewport west`() {
        val transform = MapCanvasTransform(
            projection,
            MapViewport.centeredAt(0.5f, 0.5f, 0.25f),
            1_600f,
            900f,
        )

        val delta = transform.panDeltaForScreenTranslation(Offset(120f, 0f))

        assertTrue(delta.x < 0f)
        assertEquals(0f, delta.y, 0.000_01f)
    }
}
