Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Friend NotInheritable Class MASTabLayout

        Friend Structure Metrics
            Friend OuterR As SKRect
            Friend StripR As SKRect
            Friend ViewportR As SKRect
            Friend LeftNavR As SKRect
            Friend RightNavR As SKRect

            Friend PadX As Single
            Friend PadY As Single
            Friend Gap As Single

            Friend ItemInsetX As Single
            Friend ItemInsetY As Single

            Friend ItemMinW As Single
            Friend ItemH As Single
            Friend HairW As Single
            Friend TextPadX As Single

            Friend ViewportW As Single
            Friend ContentW As Single
            Friend MaxScrollOffset As Single
            Friend EffectiveScrollOffset As Single

            Friend HasOverflow As Boolean
            Friend ShowLeftNav As Boolean
            Friend ShowRightNav As Boolean

            Public ItemRects() As SKRect
            Public ItemOuterRects() As SKRect
        End Structure

        Friend Shared Function Compute(fullBoundsPx As SKRect,
                                       dpi As Single,
                                       items As IReadOnlyList(Of MASTabItem),
                                       measureTextWidth As Func(Of String, Single),
                                       requestedScrollOffsetPx As Single) As Metrics

            Dim m As New Metrics With {
                .OuterR = SKRect.Empty,
                .StripR = SKRect.Empty,
                .ViewportR = SKRect.Empty,
                .LeftNavR = SKRect.Empty,
                .RightNavR = SKRect.Empty,
                .PadX = 0.0F,
                .PadY = 0.0F,
                .Gap = 0.0F,
                .ItemInsetX = 0.0F,
                .ItemInsetY = 0.0F,
                .ItemMinW = 0.0F,
                .ItemH = 0.0F,
                .HairW = 0.0F,
                .TextPadX = 0.0F,
                .ViewportW = 0.0F,
                .ContentW = 0.0F,
                .MaxScrollOffset = 0.0F,
                .EffectiveScrollOffset = 0.0F,
                .HasOverflow = False,
                .ShowLeftNav = False,
                .ShowRightNav = False,
                .ItemRects = Array.Empty(Of SKRect)(),
                .ItemOuterRects = Array.Empty(Of SKRect)()
            }

            If fullBoundsPx.Width <= 1.0F OrElse fullBoundsPx.Height <= 1.0F Then Return m

            Dim px As Single = PixelSnap.SafeDpi(dpi)

            m.OuterR = PixelSnap.SnapRectHalf(fullBoundsPx)

            m.PadX = MASTabPremiumVisualPolicy.ResolveRailPaddingX(dpi)
            m.PadY = MASTabPremiumVisualPolicy.ResolveRailPaddingY(dpi)
            m.Gap = MASTabPremiumVisualPolicy.ResolveItemGap(dpi)

            m.ItemInsetX = MASTabPremiumVisualPolicy.ResolveItemInsetX(dpi)
            m.ItemInsetY = MASTabPremiumVisualPolicy.ResolveItemInsetY(dpi)

            m.ItemMinW = MASTabPremiumVisualPolicy.ResolveItemMinWidth(dpi)
            m.ItemH = PixelSnap.SnapHalf(Math.Max(1.0F, m.OuterR.Height - (m.PadY * 2.0F)))
            m.HairW = PixelSnap.SnapHalf(Math.Max(1.0F, 1.0F * px))
            m.TextPadX = MASTabPremiumVisualPolicy.ResolveItemTextPadX(dpi)

            Dim strip As SKRect = m.OuterR
            strip.Inflate(-m.PadX, -m.PadY)
            m.StripR = PixelSnap.SnapRectHalf(strip)

            If items Is Nothing OrElse items.Count <= 0 OrElse measureTextWidth Is Nothing Then
                m.ViewportR = m.StripR
                m.ViewportW = Math.Max(0.0F, m.StripR.Width)
                Return m
            End If

            Dim count As Integer = items.Count
            Dim desired(count - 1) As Single
            Dim totalDesired As Single = 0.0F

            For i As Integer = 0 To count - 1
                Dim item As MASTabItem = items(i)
                Dim txt As String = If(item IsNot Nothing, item.Text, String.Empty)

                Dim measuredTextW As Single = Math.Max(0.0F, measureTextWidth(txt))
                Dim desiredW As Single =
                    measuredTextW +
                    (m.TextPadX * 2.0F) +
                    (m.ItemInsetX * 2.0F)

                desiredW = Math.Max(m.ItemMinW, PixelSnap.SnapHalf(desiredW))
                desired(i) = desiredW
                totalDesired += desiredW
            Next

            Dim totalGap As Single = m.Gap * Math.Max(0, count - 1)
            Dim stripW As Single = Math.Max(0.0F, m.StripR.Width)

            Dim navSlotW As Single = MASTabPremiumVisualPolicy.ResolveNavSlotWidth(m.StripR.Height, dpi)
            Dim navGap As Single = MASTabPremiumVisualPolicy.ResolveNavGap(dpi)

            Dim contentNaturalW As Single = totalDesired + totalGap
            Dim overflowIfNoNav As Boolean = (contentNaturalW > stripW + 0.5F)

            If overflowIfNoNav Then
                Dim viewport As SKRect = m.StripR
                viewport.Left += navSlotW + navGap
                viewport.Right -= navSlotW + navGap
                m.ViewportR = PixelSnap.SnapRectHalf(viewport)

                m.LeftNavR = PixelSnap.SnapRectHalf(New SKRect(
                    m.StripR.Left,
                    m.StripR.Top,
                    m.StripR.Left + navSlotW,
                    m.StripR.Bottom))

                m.RightNavR = PixelSnap.SnapRectHalf(New SKRect(
                    m.StripR.Right - navSlotW,
                    m.StripR.Top,
                    m.StripR.Right,
                    m.StripR.Bottom))
            Else
                m.ViewportR = m.StripR
                m.LeftNavR = SKRect.Empty
                m.RightNavR = SKRect.Empty
            End If

            m.ViewportW = Math.Max(0.0F, m.ViewportR.Width)

            Dim finalWidths(count - 1) As Single

            If contentNaturalW <= m.ViewportW + 0.5F Then
                Dim extra As Single = m.ViewportW - contentNaturalW
                Dim growEach As Single = If(count > 0, extra / count, 0.0F)

                For i As Integer = 0 To count - 1
                    finalWidths(i) = PixelSnap.SnapHalf(desired(i) + growEach)
                Next
            Else
                ' Overflow tabs keep their readable natural widths and move through
                ' the MASMotion-backed scroll route instead of being squeezed until
                ' the strip looks broken. This makes the end arrows meaningful and
                ' keeps the selected tab reveal behavior premium.
                For i As Integer = 0 To count - 1
                    finalWidths(i) = desired(i)
                Next
            End If

            Dim finalContentW As Single = totalGap
            For i As Integer = 0 To count - 1
                finalContentW += finalWidths(i)
            Next

            m.ContentW = PixelSnap.SnapHalf(finalContentW)
            m.MaxScrollOffset = Math.Max(0.0F, PixelSnap.SnapHalf(m.ContentW - m.ViewportW))
            m.EffectiveScrollOffset = Clamp(requestedScrollOffsetPx, 0.0F, m.MaxScrollOffset)

            m.HasOverflow = (m.MaxScrollOffset > 0.5F)
            m.ShowLeftNav = m.HasOverflow AndAlso (m.EffectiveScrollOffset > 0.5F)
            m.ShowRightNav = m.HasOverflow AndAlso (m.EffectiveScrollOffset < m.MaxScrollOffset - 0.5F)

            Dim outerRects(count - 1) As SKRect
            Dim innerRects(count - 1) As SKRect

            Dim x As Single = m.ViewportR.Left - m.EffectiveScrollOffset

            For i As Integer = 0 To count - 1
                Dim outerItemR As New SKRect(
                    x,
                    m.ViewportR.Top,
                    x + finalWidths(i),
                    m.ViewportR.Bottom
                )

                outerItemR = PixelSnap.SnapRectHalf(outerItemR)
                outerRects(i) = outerItemR

                Dim innerItemR As SKRect = outerItemR
                innerItemR.Inflate(-m.ItemInsetX, -m.ItemInsetY)
                innerRects(i) = PixelSnap.SnapRectHalf(innerItemR)

                x += finalWidths(i) + m.Gap
            Next

            m.ItemOuterRects = outerRects
            m.ItemRects = innerRects

            Return m
        End Function

        Friend Shared Function ComputeItemRect(lay As Metrics,
                                               index As Integer) As SKRect

            If lay.ItemRects Is Nothing Then Return SKRect.Empty
            If index < 0 OrElse index >= lay.ItemRects.Length Then Return SKRect.Empty
            Return lay.ItemRects(index)
        End Function

        Friend Shared Function HitIndex(lay As Metrics,
                                        ptPx As SKPoint) As Integer

            If lay.ItemRects Is Nothing OrElse lay.ItemRects.Length <= 0 Then Return -1
            If lay.ViewportR.IsEmpty Then Return -1
            If Not lay.ViewportR.Contains(ptPx.X, ptPx.Y) Then Return -1

            For i As Integer = 0 To lay.ItemRects.Length - 1
                Dim visibleR As SKRect = Intersect(lay.ItemRects(i), lay.ViewportR)
                If visibleR.Width > 0.5F AndAlso visibleR.Height > 0.5F Then
                    If visibleR.Contains(ptPx.X, ptPx.Y) Then
                        Return i
                    End If
                End If
            Next

            Return -1
        End Function

        Friend Shared Function HitNavButton(lay As Metrics,
                                            ptPx As SKPoint) As MASTabNavButtonKind

            If lay.HasOverflow Then
                If lay.ShowLeftNav AndAlso Not lay.LeftNavR.IsEmpty AndAlso lay.LeftNavR.Contains(ptPx.X, ptPx.Y) Then
                    Return MASTabNavButtonKind.LeftButton
                End If

                If lay.ShowRightNav AndAlso Not lay.RightNavR.IsEmpty AndAlso lay.RightNavR.Contains(ptPx.X, ptPx.Y) Then
                    Return MASTabNavButtonKind.RightButton
                End If
            End If

            Return MASTabNavButtonKind.None
        End Function

        Friend Shared Function GetScrollOffsetToRevealItem(lay As Metrics,
                                                           index As Integer) As Single

            If lay.ItemOuterRects Is Nothing Then Return lay.EffectiveScrollOffset
            If index < 0 OrElse index >= lay.ItemOuterRects.Length Then Return lay.EffectiveScrollOffset
            If lay.ViewportR.IsEmpty Then Return lay.EffectiveScrollOffset

            Dim itemOuter As SKRect = lay.ItemOuterRects(index)

            Dim desired As Single = lay.EffectiveScrollOffset

            Dim revealMarginPx As Single = MASTabPremiumVisualPolicy.ResolveRevealMarginPx(lay.HairW)
            Dim safeLeft As Single = lay.ViewportR.Left + revealMarginPx
            Dim safeRight As Single = lay.ViewportR.Right - revealMarginPx

            If itemOuter.Left < safeLeft Then
                desired -= (safeLeft - itemOuter.Left)
            ElseIf itemOuter.Right > safeRight Then
                desired += (itemOuter.Right - safeRight)
            End If

            Return Clamp(desired, 0.0F, lay.MaxScrollOffset)
        End Function

        Private Shared Function Intersect(a As SKRect, b As SKRect) As SKRect
            Dim l As Single = Math.Max(a.Left, b.Left)
            Dim t As Single = Math.Max(a.Top, b.Top)
            Dim r As Single = Math.Min(a.Right, b.Right)
            Dim bt As Single = Math.Min(a.Bottom, b.Bottom)

            If r <= l OrElse bt <= t Then
                Return SKRect.Empty
            End If

            Return New SKRect(l, t, r, bt)
        End Function

        Private Shared Function Clamp(value As Single, minValue As Single, maxValue As Single) As Single
            If value < minValue Then Return minValue
            If value > maxValue Then Return maxValue
            Return value
        End Function

    End Class

End Namespace