Option Strict On
Option Explicit On

Imports System

Namespace Nexamas.UI.Motion

    ''' <summary>
    ''' Official Friend-owned gateway for Nexamas UI motion tokens, easing evaluation, reduced-motion policy, and deterministic timelines after final public API lockdown.
    ''' </summary>
    Friend NotInheritable Class MASMotionSystem

        Private Shared ReadOnly s_syncRoot As New Object()
        Private Shared _reducedMotionMode As MASMotionReducedMotionMode = MASMotionReducedMotionMode.FullMotion

        Private Sub New()
        End Sub

        Friend Shared Property ReducedMotionMode As MASMotionReducedMotionMode
            Get
                SyncLock s_syncRoot
                    Return _reducedMotionMode
                End SyncLock
            End Get
            Set(value As MASMotionReducedMotionMode)
                SyncLock s_syncRoot
                    _reducedMotionMode = value
                End SyncLock
            End Set
        End Property

        Friend Shared Function GetToken(id As MASMotionTokenId) As MASMotionToken
            Return MASMotionTokenCatalog.GetToken(id)
        End Function

        Friend Shared Function CreateTransitionPlan(tokenId As MASMotionTokenId,
                                                    fromValue As Single,
                                                    toValue As Single) As MASMotionTransitionPlan
            Dim token As MASMotionToken = GetToken(tokenId)
            Return New MASMotionTransitionPlan(token, fromValue, toValue, ResolveEffectiveDurationMs(token))
        End Function

        Friend Shared Function CreateTransitionPlan(tokenId As MASMotionTokenId,
                                                    fromValue As Single,
                                                    toValue As Single,
                                                    durationMs As Integer,
                                                    easing As MASMotionEasing) As MASMotionTransitionPlan
            Dim token As MASMotionToken = GetToken(tokenId).WithDuration(durationMs).WithEasing(easing)
            Return New MASMotionTransitionPlan(token, fromValue, toValue, ResolveEffectiveDurationMs(token))
        End Function

        Friend Shared Function CreateDistanceScaledTransitionPlan(tokenId As MASMotionTokenId,
                                                                 fromValue As Single,
                                                                 toValue As Single,
                                                                 fullDistance As Single) As MASMotionTransitionPlan
            Dim token As MASMotionToken = GetToken(tokenId)
            Dim effectiveDurationMs As Integer = ResolveEffectiveDurationMs(token)

            Dim totalDistance As Single = Math.Abs(fullDistance)
            Dim requestedDistance As Single = Math.Abs(toValue - fromValue)

            If effectiveDurationMs > 0 AndAlso totalDistance > 0.0001F AndAlso requestedDistance > 0.0001F Then
                Dim ratio As Single = requestedDistance / totalDistance
                If ratio < 1.0F Then
                    effectiveDurationMs = Math.Max(1, CInt(Math.Round(CDbl(effectiveDurationMs) * CDbl(ratio))))
                End If
            End If

            Return New MASMotionTransitionPlan(token, fromValue, toValue, effectiveDurationMs)
        End Function

        Friend Shared Function CreateTimeline(plan As MASMotionTransitionPlan) As MASMotionTimeline
            Return New MASMotionTimeline(plan)
        End Function

        Friend Shared Function CreateTimeline(tokenId As MASMotionTokenId,
                                              fromValue As Single,
                                              toValue As Single) As MASMotionTimeline
            Return CreateTimeline(CreateTransitionPlan(tokenId, fromValue, toValue))
        End Function

        Friend Shared Function Evaluate(easing As MASMotionEasing, progress As Single) As Single
            Return MASMotionEasingEvaluator.Evaluate(easing, progress)
        End Function

        Friend Shared Function ClampProgress(progress As Single) As Single
            Return MASMotionEasingEvaluator.Clamp01(progress)
        End Function

        Friend Shared Function NormalizeFrameDeltaMs(deltaMs As Integer) As Integer
            Return MASMotionClock.NormalizeDeltaMs(deltaMs)
        End Function

        Friend Shared Function ResolveFrameProgress(tokenId As MASMotionTokenId,
                                                    deltaMs As Integer) As Single
            Dim plan As MASMotionTransitionPlan = CreateTransitionPlan(tokenId, 0.0F, 1.0F)
            If plan.EffectiveDurationMs <= 0 Then Return 1.0F

            Dim raw As Single = CSng(Math.Max(1, NormalizeFrameDeltaMs(deltaMs))) / CSng(plan.EffectiveDurationMs)
            Return Evaluate(plan.Token.Easing, raw)
        End Function

        Friend Shared Function AdvanceScalarProgress(tokenId As MASMotionTokenId,
                                                     ByRef elapsedMs As Integer,
                                                     deltaMs As Integer) As Single
            Dim plan As MASMotionTransitionPlan = CreateTransitionPlan(tokenId, 0.0F, 1.0F)
            If plan.EffectiveDurationMs <= 0 Then
                elapsedMs = 0
                Return 1.0F
            End If

            elapsedMs += NormalizeFrameDeltaMs(deltaMs)
            If elapsedMs > plan.EffectiveDurationMs Then elapsedMs = plan.EffectiveDurationMs

            Dim linear As Single = CSng(elapsedMs) / CSng(plan.EffectiveDurationMs)
            Return Evaluate(plan.Token.Easing, linear)
        End Function

        Friend Shared Sub RegisterFrameConsumer(owner As Object,
                                                consumerName As String,
                                                requestFrame As Action,
                                                advanceFrame As Func(Of Integer, Boolean),
                                                Optional motionFrameClock As MASMotionFrameClock = Nothing)
            Dim clock As MASMotionFrameClock = If(motionFrameClock, ResolveFrameClockForOwner(owner))
            If clock Is Nothing Then
                ReportDroppedFrameConsumer(owner, consumerName)
                Return
            End If

            clock.Register(owner, consumerName, requestFrame, advanceFrame)
        End Sub

        Friend Shared Sub UnregisterFrameConsumer(owner As Object)
            MASMotionFrameClock.UnregisterFromAll(owner)
        End Sub

        Friend Shared Function HasActiveFrameConsumers() As Boolean
            Return MASMotionFrameClock.AggregateHasActiveConsumers()
        End Function

        Friend Shared Function ActiveFrameConsumerCount() As Integer
            Return MASMotionFrameClock.AggregateActiveConsumerCount()
        End Function

        Friend Shared Function AdvanceScheduledFrameConsumers() As Boolean
            Return MASMotionFrameClock.AdvanceAllKnownClocksForDiagnostics()
        End Function

        Friend Shared Function AdvanceScheduledFrameConsumers(motionFrameClock As MASMotionFrameClock) As Boolean
            If motionFrameClock Is Nothing Then Return False
            Return motionFrameClock.AdvanceScheduledAnimations()
        End Function

        Friend Shared Function CreateTimelineRunner(owner As Object,
                                                    consumerName As String,
                                                    plan As MASMotionTransitionPlan,
                                                    requestFrame As Action,
                                                    applyFrame As Action(Of MASMotionFrame),
                                                    Optional completed As Action(Of MASMotionFrame) = Nothing,
                                                    Optional motionFrameClock As MASMotionFrameClock = Nothing) As MASMotionTimelineRunner
            Return New MASMotionTimelineRunner(owner, consumerName, plan, requestFrame, applyFrame, completed, motionFrameClock)
        End Function

        Friend Shared Function ResolveFrameClockForOwner(owner As Object) As MASMotionFrameClock
            If owner Is Nothing Then Return Nothing

            Dim controlOwner As Nexamas.UI.Controls.MASControlBase = TryCast(owner, Nexamas.UI.Controls.MASControlBase)
            If controlOwner IsNot Nothing Then
                Return controlOwner.TryGetMotionFrameClockInternal()
            End If

            Return Nothing
        End Function

        Private Shared Sub ReportDroppedFrameConsumer(owner As Object,
                                                      consumerName As String)
            Dim ownerName As String = If(owner Is Nothing, "<null>", owner.GetType().FullName)
            Dim safeConsumerName As String = If(consumerName, String.Empty)
            Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(
                New InvalidOperationException("Motion frame consumer '" & safeConsumerName & "' for owner '" & ownerName & "' was not registered because no host-scoped MASMotionFrameClock was available."),
                "MASMotionSystem.MissingHostFrameClock")
        End Sub

        ''' <summary>
        ''' Evaluates the Motion foundation through the official Motion gateway without exposing the concrete readiness gate.
        ''' </summary>
        Friend Shared Function EvaluateReadiness() As MASMotionReadinessReport
            Return MASMotionReadinessGate.Evaluate()
        End Function

        Friend Shared Function ResolveEffectiveDurationMs(token As MASMotionToken) As Integer
            If token Is Nothing Then Return 0

            Dim mode As MASMotionReducedMotionMode
            SyncLock s_syncRoot
                mode = _reducedMotionMode
            End SyncLock

            Select Case mode
                Case MASMotionReducedMotionMode.DisableNonEssentialMotion
                    If Not token.IsEssential Then Return 0
                    Return Math.Min(token.DurationMs, 80)

                Case MASMotionReducedMotionMode.PreferReducedMotion
                    If token.IsEssential Then Return Math.Min(token.DurationMs, 120)
                    Return Math.Min(token.DurationMs, 60)

                Case Else
                    Return token.DurationMs
            End Select
        End Function

    End Class

End Namespace
