package com.tt.honeyDue.ui.animation

import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.EaseIn
import androidx.compose.animation.core.EaseInOut
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import kotlin.math.PI

/**
 * P5 Stream Q — Compose port of iOS `TaskAnimations.swift`.
 *
 * The iOS source lives at
 * `iosApp/iosApp/Profile/AnimationTesting/TaskAnimations.swift` and defines
 * a family of SwiftUI modifiers used to celebrate task completion. The
 * Compose port here preserves:
 *
 *  - `completionCheckmark` — scale-and-bounce spring for the checkmark
 *  - `cardEnter`           — spring that slides/scales a card into place
 *  - `cardDismiss`         — ease-in shrink + fade for the "exiting" card
 *  - `priorityPulse`       — infinite reversing pulse for urgent priority
 *  - `honeycombLoop`       — infinite 8-second loop for the warm-gradient
 *                            blob layer
 *
 * Design choice: the specs are plain data classes (no Compose types) so
 * commonTest can assert timing/easing/determinism without a Compose
 * runtime. Call `toFloatSpec()` at composition time to materialize a
 * Compose `AnimationSpec<Float>`.
 */
object TaskAnimations {

    /**
     * Coarse easing labels. Kept as an enum so tests can assert parity
     * without pulling in Compose's Easing interface (which is an
     * identity-based functional type and painful to compare).
     */
    enum class Easing { LINEAR, EASE_IN, EASE_IN_OUT, SPRING }

    /**
     * Common interface exposed by every entry in [all] and accepted by
     * [AnimationTestingScreenState] — so both one-shot specs and loop
     * specs can be listed and played from the dev screen.
     */
    sealed interface TaskAnimationSpec {
        val name: String

        /** Pure interpolation at a fixed time offset. Deterministic. */
        fun sample(timeMillis: Int, from: Float, to: Float): Float

        /** Materialize a Compose `AnimationSpec<Float>` at composition time. */
        fun toFloatSpec(): AnimationSpec<Float>
    }

    /**
     * Value-object spec for a non-repeating animation. Duration is in ms,
     * easing is one of [Easing].
     */
    data class AnimationSpecValues(
        override val name: String,
        val durationMillis: Int,
        val easing: Easing,
        /** For spring-flavored specs, the SwiftUI "response" in ms. */
        val springResponseMillis: Int = durationMillis,
        val dampingRatio: Float = Spring.DampingRatioNoBouncy,
        val stiffness: Float = Spring.StiffnessMedium,
    ) : TaskAnimationSpec {

        override fun sample(timeMillis: Int, from: Float, to: Float): Float {
            if (durationMillis <= 0) return to
            val raw = timeMillis.toFloat() / durationMillis.toFloat()
            val t = raw.coerceIn(0f, 1f)
            val eased = applyEasing(easing, t)
            return from + (to - from) * eased
        }

        override fun toFloatSpec(): AnimationSpec<Float> = when (easing) {
            Easing.SPRING -> spring(
                dampingRatio = dampingRatio,
                stiffness = stiffness,
            )
            Easing.LINEAR -> tween(durationMillis, easing = LinearEasing)
            Easing.EASE_IN -> tween(durationMillis, easing = EaseIn)
            Easing.EASE_IN_OUT -> tween(durationMillis, easing = EaseInOut)
        }
    }

