package solutions.tretter.mindmachine.session

import androidx.compose.ui.geometry.Offset
import org.junit.Assert.*
import org.junit.Test

/**
 * Unit tests for single-finger drag gesture handling.
 * Tests both point dragging (up/down) and handle dragging (start/end selection).
 */
class DragGestureTest {

    @Test
    fun `single-finger point dragging updates curve values`() {
        val curve = TimelineCurve.constant(0.5f, 600)
        val heightPx = 200f

        // Simulate dragging point at index 0 upward (Y decreases, value increases)
        val newY = 20f // Near top of screen
        val result = curve.movePointVertical(0, newY, heightPx)

        // Value should increase (since Y decreased from center)
        assertTrue("Dragging up should increase value", result.points[0].value01 > 0.5f)

        // Simulate dragging point downward (Y increases, value decreases)
        val newY2 = 180f // Near bottom of screen
        val result2 = curve.movePointVertical(0, newY2, heightPx)

        // Value should decrease (since Y increased from center)
        assertTrue("Dragging down should decrease value", result2.points[0].value01 < 0.5f)
    }

    @Test
    fun `single-finger handle dragging constrains selection range`() {
        val state = TimelineEditorState.default(durationSec = 600)
        val originalSelection = state.selection

        // Simulate dragging start handle forward (increase startSec)
        // But ensure it doesn't exceed endSec - 60 (minimum 60 second duration)
        val newStartSec = originalSelection.startSec + 120

        // Clamp the selection to respect minimum duration
        val clampedStart = newStartSec.coerceAtMost(originalSelection.endSec - 60)

        val newSelection = TimelineSelection(startSec = clampedStart, endSec = originalSelection.endSec)

        // Verify minimum duration is enforced
        assertTrue("Selection should maintain minimum duration", newSelection.durationSec >= 60)
    }

    @Test
    fun `single-finger handle dragging clamps to timeline bounds`() {
        val state = TimelineEditorState.default(durationSec = 600)

        // Try to drag end handle beyond timeline duration
        val tooFarEnd = state.durationSec + 100

        // Should be clamped to duration
        val clampedEnd = tooFarEnd.coerceAtMost(state.durationSec)

        assertEquals(state.durationSec, clampedEnd)
    }

    @Test
    fun `single-finger panning updates viewport start position`() {
        val viewport = TimelineViewport(startSec = 100f, secondsPerScreen = 600f)
        val widthPx = 400f

        // Simulate panning right (drag left) - moves viewport later in time
        val deltaXPan = -50f // Negative means moving right in time
        val newViewport = viewport.applyZoomPan(
            zoomChange = 1f,
            panPx = deltaXPan,
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Start position should have increased (moved later in time)
        assertTrue("Panning right should increase startSec", newViewport.startSec > viewport.startSec)
    }

    @Test
    fun `hitTestPoint finds nearest curve point for dragging`() {
        val points = listOf(
            TimelinePoint(0, 0.5f),
            TimelinePoint(60, 0.8f),
            TimelinePoint(120, 0.3f),
        )
        val curve = TimelineCurve(points)
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 200f)
        val widthPx = 400f
        val heightPx = 200f

        // Click near the middle point (time=60, value=0.8, Y ~ 40)
        // timeToX(60) = (60 / 200) * 400 = 120
        // valueToY(0.8) = (1 - 0.8) * 200 = 40
        val offset = Offset(x = 125f, y = 45f)

        val hitIndex = curve.hitTestPoint(offset, viewport, widthPx, heightPx)

        // Should hit index 1 (the middle point)
        assertEquals(1, hitIndex)
    }

    @Test
    fun `moving multiple points creates consistent curve`() {
        val curve = TimelineCurve.constant(0.5f, 600)
        val heightPx = 200f

        // Drag point at index 0 to top
        val result1 = curve.movePointVertical(0, 0f, heightPx)

        // Drag point at index 1 to bottom
        val result2 = result1.movePointVertical(1, heightPx, heightPx)

        // Verify both changes persist
        assertTrue("First point should be at top", result2.points[0].value01 > 0.9f)
        assertTrue("Second point should be at bottom", result2.points[1].value01 < 0.1f)
    }

    @Test
    fun `selection dragging respects minimum 60 second duration constraint`() {
        val startSec = 0
        val endSec = 120

        // Try to drag end handle to 140 (would make duration 140, still valid)
        val newEndSec = 140
        val selection = TimelineSelection(startSec, newEndSec)
        assertTrue("140 second duration should be valid", selection.durationSec >= 60)

        // Try to drag start handle to 90 (would make duration 30, invalid)
        val invalidStartSec = 90
        val clampedStart = invalidStartSec.coerceAtMost(endSec - 60) // Should clamp to 60
        val clampedSelection = TimelineSelection(clampedStart, endSec)
        assertTrue("Minimum duration should be enforced", clampedSelection.durationSec >= 60)
    }

    @Test
    fun `viewport panning prevents scrolling past timeline start`() {
        val viewport = TimelineViewport(startSec = 50f, secondsPerScreen = 600f)
        val widthPx = 400f

        // Try to pan left past start (should be clamped to 0)
        val result = viewport.applyZoomPan(
            zoomChange = 1f,
            panPx = 100f, // Pan left
            widthPx = widthPx,
            centroidPx = widthPx / 2f
        )

        // Start should be clamped to 0 or above
        assertTrue("Viewport start should not be negative", result.startSec >= 0f)
    }

    @Test
    fun `screen coordinate to time conversion is accurate for dragging`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f

        // At x=400 (right edge), time should be 600 seconds
        val rightEdgeTime = viewport.xToTimeSec(400f, widthPx)
        assertEquals(600f, rightEdgeTime, 0.5f)

        // At x=200 (center), time should be 300 seconds
        val centerTime = viewport.xToTimeSec(200f, widthPx)
        assertEquals(300f, centerTime, 0.5f)
    }

    @Test
    fun `time to screen coordinate conversion is accurate for dragging`() {
        val viewport = TimelineViewport(startSec = 0f, secondsPerScreen = 600f)
        val widthPx = 400f

        // Time 0 should be at x=0
        val xAtStart = viewport.timeToX(0, widthPx)
        assertEquals(0f, xAtStart, 0.5f)

        // Time 300 should be at x=200 (halfway)
        val xAtMiddle = viewport.timeToX(300, widthPx)
        assertEquals(200f, xAtMiddle, 0.5f)
    }
}
