Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Composition

    ''' <summary>
    ''' Semantic application command-action row. It owns the production behavior expected from
    ''' a command region: preferred sizing, proportional shrink, hard in-slot clipping safety,
    ''' and a host-presented overflow entry when visible actions no longer fit at their minimum
    ''' readable width.
    ''' </summary>
    <Global.System.ComponentModel.EditorBrowsable(Global.System.ComponentModel.EditorBrowsableState.Advanced)>
    Friend NotInheritable Class MASCommandActionsLayoutNode
        Inherits MASLayoutNodeBase

        Private ReadOnly _overflowButton As MASButton
        Private ReadOnly _lastOverflowActions As New List(Of MASControlBase)()

        Friend Sub New(actions As IEnumerable(Of MASControlBase),
                       Optional spacing As Single = 0.0F,
                       Optional padding As MASLayoutThickness = Nothing)
            MyBase.New(CreateItems(actions), spacing, padding, MASLayoutAlignment.Center)

            _overflowButton = MASButton.Create("⋯").AsSubtle()
            _overflowButton.Name = "MAS_CommandActions_Overflow"
            _overflowButton.CompositionLifetimeOwnedInternal = True
            _overflowButton.WithCommand(Sub(button As MASButton) PresentOverflow())
        End Sub

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

            Dim profile As MASSizeProfile = MASRuntimeSizeProfileService.Current
            Dim preferredWidth As Single = MASScreenRegionLayoutTokens.ResolveCommandActionWidth(profile)
            Dim height As Single = MASScreenRegionLayoutTokens.ResolveCommandActionHeight(profile)
            Dim visibleCount As Integer = CountVisibleActions()

            Dim width As Single = 0.0F
            If visibleCount > 0 Then
                width = (preferredWidth * CSng(visibleCount)) + (Spacing * CSng(Math.Max(0, visibleCount - 1)))
            End If

            Return New SKSize(width + Padding.Horizontal, height + Padding.Vertical)
        End Function

        Public Overrides Function Arrange(context As MASLayoutArrangeContext) As MASLayoutArrangeResult
            Dim result As New MASLayoutArrangeResult()
            _lastOverflowActions.Clear()

            If context Is Nothing OrElse context.BoundsLogical.IsEmpty Then Return result

            Dim content As SKRect = ResolveContentRect(context.BoundsLogical, Padding)
            If content.IsEmpty Then Return result

            Dim visible As List(Of MASLayoutItem) = CollectVisibleItems(Items)
            If visible.Count = 0 Then Return result

            Dim profile As MASSizeProfile = MASRuntimeSizeProfileService.Current
            Dim preferredActionWidth As Single = MASScreenRegionLayoutTokens.ResolveCommandActionWidth(profile)
            Dim minimumActionWidth As Single = MASScreenRegionLayoutTokens.ResolveCommandActionMinimumWidth(profile)
            Dim overflowActionWidth As Single = MASScreenRegionLayoutTokens.ResolveCommandActionOverflowWidth(profile)
            Dim actionHeight As Single = Math.Min(MASScreenRegionLayoutTokens.ResolveCommandActionHeight(profile), content.Height)

            If actionHeight <= 0.0F OrElse content.Width <= 0.0F Then Return result

            Dim plan As MASCommandActionsFitPlan = ResolveFitPlan(
                actionCount:=visible.Count,
                availableWidth:=content.Width,
                preferredActionWidth:=preferredActionWidth,
                minimumActionWidth:=minimumActionWidth,
                overflowActionWidth:=overflowActionWidth,
                spacing:=Spacing)

            If plan.VisibleActionCount <= 0 AndAlso Not plan.ShowOverflow Then Return result

            RememberOverflowActions(visible, plan.VisibleActionCount)

            Dim y As Single = content.Top + Math.Max(0.0F, (content.Height - actionHeight) / 2.0F)
            Dim x As Single = content.Left

            For i As Integer = 0 To plan.VisibleActionCount - 1
                Dim item As MASLayoutItem = visible(i)
                Dim bounds As New SKRect(x, y, x + plan.ActionWidth, y + actionHeight)
                ArrangeChild(item, bounds, context, result)
                x = bounds.Right + Spacing
            Next

            If plan.ShowOverflow Then
                Dim overflowWidth As Single = Math.Min(plan.OverflowWidth, Math.Max(0.0F, content.Right - x))
                If overflowWidth > 0.0F Then
                    Dim overflowBounds As New SKRect(x, y, x + overflowWidth, y + actionHeight)
                    Dim overflowSlot As MASLayoutSlot = MASLayoutSlotFactory.CreateForControl(_overflowButton, overflowBounds, context.IntegrationContext, _overflowButton.SizeIntent)
                    result.Add(New MASLayoutPlacement(_overflowButton, overflowBounds, Nothing, overflowSlot, MASLayoutControlOwnership.OwnedByComposition))
                End If
            End If

            Return result
        End Function

        Private Shared Function CreateItems(actions As IEnumerable(Of MASControlBase)) As IEnumerable(Of MASLayoutItem)
            Dim items As New List(Of MASLayoutItem)()
            Dim seen As New HashSet(Of MASControlBase)()

            If actions Is Nothing Then Return items

            For Each action As MASControlBase In actions
                If action Is Nothing Then Continue For
                If seen.Contains(action) Then Continue For

                seen.Add(action)
                items.Add(MASComposition.Item(
                    control:=action,
                    width:=MASSizePolicy.Range(
                        min:=MASScreenRegionLayoutTokens.ResolveCommandActionMinimumWidth(MASRuntimeSizeProfileService.Current),
                        max:=MASScreenRegionLayoutTokens.ResolveCommandActionWidth(MASRuntimeSizeProfileService.Current)),
                    height:=MASSizePolicy.Fixed(MASScreenRegionLayoutTokens.ResolveCommandActionHeight(MASRuntimeSizeProfileService.Current)),
                    alignment:=MASLayoutAlignment.Center))
            Next

            Return items
        End Function

        Private Function CountVisibleActions() As Integer
            Dim result As Integer = 0

            For Each item As MASLayoutItem In Items
                If item IsNot Nothing AndAlso item.IsVisibleForLayout Then result += 1
            Next

            Return result
        End Function

        Private Sub RememberOverflowActions(visible As List(Of MASLayoutItem),
                                            visibleActionCount As Integer)
            If visible Is Nothing Then Return

            For i As Integer = Math.Max(0, visibleActionCount) To visible.Count - 1
                Dim item As MASLayoutItem = visible(i)
                If item Is Nothing OrElse item.Control Is Nothing Then Continue For
                _lastOverflowActions.Add(item.Control)
            Next
        End Sub

        Private Sub PresentOverflow()
            If _lastOverflowActions.Count = 0 Then Return
            MASCommandActionsOverflowPresenterRegistry.TryPresent(_overflowButton, _lastOverflowActions.AsReadOnly())
        End Sub

        Private Shared Function ResolveFitPlan(actionCount As Integer,
                                               availableWidth As Single,
                                               preferredActionWidth As Single,
                                               minimumActionWidth As Single,
                                               overflowActionWidth As Single,
                                               spacing As Single) As MASCommandActionsFitPlan
            Dim safeActionCount As Integer = Math.Max(0, actionCount)
            Dim safeAvailable As Single = SafeLength(availableWidth)
            Dim safePreferred As Single = SafePositive(preferredActionWidth, 1.0F)
            Dim safeMinimum As Single = Math.Min(safePreferred, SafePositive(minimumActionWidth, safePreferred))
            Dim safeOverflow As Single = Math.Min(safePreferred, SafePositive(overflowActionWidth, safeMinimum))
            Dim safeSpacing As Single = SafeLength(spacing)

            If safeActionCount <= 0 OrElse safeAvailable <= 0.0F Then
                Return MASCommandActionsFitPlan.Empty()
            End If

            Dim preferredTotal As Single = (safePreferred * CSng(safeActionCount)) + (safeSpacing * CSng(Math.Max(0, safeActionCount - 1)))
            If preferredTotal <= safeAvailable Then
                Return New MASCommandActionsFitPlan(safeActionCount, safePreferred, False, 0.0F)
            End If

            Dim equalWidth As Single = (safeAvailable - (safeSpacing * CSng(Math.Max(0, safeActionCount - 1)))) / CSng(safeActionCount)
            If equalWidth >= safeMinimum Then
                Return New MASCommandActionsFitPlan(safeActionCount, Math.Min(safePreferred, equalWidth), False, 0.0F)
            End If

            For visibleCount As Integer = safeActionCount - 1 To 0 Step -1
                Dim participantCount As Integer = visibleCount + 1
                Dim gapTotal As Single = safeSpacing * CSng(Math.Max(0, participantCount - 1))
                Dim availableForNormalActions As Single = safeAvailable - safeOverflow - gapTotal

                If visibleCount = 0 Then
                    If safeOverflow <= safeAvailable Then
                        Return New MASCommandActionsFitPlan(0, 0.0F, True, safeOverflow)
                    End If
                ElseIf availableForNormalActions > 0.0F Then
                    Dim width As Single = availableForNormalActions / CSng(visibleCount)
                    If width >= safeMinimum Then
                        Return New MASCommandActionsFitPlan(visibleCount, Math.Min(safePreferred, width), True, safeOverflow)
                    End If
                End If
            Next

            Return MASCommandActionsFitPlan.Empty()
        End Function

        Private Shared Function SafeLength(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 SafePositive(value As Single,
                                             fallback As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) OrElse value <= 0.0F Then Return Math.Max(0.0F, fallback)
            Return value
        End Function

        Private NotInheritable Class MASCommandActionsFitPlan
            Friend ReadOnly Property VisibleActionCount As Integer
            Friend ReadOnly Property ActionWidth As Single
            Friend ReadOnly Property ShowOverflow As Boolean
            Friend ReadOnly Property OverflowWidth As Single

            Friend Sub New(visibleActionCount As Integer,
                           actionWidth As Single,
                           showOverflow As Boolean,
                           overflowWidth As Single)
                Me.VisibleActionCount = Math.Max(0, visibleActionCount)
                Me.ActionWidth = SafeLength(actionWidth)
                Me.ShowOverflow = showOverflow
                Me.OverflowWidth = SafeLength(overflowWidth)
            End Sub

            Friend Shared Function Empty() As MASCommandActionsFitPlan
                Return New MASCommandActionsFitPlan(0, 0.0F, False, 0.0F)
            End Function
        End Class

    End Class

End Namespace
