Option Strict On
Option Explicit On

Imports System.Collections.Generic
Imports Nexamas.UI.Layout
Imports SkiaSharp

Namespace Nexamas.UI.Composition

    ''' <summary>
    ''' Native Nexamas UI root-screen region layout node.
    ''' It consumes semantic MASScreenRegion declarations and arranges them through MASLayout
    ''' without exposing manual coordinates, project-specific window names, or consumer-owned
    ''' measurement formulas.
    ''' </summary>
    ''' <remarks>
    ''' The node is intentionally conservative: edge regions keep intrinsic size, the Content
    ''' region fills the protected remaining area, and side regions are internally budgeted so
    ''' they cannot accidentally consume the whole screen when their content reports a large
    ''' preferred width. This is not a parallel layout system; it is a specialized MASLayout node
    ''' for root application regions.
    ''' </remarks>
    <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Advanced)>
    Friend NotInheritable Class MASScreenRegionLayoutNode
        Inherits MASLayoutNodeBase

        Private ReadOnly _regions As MASScreenRegion()

        Friend Sub New(regions As IEnumerable(Of MASScreenRegion),
                       Optional padding As MASLayoutThickness = Nothing)
            Me.New(CollectRegions(regions), padding)
        End Sub

        Private Sub New(regions As List(Of MASScreenRegion),
                        padding As MASLayoutThickness)
            MyBase.New(ToLayoutItems(regions), 0.0F, padding, MASLayoutAlignment.Stretch)
            _regions = regions.ToArray()
        End Sub

        Public Overrides Function Measure(context As MASLayoutMeasureContext) As SKSize
            If context Is Nothing Then context = New MASLayoutMeasureContext(SKSize.Empty)
            ValidateCommon(context, "MASScreenRegionLayoutNode")

            Dim contentAvailable As New SKSize(
                Math.Max(0.0F, FiniteOrZero(context.AvailableSize.Width) - Padding.Horizontal),
                Math.Max(0.0F, FiniteOrZero(context.AvailableSize.Height) - Padding.Vertical))

            Dim horizontalEdges As Single = 0.0F
            Dim verticalEdges As Single = 0.0F
            Dim centerWidth As Single = 0.0F
            Dim centerHeight As Single = 0.0F
            Dim tallestMiddle As Single = 0.0F

            For Each region As MASScreenRegion In _regions
                If region Is Nothing Then Continue For

                Dim item As MASLayoutItem = region.ToLayoutItem()
                If item Is Nothing OrElse Not item.IsVisibleForLayout Then Continue For

                Dim size As SKSize = ChildPreferredSize(item, contentAvailable, context)
                Select Case region.Role
                    Case MASScreenRegionRole.Header, MASScreenRegionRole.Command, MASScreenRegionRole.Footer
                        verticalEdges += ResolveSemanticEdgeHeight(region, size.Height, context) + item.Margin.Vertical
                        centerWidth = Math.Max(centerWidth, size.Width + item.Margin.Horizontal)
                    Case MASScreenRegionRole.Navigation, MASScreenRegionRole.Inspector
                        horizontalEdges += size.Width + item.Margin.Horizontal
                        tallestMiddle = Math.Max(tallestMiddle, size.Height + item.Margin.Vertical)
                    Case Else
                        centerWidth = Math.Max(centerWidth, size.Width + item.Margin.Horizontal)
                        centerHeight = Math.Max(centerHeight, size.Height + item.Margin.Vertical)
                End Select
            Next

            Dim measuredMiddleHeight As Single = Math.Max(tallestMiddle, centerHeight)
            Return New SKSize(horizontalEdges + centerWidth + Padding.Horizontal, verticalEdges + measuredMiddleHeight + Padding.Vertical)
        End Function

        Public Overrides Function Arrange(context As MASLayoutArrangeContext) As MASLayoutArrangeResult
            Dim result As New MASLayoutArrangeResult()
            If context Is Nothing OrElse context.BoundsLogical.IsEmpty Then Return result

            Dim remaining As SKRect = ResolveContentRect(context.BoundsLogical, Padding)
            If remaining.IsEmpty OrElse remaining.Width <= 0.0F OrElse remaining.Height <= 0.0F Then Return result

            Dim headers As List(Of MASScreenRegion) = RegionsFor(MASScreenRegionRole.Header, MASScreenRegionRole.Command)
            Dim footers As List(Of MASScreenRegion) = RegionsFor(MASScreenRegionRole.Footer)
            Dim navigation As List(Of MASScreenRegion) = RegionsFor(MASScreenRegionRole.Navigation)
            Dim inspectors As List(Of MASScreenRegion) = RegionsFor(MASScreenRegionRole.Inspector)
            Dim content As List(Of MASScreenRegion) = RegionsFor(MASScreenRegionRole.Content)

            ArrangeTopRegions(headers, remaining, context, result)
            ArrangeBottomRegions(footers, remaining, context, result)

            Dim hasContent As Boolean = content.Count > 0
            Dim middleWidth As Single = Math.Max(0.0F, remaining.Width)
            Dim navigationDesired As Single = TotalDesiredSideWidth(navigation, remaining, context)
            Dim inspectorDesired As Single = TotalDesiredSideWidth(inspectors, remaining, context)

            Dim navigationAllocated As Single = ResolveSideAllocation(
                desired:=navigationDesired,
                availableWidth:=middleWidth,
                role:=MASScreenRegionRole.Navigation,
                hasContent:=hasContent)

            Dim inspectorAllocated As Single = ResolveSideAllocation(
                desired:=inspectorDesired,
                availableWidth:=middleWidth,
                role:=MASScreenRegionRole.Inspector,
                hasContent:=hasContent)

            ProtectContentWidth(navigationAllocated, inspectorAllocated, middleWidth, hasContent)

            ArrangeLeftRegions(navigation, remaining, navigationAllocated, context, result)
            ArrangeRightRegions(inspectors, remaining, inspectorAllocated, context, result)
            ArrangeCenterRegions(content, remaining, context, result)

            Return result
        End Function

        Private Sub ArrangeTopRegions(regions As IEnumerable(Of MASScreenRegion),
                                      ByRef remaining As SKRect,
                                      context As MASLayoutArrangeContext,
                                      result As MASLayoutArrangeResult)
            If regions Is Nothing Then Return

            For Each region As MASScreenRegion In regions
                Dim item As MASLayoutItem = ToVisibleItem(region)
                If item Is Nothing Then Continue For

                Dim measureContext As New MASLayoutMeasureContext(New SKSize(remaining.Width, remaining.Height), context.IntegrationContext, context.Diagnostics)
                Dim desired As SKSize = ChildPreferredSize(item, New SKSize(remaining.Width, remaining.Height), measureContext)
                Dim preferredHeight As Single = ResolveSemanticEdgeHeight(region, desired.Height, measureContext)
                Dim h As Single = ResolveLayoutLength(item.HeightPolicy, preferredHeight, remaining.Height)
                If h <= 0.0F Then Continue For

                Dim allocatedBounds As New SKRect(
                    remaining.Left + item.Margin.Left,
                    remaining.Top + item.Margin.Top,
                    remaining.Right - item.Margin.Right,
                    remaining.Top + item.Margin.Top + h)

                Dim childBounds As SKRect = ResolveSemanticEdgeChildBounds(region, allocatedBounds, desired, h)
                ArrangeChild(item, childBounds, context, result)
                remaining.Top = Math.Min(remaining.Bottom, remaining.Top + h + item.Margin.Vertical)
                If remaining.Height <= 0.0F Then Exit For
            Next
        End Sub

        Private Sub ArrangeBottomRegions(regions As IEnumerable(Of MASScreenRegion),
                                         ByRef remaining As SKRect,
                                         context As MASLayoutArrangeContext,
                                         result As MASLayoutArrangeResult)
            If regions Is Nothing Then Return

            For Each region As MASScreenRegion In regions
                Dim item As MASLayoutItem = ToVisibleItem(region)
                If item Is Nothing Then Continue For

                Dim desired As SKSize = ChildPreferredSize(item, New SKSize(remaining.Width, remaining.Height), New MASLayoutMeasureContext(New SKSize(remaining.Width, remaining.Height), context.IntegrationContext, context.Diagnostics))
                Dim h As Single = ResolveLayoutLength(item.HeightPolicy, desired.Height, remaining.Height)
                If h <= 0.0F Then Continue For

                Dim bounds As New SKRect(
                    remaining.Left + item.Margin.Left,
                    remaining.Bottom - item.Margin.Bottom - h,
                    remaining.Right - item.Margin.Right,
                    remaining.Bottom - item.Margin.Bottom)

                ArrangeChild(item, bounds, context, result)
                remaining.Bottom = Math.Max(remaining.Top, remaining.Bottom - h - item.Margin.Vertical)
                If remaining.Height <= 0.0F Then Exit For
            Next
        End Sub

        Private Sub ArrangeLeftRegions(regions As IEnumerable(Of MASScreenRegion),
                                       ByRef remaining As SKRect,
                                       allocatedTotal As Single,
                                       context As MASLayoutArrangeContext,
                                       result As MASLayoutArrangeResult)
            ArrangeSideRegions(regions, remaining, allocatedTotal, context, result, isLeft:=True)
        End Sub

        Private Sub ArrangeRightRegions(regions As IEnumerable(Of MASScreenRegion),
                                        ByRef remaining As SKRect,
                                        allocatedTotal As Single,
                                        context As MASLayoutArrangeContext,
                                        result As MASLayoutArrangeResult)
            ArrangeSideRegions(regions, remaining, allocatedTotal, context, result, isLeft:=False)
        End Sub

        Private Sub ArrangeSideRegions(regions As IEnumerable(Of MASScreenRegion),
                                       ByRef remaining As SKRect,
                                       allocatedTotal As Single,
                                       context As MASLayoutArrangeContext,
                                       result As MASLayoutArrangeResult,
                                       isLeft As Boolean)
            If regions Is Nothing OrElse allocatedTotal <= 0.0F Then Return

            Dim desiredTotal As Single = TotalDesiredSideWidth(regions, remaining, context)
            If desiredTotal <= 0.0F Then Return

            Dim scale As Single = Math.Min(1.0F, allocatedTotal / desiredTotal)

            For Each region As MASScreenRegion In regions
                Dim item As MASLayoutItem = ToVisibleItem(region)
                If item Is Nothing Then Continue For

                Dim desired As SKSize = ChildPreferredSize(item, New SKSize(remaining.Width, remaining.Height), New MASLayoutMeasureContext(New SKSize(remaining.Width, remaining.Height), context.IntegrationContext, context.Diagnostics))
                Dim measuredWidth As Single = ResolveLayoutLength(item.WidthPolicy, desired.Width, remaining.Width)
                Dim w As Single = Math.Max(0.0F, Math.Min(measuredWidth * scale, remaining.Width))
                If w <= 0.0F Then Continue For

                Dim bounds As SKRect
                If isLeft Then
                    bounds = New SKRect(
                        remaining.Left + item.Margin.Left,
                        remaining.Top + item.Margin.Top,
                        remaining.Left + item.Margin.Left + w,
                        remaining.Bottom - item.Margin.Bottom)
                    ArrangeChild(item, bounds, context, result)
                    remaining.Left = Math.Min(remaining.Right, remaining.Left + w + item.Margin.Horizontal)
                Else
                    bounds = New SKRect(
                        remaining.Right - item.Margin.Right - w,
                        remaining.Top + item.Margin.Top,
                        remaining.Right - item.Margin.Right,
                        remaining.Bottom - item.Margin.Bottom)
                    ArrangeChild(item, bounds, context, result)
                    remaining.Right = Math.Max(remaining.Left, remaining.Right - w - item.Margin.Horizontal)
                End If

                If remaining.Width <= 0.0F Then Exit For
            Next
        End Sub

        Private Sub ArrangeCenterRegions(regions As IEnumerable(Of MASScreenRegion),
                                         remaining As SKRect,
                                         context As MASLayoutArrangeContext,
                                         result As MASLayoutArrangeResult)
            If regions Is Nothing Then Return

            For Each region As MASScreenRegion In regions
                Dim item As MASLayoutItem = ToVisibleItem(region)
                If item Is Nothing Then Continue For

                Dim bounds As New SKRect(
                    remaining.Left + item.Margin.Left,
                    remaining.Top + item.Margin.Top,
                    remaining.Right - item.Margin.Right,
                    remaining.Bottom - item.Margin.Bottom)

                ArrangeChild(item, bounds, context, result)
            Next
        End Sub

        Private Shared Function ResolveSemanticEdgeHeight(region As MASScreenRegion,
                                                          measuredHeight As Single,
                                                          context As MASLayoutMeasureContext) As Single
            Dim safeHeight As Single = Math.Max(0.0F, FiniteOrZero(measuredHeight))
            If region Is Nothing Then Return safeHeight
            If region.Role <> MASScreenRegionRole.Command Then Return safeHeight
            If region.HeightPolicy IsNot Nothing Then Return safeHeight

            Dim profile As MASSizeProfile = MASSizeProfile.[Default]
            If context IsNot Nothing AndAlso context.IntegrationContext IsNot Nothing Then
                profile = context.IntegrationContext.SizeProfile
            End If

            Return Math.Max(safeHeight, MASScreenRegionLayoutTokens.ResolveCommandRegionHeight(profile))
        End Function

        ''' <summary>
        ''' Keeps semantic command content optically centered inside the slot owned by the
        ''' root region layout. The reserved region height remains unchanged, so Content
        ''' still starts after the official Command slot; only the child row/control is
        ''' placed at the slot center instead of inheriting a top-edge bias.
        ''' </summary>
        Private Shared Function ResolveSemanticEdgeChildBounds(region As MASScreenRegion,
                                                               allocatedBounds As SKRect,
                                                               desired As SKSize,
                                                               allocatedHeight As Single) As SKRect
            If region Is Nothing OrElse region.Role <> MASScreenRegionRole.Command Then Return allocatedBounds
            If region.HeightPolicy IsNot Nothing Then Return allocatedBounds
            If allocatedBounds.IsEmpty Then Return allocatedBounds

            Dim desiredHeight As Single = Math.Max(0.0F, FiniteOrZero(desired.Height))
            Dim safeAllocatedHeight As Single = Math.Max(0.0F, FiniteOrZero(allocatedHeight))
            If desiredHeight <= 0.0F OrElse safeAllocatedHeight <= desiredHeight Then Return allocatedBounds

            Dim y As Single = allocatedBounds.Top + ((safeAllocatedHeight - desiredHeight) / 2.0F)
            Return New SKRect(allocatedBounds.Left, y, allocatedBounds.Right, y + desiredHeight)
        End Function

        Private Function RegionsFor(ParamArray roles As MASScreenRegionRole()) As List(Of MASScreenRegion)
            Dim result As New List(Of MASScreenRegion)()
            If roles Is Nothing OrElse roles.Length = 0 Then Return result

            For Each region As MASScreenRegion In _regions
                If region Is Nothing Then Continue For
                For Each role As MASScreenRegionRole In roles
                    If region.Role = role Then
                        result.Add(region)
                        Exit For
                    End If
                Next
            Next

            Return result
        End Function

        Private Shared Function TotalDesiredSideWidth(regions As IEnumerable(Of MASScreenRegion),
                                                      remaining As SKRect,
                                                      context As MASLayoutArrangeContext) As Single
            Dim total As Single = 0.0F
            If regions Is Nothing OrElse context Is Nothing Then Return total

            For Each region As MASScreenRegion In regions
                If region Is Nothing Then Continue For
                Dim item As MASLayoutItem = region.ToLayoutItem()
                If item Is Nothing OrElse Not item.IsVisibleForLayout Then Continue For

                Dim desired As SKSize = ChildPreferredSize(item, New SKSize(remaining.Width, remaining.Height), New MASLayoutMeasureContext(New SKSize(remaining.Width, remaining.Height), context.IntegrationContext, context.Diagnostics))
                total += ResolveLayoutLength(item.WidthPolicy, desired.Width, remaining.Width) + item.Margin.Horizontal
            Next

            Return Math.Max(0.0F, total)
        End Function

        Private Shared Function ResolveSideAllocation(desired As Single,
                                                      availableWidth As Single,
                                                      role As MASScreenRegionRole,
                                                      hasContent As Boolean) As Single
            Dim safeAvailable As Single = Math.Max(0.0F, FiniteOrZero(availableWidth))
            Dim safeDesired As Single = Math.Max(0.0F, FiniteOrZero(desired))
            If safeDesired <= 0.0F OrElse safeAvailable <= 0.0F Then Return 0.0F
            If Not hasContent Then Return Math.Min(safeDesired, safeAvailable)

            Dim share As Single = If(role = MASScreenRegionRole.Inspector, MASScreenRegionLayoutTokens.InspectorMaxShareWithContent, MASScreenRegionLayoutTokens.NavigationMaxShareWithContent)
            Return Math.Min(safeDesired, safeAvailable * share)
        End Function

        Private Shared Sub ProtectContentWidth(ByRef navigationAllocated As Single,
                                               ByRef inspectorAllocated As Single,
                                               availableWidth As Single,
                                               hasContent As Boolean)
            If Not hasContent Then Return

            Dim safeAvailable As Single = Math.Max(0.0F, FiniteOrZero(availableWidth))
            If safeAvailable <= 0.0F Then
                navigationAllocated = 0.0F
                inspectorAllocated = 0.0F
                Return
            End If

            Dim maxCombined As Single = safeAvailable * MASScreenRegionLayoutTokens.SideCombinedMaxShareWithContent
            Dim combined As Single = Math.Max(0.0F, navigationAllocated) + Math.Max(0.0F, inspectorAllocated)
            If combined <= maxCombined OrElse combined <= 0.0F Then Return

            Dim scale As Single = maxCombined / combined
            navigationAllocated *= scale
            inspectorAllocated *= scale
        End Sub

        Private Shared Function ToVisibleItem(region As MASScreenRegion) As MASLayoutItem
            If region Is Nothing Then Return Nothing
            Dim item As MASLayoutItem = region.ToLayoutItem()
            If item Is Nothing OrElse Not item.IsVisibleForLayout Then Return Nothing
            Return item
        End Function

        Private Shared Function CollectRegions(regions As IEnumerable(Of MASScreenRegion)) As List(Of MASScreenRegion)
            Dim result As New List(Of MASScreenRegion)()
            If regions Is Nothing Then Return result

            For Each region As MASScreenRegion In regions
                If region IsNot Nothing Then result.Add(region)
            Next

            Return result
        End Function

        Private Shared Function ToLayoutItems(regions As IEnumerable(Of MASScreenRegion)) As IEnumerable(Of MASLayoutItem)
            Dim result As New List(Of MASLayoutItem)()
            If regions Is Nothing Then Return result

            For Each region As MASScreenRegion In regions
                If region Is Nothing Then Continue For
                result.Add(region.ToLayoutItem())
            Next

            Return result
        End Function

    End Class

End Namespace
