Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Virtualization

    ''' <summary>
    ''' Executed accessibility virtual-range proof for current shared-controller consumers. The gate verifies off-screen
    ''' navigation by scroll-to-index and logical position metadata while preserving bounded realized snapshots. It deliberately
    ''' does not create UI Automation providers and does not move accessibility ownership into the core virtualization engine.
    ''' </summary>
    Friend NotInheritable Class MASVirtualizationAccessibleRangeGate

        Friend Const MissingBehaviorProofCode As String = "MASVirtualizationAccessibleRange.MissingBehaviorProof"
        Friend Const StructuralContractCode As String = "MASVirtualizationAccessibleRange.StructuralContract"
        Friend Const LogicalMetadataCode As String = "MASVirtualizationAccessibleRange.LogicalMetadata"
        Friend Const OffscreenBridgeCode As String = "MASVirtualizationAccessibleRange.OffscreenScrollBridge"
        Friend Const BoundedSnapshotCode As String = "MASVirtualizationAccessibleRange.BoundedSnapshot"
        Friend Const ComposedRangeCode As String = "MASVirtualizationAccessibleRange.ComposedRange"

        Private Const RequiredLogicalItemCount As Integer = 100000
        Private Const LinearEstimatedExtentPx As Single = 32.0F
        Private Const LinearViewportExtentPx As Single = 320.0F
        Private Const LinearOverscanItemCount As Integer = 2
        Private Const TileViewportWidthPx As Single = 500.0F
        Private Const TileViewportHeightPx As Single = 480.0F
        Private Const TileRowExtentPx As Single = 96.0F
        Private Const TileColumnCount As Integer = 5
        Private Const TileOverscanRowCount As Integer = 2

        Private Sub New()
        End Sub

        Friend Shared Function EvaluateCurrentConsumers(Optional consumerBehaviorReport As MASVirtualizationConsumerBehaviorReport = Nothing) As MASVirtualizationAccessibleRangeReport
            Dim catalogBindings As IReadOnlyList(Of MASVirtualizationConsumerBinding) = MASVirtualizationConsumerCatalog.GetCurrentConsumers()
            Dim behavior As MASVirtualizationConsumerBehaviorReport = If(consumerBehaviorReport, MASVirtualizationConsumerBehaviorGate.EvaluateCurrentConsumers())
            Dim accessibleBindings As New List(Of MASVirtualizationAccessibleRangeBinding)()
            Dim findings As New List(Of MASVirtualizationReadinessFinding)()
            Dim provedConsumerIds As New List(Of String)()

            For Each binding As MASVirtualizationConsumerBinding In catalogBindings
                If binding Is Nothing OrElse Not binding.IsActiveOrComposedSharedControllerConsumer Then Continue For

                Dim accessibleBinding As MASVirtualizationAccessibleRangeBinding = CreateAccessibleRangeBinding(binding)
                accessibleBindings.Add(accessibleBinding)

                If Not accessibleBinding.IsStructurallyValid Then
                    findings.Add(New MASVirtualizationReadinessFinding(StructuralContractCode, binding.Id & " accessibility virtual-range binding is structurally invalid.", MASVirtualizationFindingSeverity.Error))
                    Continue For
                End If

                If behavior Is Nothing OrElse Not behavior.ProvedConsumerIds.Contains(binding.Id) Then
                    findings.Add(New MASVirtualizationReadinessFinding(MissingBehaviorProofCode, binding.Id & " accessibility virtual-range proof requires completed virtualization consumer behavior proof first.", MASVirtualizationFindingSeverity.Error))
                    Continue For
                End If

                Dim passed As Boolean
                If accessibleBinding.IsComposed Then
                    passed = RunComposedAccessibleRangeProof(accessibleBinding, behavior, findings)
                ElseIf accessibleBinding.IsTileGrid Then
                    passed = RunTileGridAccessibleRangeProof(accessibleBinding, findings)
                Else
                    passed = RunLinearAccessibleRangeProof(accessibleBinding, binding.ExtentStrategy, findings)
                End If

                If passed AndAlso Not provedConsumerIds.Contains(binding.Id) Then
                    provedConsumerIds.Add(binding.Id)
                End If
            Next

            Return New MASVirtualizationAccessibleRangeReport(accessibleBindings, findings, provedConsumerIds)
        End Function

        Private Shared Function CreateAccessibleRangeBinding(binding As MASVirtualizationConsumerBinding) As MASVirtualizationAccessibleRangeBinding
            If binding Is Nothing Then
                Return New MASVirtualizationAccessibleRangeBinding(String.Empty, String.Empty, 0, 0, 0.0F, 0, False, False, False)
            End If

            If binding.IsComposedSharedControllerConsumer Then
                Return New MASVirtualizationAccessibleRangeBinding(
                    binding.Id,
                    binding.DisplayName,
                    RequiredLogicalItemCount,
                    RequiredLogicalItemCount - 1,
                    LinearViewportExtentPx,
                    ResolveLinearRealizationBudget(),
                    usesScrollToIndexBridge:=True,
                    exposesLogicalPositionMetadata:=True,
                    keepsSnapshotBoundedToRealizedRange:=True,
                    isComposed:=True,
                    composedThroughConsumerIds:=binding.ComposedThroughConsumerIds)
            End If

            Dim isTileGrid As Boolean = binding.ExtentStrategy = MASVirtualizationExtentStrategy.TileGridMeasuredCells
            Dim viewportExtent As Single = If(isTileGrid, TileViewportHeightPx, LinearViewportExtentPx)
            Dim maxRealized As Integer = If(isTileGrid, ResolveTileRowRealizationBudget(), ResolveLinearRealizationBudget())
            Dim targetIndex As Integer = If(isTileGrid, RequiredLogicalItemCount - TileColumnCount, RequiredLogicalItemCount - 1)

            Return New MASVirtualizationAccessibleRangeBinding(
                binding.Id,
                binding.DisplayName,
                RequiredLogicalItemCount,
                targetIndex,
                viewportExtent,
                maxRealized,
                usesScrollToIndexBridge:=True,
                exposesLogicalPositionMetadata:=True,
                keepsSnapshotBoundedToRealizedRange:=True,
                isTileGrid:=isTileGrid,
                columnCount:=If(isTileGrid, TileColumnCount, 1))
        End Function

        Private Shared Function RunLinearAccessibleRangeProof(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                              extentStrategy As MASVirtualizationExtentStrategy,
                                                              findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            Dim passed As Boolean = True
            Dim coordinator As New MASLinearVirtualizationConsumerCoordinator()
            coordinator.Configure(
                itemCount:=accessibleBinding.LogicalItemCount,
                estimatedItemExtentPx:=LinearEstimatedExtentPx,
                overscanItemCount:=LinearOverscanItemCount,
                axis:=MASVirtualizationAxis.Vertical,
                extentStrategy:=extentStrategy)

            Dim firstPlan As MASVirtualizationRealizationPlan = coordinator.UpdateViewport(0.0F, accessibleBinding.ViewportExtentPx)
            passed = AssertBoundedSnapshot(accessibleBinding, "linear-initial", firstPlan, findings) AndAlso passed
            If firstPlan IsNot Nothing AndAlso firstPlan.Range IsNot Nothing AndAlso firstPlan.Range.Contains(accessibleBinding.OffscreenTargetIndex) Then
                findings.Add(New MASVirtualizationReadinessFinding(OffscreenBridgeCode, accessibleBinding.ConsumerId & " accessibility proof expected the target item to start outside the first realized range.", MASVirtualizationFindingSeverity.Error))
                passed = False
            End If

            Dim scrollRequest As MASVirtualizationScrollRequest = coordinator.CreateScrollToIndexRequest(
                accessibleBinding.OffscreenTargetIndex,
                0.0F,
                accessibleBinding.ViewportExtentPx,
                MASVirtualizationAlignment.Start)

            passed = AssertScrollBridge(accessibleBinding, scrollRequest, accessibleBinding.OffscreenTargetIndex, findings) AndAlso passed

            Dim targetOffset As Single = If(scrollRequest Is Nothing, 0.0F, scrollRequest.TargetOffsetPx)
            Dim targetPlan As MASVirtualizationRealizationPlan = coordinator.UpdateViewport(targetOffset, accessibleBinding.ViewportExtentPx)
            passed = AssertBoundedSnapshot(accessibleBinding, "linear-target", targetPlan, findings) AndAlso passed
            passed = AssertRangeContainsLogicalTarget(accessibleBinding, targetPlan, accessibleBinding.OffscreenTargetIndex, findings) AndAlso passed
            passed = AssertLogicalPositionMetadata(accessibleBinding, accessibleBinding.OffscreenTargetIndex, accessibleBinding.LogicalItemCount, findings) AndAlso passed

            Dim resolvedIndex As Integer = coordinator.ResolveIndexAtPointY(8.0F, 0.0F, accessibleBinding.ViewportExtentPx, targetOffset)
            If resolvedIndex < 0 OrElse targetPlan Is Nothing OrElse targetPlan.Range Is Nothing OrElse Not targetPlan.Range.Contains(resolvedIndex) Then
                findings.Add(New MASVirtualizationReadinessFinding(LogicalMetadataCode, accessibleBinding.ConsumerId & " accessibility proof rejected point-to-logical-index metadata inside the target realized range.", MASVirtualizationFindingSeverity.Error))
                passed = False
            End If

            Return passed
        End Function

        Private Shared Function RunTileGridAccessibleRangeProof(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                                findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            Dim passed As Boolean = True
            Dim coordinator As New MASTileGridVirtualizationConsumerCoordinator()
            Dim viewport As New SKRect(0.0F, 0.0F, TileViewportWidthPx, TileViewportHeightPx)
            Dim ctx As MASThemeContext = CreateThemeContext()
            Dim targetRowIndex As Integer = accessibleBinding.OffscreenTargetIndex \ accessibleBinding.ColumnCount
            Dim logicalRowCount As Integer = MASTileGridVirtualizationConsumerCoordinator.ResolveRowCount(accessibleBinding.LogicalItemCount, accessibleBinding.ColumnCount)

            Dim firstPlan As MASVirtualizationRealizationPlan = coordinator.PrepareGridPlan(ctx, accessibleBinding.LogicalItemCount, accessibleBinding.ColumnCount, TileRowExtentPx, viewport, 0.0F, 1, 1)
            passed = AssertBoundedSnapshot(accessibleBinding, "tile-initial", firstPlan, findings) AndAlso passed
            If firstPlan IsNot Nothing AndAlso firstPlan.Range IsNot Nothing AndAlso firstPlan.Range.Contains(targetRowIndex) Then
                findings.Add(New MASVirtualizationReadinessFinding(OffscreenBridgeCode, accessibleBinding.ConsumerId & " accessibility proof expected the target tile row to start outside the first realized range.", MASVirtualizationFindingSeverity.Error))
                passed = False
            End If

            Dim scrollRequest As MASVirtualizationScrollRequest = coordinator.CreateScrollToItemRequest(
                accessibleBinding.OffscreenTargetIndex,
                accessibleBinding.ColumnCount,
                0.0F,
                accessibleBinding.ViewportExtentPx,
                MASVirtualizationAlignment.Start)

            passed = AssertScrollBridge(accessibleBinding, scrollRequest, targetRowIndex, findings) AndAlso passed

            Dim targetOffset As Single = If(scrollRequest Is Nothing, 0.0F, scrollRequest.TargetOffsetPx)
            Dim targetPlan As MASVirtualizationRealizationPlan = coordinator.PrepareGridPlan(ctx, accessibleBinding.LogicalItemCount, accessibleBinding.ColumnCount, TileRowExtentPx, viewport, targetOffset, 1, 1)
            passed = AssertBoundedSnapshot(accessibleBinding, "tile-target", targetPlan, findings) AndAlso passed
            passed = AssertRangeContainsLogicalTarget(accessibleBinding, targetPlan, targetRowIndex, findings) AndAlso passed
            passed = AssertLogicalPositionMetadata(accessibleBinding, accessibleBinding.OffscreenTargetIndex, accessibleBinding.LogicalItemCount, findings) AndAlso passed
            passed = AssertLogicalPositionMetadata(accessibleBinding, targetRowIndex, logicalRowCount, findings) AndAlso passed

            Return passed
        End Function

        Private Shared Function RunComposedAccessibleRangeProof(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                                behavior As MASVirtualizationConsumerBehaviorReport,
                                                                findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            For Each dependencyId As String In accessibleBinding.ComposedThroughConsumerIds
                If behavior Is Nothing OrElse Not behavior.ProvedConsumerIds.Contains(dependencyId) Then
                    findings.Add(New MASVirtualizationReadinessFinding(ComposedRangeCode, accessibleBinding.ConsumerId & " accessibility virtual-range proof requires child proof: " & dependencyId, MASVirtualizationFindingSeverity.Error))
                    Return False
                End If
            Next

            Return accessibleBinding.UsesScrollToIndexBridge AndAlso
                   accessibleBinding.ExposesLogicalPositionMetadata AndAlso
                   accessibleBinding.KeepsSnapshotBoundedToRealizedRange
        End Function

        Private Shared Function AssertScrollBridge(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                   scrollRequest As MASVirtualizationScrollRequest,
                                                   expectedRequestIndex As Integer,
                                                   findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            If scrollRequest Is Nothing OrElse
               scrollRequest.Index <> expectedRequestIndex OrElse
               scrollRequest.TargetOffsetPx <= 0.0F OrElse
               scrollRequest.TotalExtentPx <= accessibleBinding.ViewportExtentPx Then
                findings.Add(New MASVirtualizationReadinessFinding(OffscreenBridgeCode, accessibleBinding.ConsumerId & " accessibility proof rejected scroll-to-index bridge for an off-screen logical item.", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            Return True
        End Function

        Private Shared Function AssertBoundedSnapshot(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                      scenario As String,
                                                      plan As MASVirtualizationRealizationPlan,
                                                      findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            If plan Is Nothing OrElse plan.Range Is Nothing OrElse plan.IsEmpty Then
                findings.Add(New MASVirtualizationReadinessFinding(BoundedSnapshotCode, accessibleBinding.ConsumerId & " accessibility proof rejected empty plan for " & scenario & ".", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            If plan.RealizedCount <= 0 OrElse plan.RealizedCount > accessibleBinding.MaxRealizedRequestCount Then
                findings.Add(New MASVirtualizationReadinessFinding(BoundedSnapshotCode, accessibleBinding.ConsumerId & " accessibility proof rejected unbounded realized snapshot for " & scenario & ".", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            If plan.RealizedCount >= accessibleBinding.LogicalItemCount Then
                findings.Add(New MASVirtualizationReadinessFinding(BoundedSnapshotCode, accessibleBinding.ConsumerId & " accessibility proof rejected snapshot because it collapsed virtual range semantics to all logical items.", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            Return True
        End Function

        Private Shared Function AssertRangeContainsLogicalTarget(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                                 plan As MASVirtualizationRealizationPlan,
                                                                 targetIndex As Integer,
                                                                 findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            If plan Is Nothing OrElse plan.Range Is Nothing OrElse Not plan.Range.Contains(targetIndex) Then
                findings.Add(New MASVirtualizationReadinessFinding(OffscreenBridgeCode, accessibleBinding.ConsumerId & " accessibility proof rejected realized range after off-screen navigation because it does not contain the target logical index.", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            Return True
        End Function

        Private Shared Function AssertLogicalPositionMetadata(accessibleBinding As MASVirtualizationAccessibleRangeBinding,
                                                              index As Integer,
                                                              count As Integer,
                                                              findings As IList(Of MASVirtualizationReadinessFinding)) As Boolean
            If count <> accessibleBinding.LogicalItemCount AndAlso Not accessibleBinding.IsTileGrid Then
                findings.Add(New MASVirtualizationReadinessFinding(LogicalMetadataCode, accessibleBinding.ConsumerId & " accessibility proof rejected logical count metadata.", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            If count <= 0 OrElse index < 0 OrElse index >= count Then
                findings.Add(New MASVirtualizationReadinessFinding(LogicalMetadataCode, accessibleBinding.ConsumerId & " accessibility proof rejected logical position metadata outside the virtual range.", MASVirtualizationFindingSeverity.Error))
                Return False
            End If

            Return True
        End Function

        Private Shared Function CreateThemeContext() As MASThemeContext
            Return New MASThemeContext(1.0F, New MASTypography(1.0F), ThemeManager.CurrentCore, 1)
        End Function

        Private Shared Function ResolveLinearRealizationBudget() As Integer
            Return CInt(Math.Ceiling(CDbl(LinearViewportExtentPx / LinearEstimatedExtentPx))) + (LinearOverscanItemCount * 2) + 2
        End Function

        Private Shared Function ResolveTileRowRealizationBudget() As Integer
            Return CInt(Math.Ceiling(CDbl(TileViewportHeightPx / TileRowExtentPx))) + (TileOverscanRowCount * 2) + 2
        End Function
    End Class

End Namespace
