﻿Option Strict On
Option Explicit On

Imports System
Imports SkiaSharp

Namespace Nexamas.UI.Composition

    ''' <summary>
    ''' Phase 2.4B vertical scroll layout adapter for Page/Form recipes.
    ''' It does not render, host controls, virtualize controls, or own Core input routing. It only arranges its content
    ''' with the latest known vertical logical offset and reports scroll metadata in the Arrange result. The host runtime
    ''' applies that metadata to the official MAS Scroll stack after Arrange, preserving the Build/Measure/Arrange/Apply contract.
    ''' Keep off-screen controls attached and clipped so ownership, focus, and scroll-state remain stable;
    ''' this node only emits a scalability warning when a large non-virtualized scroll surface is detected.
    ''' </summary>
    Friend NotInheritable Class MASVerticalScrollLayoutNode

        Implements IMASLayoutNode

        Private Const CompositionScrollOwnerRouteGuidance As String = "Use a Nexamas UI Data/Virtualization owner for large repeated item sets"

        Private ReadOnly _content As IMASLayoutNode
        Private ReadOnly _scrollKey As String
        Private ReadOnly _maxContentWidth As Single
        Private ReadOnly _padding As MASLayoutThickness

        Friend Shared ReadOnly NonVirtualizedScrollScalabilityWarningCode As String = MASCompositionScrollScalabilityPolicy.NonVirtualizedScrollScalabilityWarningCode
        Friend Shared ReadOnly NonVirtualizedScrollScalabilityBlockCode As String = MASCompositionScrollScalabilityPolicy.NonVirtualizedScrollScalabilityBlockCode

        Private _lastMeasuredContentSize As SKSize = SKSize.Empty
        Private _hasMeasuredContent As Boolean

        Friend Sub New(content As IMASLayoutNode,
                       scrollKey As String,
                       Optional maxContentWidth As Single = 0.0F,
                       Optional padding As MASLayoutThickness = Nothing)

            If content Is Nothing Then Throw New ArgumentNullException(NameOf(content))

            _content = content
            _scrollKey = NormalizeKey(scrollKey)
            _maxContentWidth = If(IsFinitePositive(maxContentWidth), maxContentWidth, 0.0F)
            _padding = padding
        End Sub

        Public Function Measure(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutNode.Measure
            If context Is Nothing Then Return SKSize.Empty

            Dim availableW As Single = SafeSize(context.AvailableSize.Width)
            Dim availableH As Single = SafeSize(context.AvailableSize.Height)
            Dim contentW As Single = ResolveContentWidth(availableW)

            Dim contentAvailable As New SKSize(contentW, Single.PositiveInfinity)
            _lastMeasuredContentSize = _content.Measure(New MASLayoutMeasureContext(contentAvailable, context.IntegrationContext, context.Diagnostics))
            _hasMeasuredContent = True

            Return New SKSize(availableW, availableH)
        End Function

        Public Function Arrange(context As MASLayoutArrangeContext) As MASLayoutArrangeResult Implements IMASLayoutNode.Arrange
            Dim result As New MASLayoutArrangeResult()
            If context Is Nothing Then Return result

            Dim viewport As SKRect = context.BoundsLogical
            If viewport.IsEmpty OrElse viewport.Width <= 1.0F OrElse viewport.Height <= 1.0F Then
                AddScrollRequest(result, SKRect.Empty, 0.0F, 0.0F)
                Return result
            End If

            Dim paddingLeft As Single = MASLayoutThickness.SafeValue(_padding.Left)
            Dim paddingTop As Single = MASLayoutThickness.SafeValue(_padding.Top)
            Dim paddingRight As Single = MASLayoutThickness.SafeValue(_padding.Right)
            Dim paddingBottom As Single = MASLayoutThickness.SafeValue(_padding.Bottom)

            If paddingLeft <= 0.0F AndAlso paddingRight <= 0.0F AndAlso paddingTop <= 0.0F AndAlso paddingBottom <= 0.0F Then
                paddingLeft = 24.0F
                paddingTop = 24.0F
                paddingRight = 24.0F
                paddingBottom = 32.0F
            End If

            Dim scrollViewport As New SKRect(
                viewport.Left + paddingLeft,
                viewport.Top + paddingTop,
                viewport.Right - paddingRight,
                viewport.Bottom - paddingBottom)

            If scrollViewport.IsEmpty OrElse scrollViewport.Width <= 1.0F OrElse scrollViewport.Height <= 1.0F Then
                AddScrollRequest(result, SKRect.Empty, 0.0F, 0.0F)
                Return result
            End If

            Dim contentH As Single = SafeSize(_lastMeasuredContentSize.Height)

            If Not _hasMeasuredContent OrElse contentH <= 0.0F Then
                If context.Diagnostics IsNot Nothing Then
                    context.Diagnostics.AddWarning(
                        code:="MASLayout.ScrollArrangeWithoutMeasure",
                        message:="MASVerticalScrollLayoutNode.Arrange was called before a valid Measure result was available.",
                        detail:="Arrange did not call Measure. It used the viewport height as a safe fallback content height."
                    )
                End If

                contentH = scrollViewport.Height
            End If

            Dim hasOverflow As Boolean = contentH > scrollViewport.Height + 0.5F
            Dim contentArea As SKRect = scrollViewport

            If hasOverflow Then
                contentArea.Right = Math.Max(contentArea.Left, contentArea.Right - MASCompositionScrollState.ScrollbarGutterLogical)
            End If

            Dim contentW As Single = ResolveContentWidth(contentArea.Width)

            If contentH < scrollViewport.Height Then contentH = scrollViewport.Height

            AddScrollRequest(result, scrollViewport, contentH, scrollViewport.Height)

            Dim offsetY As Single = context.ResolveScrollOffset(_scrollKey)
            Dim x As Single = contentArea.Left + Math.Max(0.0F, (contentArea.Width - contentW) / 2.0F)
            Dim y As Single = scrollViewport.Top - offsetY

            Dim contentBounds As New SKRect(
                x,
                y,
                x + contentW,
                y + contentH)

            Dim contentResult As MASLayoutArrangeResult =
                _content.Arrange(context.WithBoundsLogical(contentBounds))

            If contentResult IsNot Nothing Then
                Dim contentPlacements As MASLayoutPlacement() = contentResult.Placements
                Dim offscreenPlacementCount As Integer = CountOffscreenPlacements(contentPlacements, scrollViewport)

                If AddScrollScalabilityDiagnosticIfNeeded(context, contentPlacements.Length, offscreenPlacementCount, scrollViewport, contentH) Then
                    ' Do not publish thousands of retained placements after the policy has blocked the layout.
                    ' The host sees diagnostics.HasErrors and fails before ControlsLayer mutation; returning only
                    ' the scroll metadata keeps the arrange result small and prevents accidental downstream use.
                    Return result
                End If

                For Each placement As MASLayoutPlacement In contentPlacements
                    If placement Is Nothing OrElse placement.Control Is Nothing Then Continue For

                    Dim clippedPlacement As MASLayoutPlacement = placement.WithClipLogical(scrollViewport)

                    ' Keep off-screen controls attached and clipped so focus/ownership remain stable across scroll changes.
                    result.Add(clippedPlacement)
                Next
            End If

            Return result
        End Function


        Private Shared Function AddScrollScalabilityDiagnosticIfNeeded(context As MASLayoutArrangeContext,
                                                                      placementCount As Integer,
                                                                      offscreenPlacementCount As Integer,
                                                                      viewport As SKRect,
                                                                      contentHeight As Single) As Boolean
            If context Is Nothing OrElse context.Diagnostics Is Nothing Then Return False

            Dim viewportHeight As Single = SafeSize(viewport.Height)

            If MASCompositionScrollScalabilityPolicy.ShouldBlock(
                placementCount:=placementCount,
                offscreenPlacementCount:=offscreenPlacementCount,
                viewportHeight:=viewportHeight,
                contentHeight:=contentHeight) Then

                context.Diagnostics.AddError(
                    code:=NonVirtualizedScrollScalabilityBlockCode,
                    message:=CompositionScrollOwnerRouteGuidance & "; block before ControlsLayer mutation.",
                    detail:=MASCompositionScrollScalabilityPolicy.BuildBlockDetail(
                        placementCount:=placementCount,
                        offscreenPlacementCount:=offscreenPlacementCount,
                        viewportHeight:=viewportHeight,
                        contentHeight:=contentHeight))
                Return True
            End If

            If Not MASCompositionScrollScalabilityPolicy.ShouldWarn(
                placementCount:=placementCount,
                offscreenPlacementCount:=offscreenPlacementCount,
                viewportHeight:=viewportHeight,
                contentHeight:=contentHeight) Then Return False

            context.Diagnostics.AddWarning(
                code:=NonVirtualizedScrollScalabilityWarningCode,
                message:=CompositionScrollOwnerRouteGuidance,
                detail:=MASCompositionScrollScalabilityPolicy.BuildWarningDetail(
                    placementCount:=placementCount,
                    offscreenPlacementCount:=offscreenPlacementCount,
                    viewportHeight:=viewportHeight,
                    contentHeight:=contentHeight))
            Return False
        End Function

        Private Shared Function CountOffscreenPlacements(placements As MASLayoutPlacement(),
                                                         viewport As SKRect) As Integer
            If placements Is Nothing OrElse placements.Length = 0 Then Return 0

            Dim count As Integer = 0
            For Each placement As MASLayoutPlacement In placements
                If placement Is Nothing OrElse placement.Control Is Nothing Then Continue For
                If Not RectsIntersect(placement.BoundsLogical, viewport) Then count += 1
            Next
            Return count
        End Function


        Private Sub AddScrollRequest(result As MASLayoutArrangeResult,
                                     viewport As SKRect,
                                     contentHeight As Single,
                                     viewportHeight As Single)
            If result Is Nothing Then Return
            result.AddScrollRequest(New MASLayoutScrollRequest(_scrollKey, viewport, contentHeight, viewportHeight))
        End Sub

        Private Function ResolveContentWidth(availableWidth As Single) As Single
            Dim w As Single = SafeSize(availableWidth)
            If w <= 0.0F Then Return 0.0F

            If IsFinitePositive(_maxContentWidth) Then
                Return Math.Min(w, _maxContentWidth)
            End If

            Return w
        End Function

        Private Shared Function NormalizeKey(value As String) As String
            If String.IsNullOrWhiteSpace(value) Then Return "MASPage.Form.Scroll"
            Return value.Trim()
        End Function

        Private Shared Function SafeSize(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) OrElse value < 0.0F Then Return 0.0F
            Return value
        End Function

        Private Shared Function IsFinitePositive(value As Single) As Boolean
            Return Not Single.IsNaN(value) AndAlso Not Single.IsInfinity(value) AndAlso value > 0.0F
        End Function

        Private Shared Function RectsIntersect(a As SKRect, b As SKRect) As Boolean
            If a.Right <= b.Left OrElse b.Right <= a.Left Then Return False
            If a.Bottom <= b.Top OrElse b.Bottom <= a.Top Then Return False
            Return True
        End Function

    End Class

End Namespace
