Option Strict On
Option Explicit On

Imports System.Collections.Generic
Imports System.Linq

Namespace Nexamas.UI.Motion

    ''' <summary>
    ''' Official readiness gate for Phase 4. It verifies tokens, deterministic timelines, reduced-motion policy, and integration bindings.
    ''' </summary>
    Friend NotInheritable Class MASMotionReadinessGate

        Private Sub New()
        End Sub

        Friend Shared Function Evaluate() As MASMotionReadinessReport
            Dim findings As New List(Of MASMotionReadinessFinding)()
            Dim tokens As IReadOnlyCollection(Of MASMotionToken) = MASMotionTokenCatalog.GetTokens()

            RequireToken(tokens, MASMotionTokenId.HoverEnter, MASMotionPurpose.HoverFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.PressDown, MASMotionPurpose.PressFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.FloatOpen, MASMotionPurpose.FloatTransition, findings)
            RequireToken(tokens, MASMotionTokenId.FloatClose, MASMotionPurpose.FloatTransition, findings)
            RequireToken(tokens, MASMotionTokenId.SelectionMove, MASMotionPurpose.SelectionMovement, findings)
            RequireToken(tokens, MASMotionTokenId.FocusIn, MASMotionPurpose.FocusTransition, findings)
            RequireToken(tokens, MASMotionTokenId.DisclosureOpen, MASMotionPurpose.DisclosureTransition, findings)
            RequireToken(tokens, MASMotionTokenId.DisclosureClose, MASMotionPurpose.DisclosureTransition, findings)
            RequireToken(tokens, MASMotionTokenId.IndicatorMove, MASMotionPurpose.NavigationIndicator, findings)
            RequireToken(tokens, MASMotionTokenId.RowSelectionMove, MASMotionPurpose.SelectionMovement, findings)
            RequireToken(tokens, MASMotionTokenId.ValueScrub, MASMotionPurpose.ValueFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.ValidationReveal, MASMotionPurpose.ValidationFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.BadgePulse, MASMotionPurpose.ValueFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.ItemAdd, MASMotionPurpose.ItemMutation, findings)
            RequireToken(tokens, MASMotionTokenId.ItemRemove, MASMotionPurpose.ItemMutation, findings)
            RequireToken(tokens, MASMotionTokenId.PanelReveal, MASMotionPurpose.PanelTransition, findings)
            RequireToken(tokens, MASMotionTokenId.DragFeedback, MASMotionPurpose.DragFeedback, findings)
            RequireToken(tokens, MASMotionTokenId.DropFeedback, MASMotionPurpose.DragFeedback, findings)

            Dim timeline As MASMotionTimeline = MASMotionSystem.CreateTimeline(MASMotionTokenId.HoverEnter, 0.0F, 1.0F)
            Dim frame As MASMotionFrame = timeline.Advance(60)
            If frame.LinearProgress <= 0.0F OrElse frame.LinearProgress >= 1.0F Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.InvalidTimelineProgress", "Timeline did not produce an in-flight frame for a partial tick."))
            End If

            Dim originalReducedMotion As MASMotionReducedMotionMode = MASMotionSystem.ReducedMotionMode
            Dim reducedPlan As MASMotionTransitionPlan = Nothing
            Try
                MASMotionSystem.ReducedMotionMode = MASMotionReducedMotionMode.DisableNonEssentialMotion
                reducedPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.HoverEnter, 0.0F, 1.0F)
            Finally
                MASMotionSystem.ReducedMotionMode = originalReducedMotion
            End Try

            If reducedPlan Is Nothing OrElse reducedPlan.ShouldAnimate Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.ReducedMotionNotApplied", "Non-essential hover motion must collapse when reduced motion disables non-essential motion."))
            End If

            RequireReducedMotionPlan(MASMotionReducedMotionMode.DisableNonEssentialMotion, MASMotionTokenId.RowSelectionMove, shouldAnimate:=False, maximumDurationMs:=0, findings:=findings)
            RequireReducedMotionPlan(MASMotionReducedMotionMode.DisableNonEssentialMotion, MASMotionTokenId.BadgePulse, shouldAnimate:=False, maximumDurationMs:=0, findings:=findings)
            RequireReducedMotionPlan(MASMotionReducedMotionMode.DisableNonEssentialMotion, MASMotionTokenId.PanelReveal, shouldAnimate:=True, maximumDurationMs:=80, findings:=findings)
            RequireReducedMotionPlan(MASMotionReducedMotionMode.DisableNonEssentialMotion, MASMotionTokenId.DragFeedback, shouldAnimate:=True, maximumDurationMs:=80, findings:=findings)
            RequireReducedMotionPlan(MASMotionReducedMotionMode.PreferReducedMotion, MASMotionTokenId.RowSelectionMove, shouldAnimate:=True, maximumDurationMs:=60, findings:=findings)
            RequireReducedMotionPlan(MASMotionReducedMotionMode.PreferReducedMotion, MASMotionTokenId.PanelReveal, shouldAnimate:=True, maximumDurationMs:=120, findings:=findings)

            Dim integration As MASMotionIntegrationReport = MASMotionIntegrationGate.EvaluateCurrentConsumers()
            For Each finding As MASMotionIntegrationFinding In integration.Findings
                findings.Add(New MASMotionReadinessFinding(finding.Code, finding.Message))
            Next

            Return New MASMotionReadinessReport(
                tokens.Count,
                integration.ConsumerCount,
                findings,
                runtimeProvenConsumerCount:=integration.RuntimeProvenConsumerCount,
                declaredOnlyConsumerCount:=integration.DeclaredOnlyConsumerCount,
                timelineRunnerConsumerCount:=integration.TimelineRunnerConsumerCount,
                frameConsumerCount:=integration.FrameConsumerCount,
                floatRuntimeInheritedConsumerCount:=integration.FloatRuntimeInheritedConsumerCount)
        End Function

        Private Shared Sub RequireReducedMotionPlan(mode As MASMotionReducedMotionMode,
                                                           tokenId As MASMotionTokenId,
                                                           shouldAnimate As Boolean,
                                                           maximumDurationMs As Integer,
                                                           findings As List(Of MASMotionReadinessFinding))
            Dim originalMode As MASMotionReducedMotionMode = MASMotionSystem.ReducedMotionMode
            Dim plan As MASMotionTransitionPlan = Nothing
            Try
                MASMotionSystem.ReducedMotionMode = mode
                plan = MASMotionSystem.CreateTransitionPlan(tokenId, 0.0F, 1.0F)
            Finally
                MASMotionSystem.ReducedMotionMode = originalMode
            End Try

            If plan Is Nothing Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.MissingReducedMotionPlan", tokenId.ToString() & " did not produce a reduced-motion transition plan for " & mode.ToString() & "."))
                Return
            End If

            If plan.ShouldAnimate <> shouldAnimate Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.ReducedMotionPolicyMismatch", tokenId.ToString() & " ShouldAnimate=" & plan.ShouldAnimate.ToString() & " under " & mode.ToString() & "; expected " & shouldAnimate.ToString() & "."))
            End If

            If maximumDurationMs <= 0 Then
                If plan.EffectiveDurationMs <> 0 Then
                    findings.Add(New MASMotionReadinessFinding("MASMotion.ReducedMotionDurationMismatch", tokenId.ToString() & " must collapse to duration 0 under " & mode.ToString() & "."))
                End If
            ElseIf plan.EffectiveDurationMs <= 0 OrElse plan.EffectiveDurationMs > maximumDurationMs Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.ReducedMotionDurationMismatch", tokenId.ToString() & " effective duration under " & mode.ToString() & " must be between 1 and " & maximumDurationMs.ToString(Global.System.Globalization.CultureInfo.InvariantCulture) & " ms."))
            End If
        End Sub

        Private Shared Sub RequireToken(tokens As IReadOnlyCollection(Of MASMotionToken),
                                        id As MASMotionTokenId,
                                        purpose As MASMotionPurpose,
                                        findings As List(Of MASMotionReadinessFinding))
            Dim token As MASMotionToken = tokens.FirstOrDefault(Function(candidate) candidate IsNot Nothing AndAlso candidate.Id = id)
            If token Is Nothing Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.MissingToken", "Missing motion token " & id.ToString()))
                Return
            End If

            If token.Purpose <> purpose Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.TokenPurposeMismatch", id.ToString() & " does not have purpose " & purpose.ToString()))
            End If

            If id <> MASMotionTokenId.None AndAlso token.DurationMs <= 0 Then
                findings.Add(New MASMotionReadinessFinding("MASMotion.InvalidDuration", id.ToString() & " has no duration."))
            End If
        End Sub

    End Class

End Namespace