    /**
     * Value-object spec for an *infinite* animation (priority pulse,
     * honeycomb loop). [periodMillis] is one full cycle; [reverses] maps
     * to Compose's `RepeatMode.Reverse` vs `Restart`.
     */
    data class LoopSpecValues(
        override val name: String,
        val periodMillis: Int,
        val easing: Easing,
        val reverses: Boolean,
    ) : TaskAnimationSpec {

        override fun sample(timeMillis: Int, from: Float, to: Float): Float {
            if (periodMillis <= 0) return from
            val t = (timeMillis.toFloat() / periodMillis.toFloat())
            val phase = t - kotlin.math.floor(t)
            val eased = if (reverses) {
                // smooth sine wave: 0 → 1 → 0 every period
                0.5f - 0.5f * kotlin.math.cos(phase * 2f * PI.toFloat())
            } else {
                applyEasing(easing, phase)
            }
            return from + (to - from) * eased
        }

        override fun toFloatSpec(): AnimationSpec<Float> = infiniteRepeatable(
            animation = tween(
                durationMillis = periodMillis,
                easing = when (easing) {
                    Easing.LINEAR -> LinearEasing
                    Easing.EASE_IN -> EaseIn
                    Easing.EASE_IN_OUT -> EaseInOut
                    Easing.SPRING -> EaseInOut
                },
            ),
            repeatMode = if (reverses) RepeatMode.Reverse else RepeatMode.Restart,
        )
    }

    // ------------------------------------------------------------
    //  iOS timings — see TaskAnimations.swift for the source lines.
    // ------------------------------------------------------------

    /**
     * Completion checkmark spring:
     *   iOS:  `.spring(response: 0.4, dampingFraction: 0.6)`
     */
    val completionCheckmark = AnimationSpecValues(
        name = "completionCheckmark",
        durationMillis = 400,
        easing = Easing.SPRING,
        springResponseMillis = 400,
        dampingRatio = 0.6f,
        stiffness = Spring.StiffnessMediumLow,
    )

    /**
     * Card-enter transition: spring arriving after the task moves between
     * columns. Driven by the "entering" phase (iOS timeline: 350ms after
     * exit starts).
     */
    val cardEnter = AnimationSpecValues(
        name = "cardEnter",
        durationMillis = 350,
        easing = Easing.SPRING,
        springResponseMillis = 350,
        dampingRatio = 0.7f,
    )

    /**
     * Card-dismiss transition:
     *   iOS:  `.easeIn(duration: 0.3)` on the card shrink+fade.
     */
    val cardDismiss = AnimationSpecValues(
        name = "cardDismiss",
        durationMillis = 300,
        easing = Easing.EASE_IN,
    )

    /**
     * Urgent-priority pulse — breathes between 1.0x and 1.1x scale.
     * iOS uses `.easeOut(duration: 0.6)` one-shots on the starburst pulse
     * ring; the sustained indicator on the task card uses a 1.2s reversing
     * pulse so the motion reads as "breathing" at ~50bpm.
     */
    val priorityPulse = LoopSpecValues(
        name = "priorityPulse",
        periodMillis = 1200,
        easing = Easing.EASE_IN_OUT,
        reverses = true,
    )

    /**
     * Honeycomb / warm-gradient blob loop. The iOS
     * `WarmGradientBackground` rotates its blob layer on an 8-second
     * cycle; we match that for the loading shimmer.
     */
    val honeycombLoop = LoopSpecValues(
        name = "honeycombLoop",
        periodMillis = 8000,
        easing = Easing.LINEAR,
        reverses = false,
    )

    /** Registry used by [AnimationTestingScreenState] and tests. */
    val all: List<TaskAnimationSpec> = listOf(
        completionCheckmark,
        cardEnter,
        cardDismiss,
        priorityPulse,
        honeycombLoop,
    )

    /** Case-sensitive lookup into [all]. Returns `null` if not present. */
    fun byName(name: String): TaskAnimationSpec? =
        all.firstOrNull { it.name == name }

    private fun applyEasing(easing: Easing, t: Float): Float = when (easing) {
        Easing.LINEAR -> t
        // Standard "ease-in" cubic: f(t) = t^3
        Easing.EASE_IN -> t * t * t
        // "Ease-in-out": smooth cosine from 0→1
        Easing.EASE_IN_OUT -> 0.5f - 0.5f * kotlin.math.cos(PI.toFloat() * t)
        // Spring sampled as a slightly overshooting ease-out-ish curve so
        // it remains deterministic for tests. The real Compose spring is
        // still used at runtime via [toFloatSpec].
        Easing.SPRING -> 1f - (1f - t) * (1f - t)
    }
}
