Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components
Imports Nexamas.UI.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    Partial Public NotInheritable Class MASProgressBar
#Region "Label Layout"

        Private Sub OnLabelContentChanged(sender As Object, e As EventArgs)
            ArrangeLabel()
            InvalidateVisual()
        End Sub

        Private Function ResolveActiveLabelPlacement() As MASProgressBarLabelPlacement
            If _label Is Nothing Then Return MASProgressBarLabelPlacement.None
            If _labelPlacement = MASProgressBarLabelPlacement.None Then Return MASProgressBarLabelPlacement.None
            Return _labelPlacement
        End Function

        ''' <summary>
        ''' Keeps the imported label in the progress-bar-owned child slot.
        ''' This method is intentionally called from state/bounds transitions only, never from Render.
        ''' Render must stay side-effect free; otherwise a label placement update can request a host
        ''' repaint while the layered window is already painting, creating a Render/Invalidate loop.
        ''' </summary>
        Private Sub ArrangeLabel()
            If _label Is Nothing Then Return

            Dim placement As MASProgressBarLabelPlacement = ResolveActiveLabelPlacement()
            If placement = MASProgressBarLabelPlacement.None Then
                ApplyLabelBoundsIfChanged(SKRect.Empty)
                Return
            End If

            Dim labelBounds As SKRect = ResolveLabelBoundsLogical(BoundsLogical, placement)
            ApplyLabelBoundsIfChanged(labelBounds)

            If _autoArrangeLabelAlignment Then
                ApplyLabelAlignment(placement)
            End If
        End Sub

        Private Sub ApplyLabelBoundsIfChanged(bounds As SKRect)
            If _label Is Nothing Then Return
            If SameRect(_label.BoundsLogical, bounds) Then Return

            _label.SetLocalLayoutBoundsInternal(bounds)
        End Sub

        Private Shared Function SameRect(left As SKRect, right As SKRect) As Boolean
            Const Epsilon As Single = 0.01F

            Return Math.Abs(left.Left - right.Left) <= Epsilon AndAlso
                   Math.Abs(left.Top - right.Top) <= Epsilon AndAlso
                   Math.Abs(left.Right - right.Right) <= Epsilon AndAlso
                   Math.Abs(left.Bottom - right.Bottom) <= Epsilon
        End Function

        Private Sub ApplyLabelAlignment(placement As MASProgressBarLabelPlacement)
            If _label Is Nothing Then Return

            Select Case placement
                Case MASProgressBarLabelPlacement.Left
                    _label.HorizontalAlignment = MASHorizontalAlignment.Right
                    _label.VerticalAlignment = MASVerticalAlignment.Center
                Case MASProgressBarLabelPlacement.Right
                    _label.HorizontalAlignment = MASHorizontalAlignment.Left
                    _label.VerticalAlignment = MASVerticalAlignment.Center
                Case Else
                    _label.HorizontalAlignment = MASHorizontalAlignment.Center
                    _label.VerticalAlignment = MASVerticalAlignment.Center
            End Select
        End Sub

        Private Function ResolveLabelBoundsLogical(owner As SKRect,
                                                   placement As MASProgressBarLabelPlacement) As SKRect
            If owner.Width <= 0.0F OrElse owner.Height <= 0.0F Then
                Return New SKRect(0.0F, 0.0F, 0.0F, 0.0F)
            End If

            Dim gap As Single = ResolveOutsideGap(placement)
            Dim extent As Single = ResolveSafeLabelExtent(owner, placement, gap)

            Select Case placement
                Case MASProgressBarLabelPlacement.Top
                    Return New SKRect(owner.Left, owner.Top, owner.Right, owner.Top + extent)
                Case MASProgressBarLabelPlacement.Bottom
                    Return New SKRect(owner.Left, owner.Bottom - extent, owner.Right, owner.Bottom)
                Case MASProgressBarLabelPlacement.Left
                    Return New SKRect(owner.Left, owner.Top, owner.Left + extent, owner.Bottom)
                Case MASProgressBarLabelPlacement.Right
                    Return New SKRect(owner.Right - extent, owner.Top, owner.Right, owner.Bottom)
                Case MASProgressBarLabelPlacement.InsideCenter
                    Return ResolveTrackBoundsLogical(ResolveProgressSlotBoundsLogical(owner))
                Case Else
                    Return New SKRect(0.0F, 0.0F, 0.0F, 0.0F)
            End Select
        End Function

        Private Function ResolveProgressSlotBoundsLogical(owner As SKRect) As SKRect
            If owner.Width <= 0.0F OrElse owner.Height <= 0.0F Then Return owner

            Dim placement As MASProgressBarLabelPlacement = ResolveActiveLabelPlacement()
            If placement = MASProgressBarLabelPlacement.None OrElse placement = MASProgressBarLabelPlacement.InsideCenter Then Return owner

            Dim gap As Single = ResolveOutsideGap(placement)
            Dim extent As Single = ResolveSafeLabelExtent(owner, placement, gap)

            Select Case placement
                Case MASProgressBarLabelPlacement.Top
                    Return New SKRect(owner.Left, owner.Top + extent + gap, owner.Right, owner.Bottom)
                Case MASProgressBarLabelPlacement.Bottom
                    Return New SKRect(owner.Left, owner.Top, owner.Right, owner.Bottom - extent - gap)
                Case MASProgressBarLabelPlacement.Left
                    Return New SKRect(owner.Left + extent + gap, owner.Top, owner.Right, owner.Bottom)
                Case MASProgressBarLabelPlacement.Right
                    Return New SKRect(owner.Left, owner.Top, owner.Right - extent - gap, owner.Bottom)
                Case Else
                    Return owner
            End Select
        End Function

        Private Function ResolveTrackBoundsLogical(progressSlot As SKRect) As SKRect
            If progressSlot.Width <= 0.0F OrElse progressSlot.Height <= 0.0F Then Return progressSlot
            If _frameMode <> MASProgressBarFrameMode.ContainedField Then Return progressSlot

            Dim pad As Single = ResolveContainerPaddingLogical(progressSlot)
            If pad <= 0.0F Then Return progressSlot

            Dim track As SKRect = progressSlot
            track.Inflate(-pad, -pad)

            If track.Width <= 0.0F OrElse track.Height <= 0.0F Then Return progressSlot
            Return track
        End Function

        Private Shared Function ResolveContainerPaddingLogical(progressSlot As SKRect) As Single
            Dim requested As Single = ProgressBarTokens.ContainerPadding
            Dim maxByHeight As Single = Math.Max(0.0F, (progressSlot.Height - ProgressBarTokens.MinTrackThickness) * 0.5F)
            Dim maxByWidth As Single = Math.Max(0.0F, (progressSlot.Width - ProgressBarTokens.MinWidth) * 0.5F)

            Return Math.Max(0.0F, Math.Min(requested, Math.Min(maxByHeight, maxByWidth)))
        End Function

        Private Shared Function ResolveFrameExtraLogical(frameMode As MASProgressBarFrameMode) As Single
            If NormalizeFrameMode(frameMode) <> MASProgressBarFrameMode.ContainedField Then Return 0.0F
            Return ProgressBarTokens.ContainerPadding * 2.0F
        End Function

        Private Function ResolveOutsideGap(placement As MASProgressBarLabelPlacement) As Single
            Select Case placement
                Case MASProgressBarLabelPlacement.Top,
                     MASProgressBarLabelPlacement.Bottom,
                     MASProgressBarLabelPlacement.Left,
                     MASProgressBarLabelPlacement.Right
                    Return _labelGap
                Case Else
                    Return 0.0F
            End Select
        End Function

        Private Function ResolveSafeLabelExtent(owner As SKRect,
                                                placement As MASProgressBarLabelPlacement,
                                                gap As Single) As Single
            Dim desired As Single = ResolveLabelExtent(placement)

            Dim frameExtra As Single = ResolveFrameExtraLogical(_frameMode)

            Select Case placement
                Case MASProgressBarLabelPlacement.Top, MASProgressBarLabelPlacement.Bottom
                    Dim minProgressHeight As Single = ProgressBarTokens.MinTrackThickness + frameExtra
                    Dim maxExtent As Single = Math.Max(0.0F, owner.Height - minProgressHeight - gap)
                    Return Math.Min(desired, maxExtent)
                Case MASProgressBarLabelPlacement.Left, MASProgressBarLabelPlacement.Right
                    Dim minProgressWidth As Single = ProgressBarTokens.MinWidth + frameExtra
                    Dim maxExtent As Single = Math.Max(0.0F, owner.Width - minProgressWidth - gap)
                    Return Math.Min(desired, maxExtent)
                Case Else
                    Return desired
            End Select
        End Function

        Private Function ResolveLabelExtent(placement As MASProgressBarLabelPlacement) As Single
            If _labelExtent > 0.0F Then Return _labelExtent

            Select Case placement
                Case MASProgressBarLabelPlacement.Left, MASProgressBarLabelPlacement.Right
                    Return ProgressBarTokens.LabelSideExtent
                Case MASProgressBarLabelPlacement.Top, MASProgressBarLabelPlacement.Bottom
                    Return ProgressBarTokens.LabelBlockExtent
                Case Else
                    Return 0.0F
            End Select
        End Function

#End Region
    End Class

End Namespace
