package com.tt.honeyDue.ui.animation

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.math.abs

/**
 * P5 Stream Q — unit tests for [TaskAnimations] spec objects.
 *
 * Pure-Kotlin tests that assert iOS-parity timings and deterministic
 * keyframe output. The specs in [TaskAnimations] expose a [sample] method
 * that returns a frame value at a fixed time offset in milliseconds — no
 * Compose runtime is involved, so the specs are fully testable in
 * commonTest.
 *
 * iOS reference:
 * `iosApp/iosApp/Profile/AnimationTesting/TaskAnimations.swift`
 * — the implode/firework/starburst/ripple checkmark phases all use
 *   `.spring(response: 0.4, dampingFraction: 0.6)` or similar, preceded
 *   by ease-in shrinks of 0.25-0.3s and trailing 1.5s holds + 0.3-0.4s
 *   fade-outs.
 */
class TaskAnimationsTest {

    private val tol = 0.001f

    // ---- Duration assertions (matches iOS) ----

    @Test
    fun completionCheckmark_springDuration_matchesIos() {
        // iOS: .spring(response: 0.4, dampingFraction: 0.6)
        // "response" in SwiftUI is roughly the period in seconds (400ms).
        val spec = TaskAnimations.completionCheckmark
        assertEquals(400, spec.springResponseMillis, "spring response = 400ms")
        assertTrue(abs(spec.dampingRatio - 0.6f) < tol, "dampingFraction = 0.6")
    }

    @Test
    fun cardEnter_duration_matchesIos() {
        // iOS uses ~350ms spring for the "entering" phase (300ms delay +
        // 200ms reset window after the move).
        val spec = TaskAnimations.cardEnter
        assertEquals(350, spec.durationMillis, "cardEnter = 350ms")
    }

    @Test
    fun cardDismiss_duration_matchesIos() {
        // iOS: easeIn(duration: 0.3) for the shrink-out phase on the card.
        val spec = TaskAnimations.cardDismiss
        assertEquals(300, spec.durationMillis, "cardDismiss = 300ms")
    }

    @Test
    fun priorityPulse_period_matchesIos() {
        // iOS starburst pulse ring uses easeOut(duration: 0.6) cycled;
        // we encode it as a 1200ms reversing pulse so urgent indicators
        // breathe at ~50bpm.
        val spec = TaskAnimations.priorityPulse
        assertEquals(1200, spec.periodMillis, "priorityPulse = 1200ms per cycle")
        assertTrue(spec.reverses, "pulse must reverse (breathing effect)")
    }

    @Test
    fun honeycombLoop_period_matchesIos() {
        // iOS doesn't ship a dedicated honeycomb blob loop, so we match
        // the WarmGradientBackground blob rotation cadence (~8s).
        val spec = TaskAnimations.honeycombLoop
        assertEquals(8000, spec.periodMillis, "honeycombLoop = 8000ms")
        assertFalse_notReversing(spec.reverses)
    }

    // ---- Easing / curve assertions ----

    @Test
    fun cardDismiss_usesEaseIn() {
        assertEquals(TaskAnimations.Easing.EASE_IN, TaskAnimations.cardDismiss.easing)
    }

    @Test
    fun cardEnter_usesSpring() {
        assertEquals(TaskAnimations.Easing.SPRING, TaskAnimations.cardEnter.easing)
    }

    @Test
    fun priorityPulse_usesEaseInOut() {
        assertEquals(TaskAnimations.Easing.EASE_IN_OUT, TaskAnimations.priorityPulse.easing)
    }

    @Test
    fun honeycombLoop_usesLinear() {
        assertEquals(TaskAnimations.Easing.LINEAR, TaskAnimations.honeycombLoop.easing)
    }

    // ---- Determinism assertions ----

    @Test
    fun cardDismiss_sampleAtStart_isOne() {
        // At t=0 a 1.0→0.0 dismiss should output exactly 1.0.
        val v = TaskAnimations.cardDismiss.sample(timeMillis = 0, from = 1f, to = 0f)
        assertTrue(abs(v - 1f) < tol, "t=0 must equal `from`; was $v")
    }

    @Test
    fun cardDismiss_sampleAtEnd_isZero() {
        val spec = TaskAnimations.cardDismiss
        val v = spec.sample(timeMillis = spec.durationMillis, from = 1f, to = 0f)
        assertTrue(abs(v - 0f) < tol, "t=end must equal `to`; was $v")
    }

    @Test
    fun cardDismiss_isDeterministic() {
        // Same inputs → same frame value every call.
        val a = TaskAnimations.cardDismiss.sample(150, 1f, 0f)
        val b = TaskAnimations.cardDismiss.sample(150, 1f, 0f)
        val c = TaskAnimations.cardDismiss.sample(150, 1f, 0f)
        assertEquals(a, b, "sample must be pure")
        assertEquals(b, c, "sample must be pure")
    }

    @Test
    fun cardDismiss_monotonicallyDecreasingForShrink() {
        // Easing is ease-in on a 1→0 range: output decreases as t grows.
        val spec = TaskAnimations.cardDismiss
        val v0 = spec.sample(0, 1f, 0f)
        val v100 = spec.sample(100, 1f, 0f)
        val v200 = spec.sample(200, 1f, 0f)
        val v300 = spec.sample(300, 1f, 0f)
        assertTrue(v0 >= v100, "t=0 ($v0) should be ≥ t=100 ($v100)")
        assertTrue(v100 >= v200, "t=100 ($v100) should be ≥ t=200 ($v200)")
        assertTrue(v200 >= v300, "t=200 ($v200) should be ≥ t=300 ($v300)")
    }

    @Test
    fun priorityPulse_samplePhase_periodicity() {
        // After one full period the pulse must return to its start value.
        val spec = TaskAnimations.priorityPulse
        val start = spec.sample(0, from = 1f, to = 1.1f)
        val afterCycle = spec.sample(spec.periodMillis * 2, from = 1f, to = 1.1f)
        assertTrue(
            abs(start - afterCycle) < 0.01f,
            "pulse should return to start after full reverse cycle; start=$start end=$afterCycle"
        )
    }

    // ---- Registry / enumeration ----

    @Test
    fun registry_listsAllAnimations() {
        val names = TaskAnimations.all.map { it.name }
        assertTrue("completionCheckmark" in names)
        assertTrue("cardEnter" in names)
        assertTrue("cardDismiss" in names)
        assertTrue("priorityPulse" in names)
        assertTrue("honeycombLoop" in names)
        assertEquals(5, TaskAnimations.all.size, "registry has 5 entries")
    }

    @Test
    fun registry_lookupByName_returnsSameInstance() {
        val byName = TaskAnimations.byName("cardDismiss")
        assertNotNull(byName)
        // byName returns the common interface type; downcast to assert
        // the lookup returns the same OneShot spec we expect.
        val spec = byName as TaskAnimations.AnimationSpecValues
        assertEquals(TaskAnimations.cardDismiss.durationMillis, spec.durationMillis)
    }

    // Kotlin-test doesn't ship an assertFalse helper on this version; wrap
    // the boolean assertion we need as a helper to keep signatures small.
    private fun assertFalse_notReversing(reverses: Boolean) {
        assertTrue(!reverses, "honeycombLoop must not reverse (continuous loop)")
    }
}
