Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Official Nexamas UI menu bar control. It presents top-level application menus while
    ''' delegating command model construction to MASDropDownMenuBuilder and popup lifetime to
    ''' the shell-owned MASMenuBarDropDown route. The dropdown renderer is intentionally
    ''' isolated from the general DropDownMenu/ContextMenu renderer so MenuBar palette changes
    ''' cannot leak into non-shell menus.
    ''' </summary>
    Public NotInheritable Class MASMenuBar
        Inherits Nexamas.UI.Controls.MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASMenuBar))

        Private Const OverflowHitIndex As Integer = -2
        Private Const OverflowText As String = "More"
        Private Const TextFitGuardPx As Single = 2.0F

        Private NotInheritable Class MenuEntry
            Friend Sub New(text As String,
                           configure As Action(Of MASDropDownMenuBuilder),
                           commandHandler As Action(Of String))
                Me.Text = NormalizeMenuText(text)
                Me.Configure = configure
                Me.CommandHandler = commandHandler
                Me.BoundsPx = SKRect.Empty
            End Sub

            Friend Property Text As String
            Friend Property Configure As Action(Of MASDropDownMenuBuilder)
            Friend Property CommandHandler As Action(Of String)
            Friend Property BoundsPx As SKRect
        End Class

        Private NotInheritable Class MenuItemMetric
            Friend Sub New(index As Integer,
                           textWidthPx As Single,
                           naturalWidthPx As Single,
                           compactWidthPx As Single)
                Me.Index = index
                Me.TextWidthPx = Math.Max(1.0F, textWidthPx)
                Me.NaturalWidthPx = Math.Max(1.0F, naturalWidthPx)
                Me.CompactWidthPx = Math.Max(1.0F, compactWidthPx)
            End Sub

            Friend ReadOnly Property Index As Integer
            Friend ReadOnly Property TextWidthPx As Single
            Friend ReadOnly Property NaturalWidthPx As Single
            Friend ReadOnly Property CompactWidthPx As Single

            Friend Function ResolveWidth(useCompact As Boolean) As Single
                Return If(useCompact, CompactWidthPx, NaturalWidthPx)
            End Function
        End Class

        Private NotInheritable Class MenuRenderSlot
            Friend Sub New(index As Integer,
                           boundsPx As SKRect,
                           paddingPx As Single)
                Me.Index = index
                Me.BoundsPx = boundsPx
                Me.PaddingPx = Math.Max(0.0F, paddingPx)
            End Sub

            Friend ReadOnly Property Index As Integer
            Friend ReadOnly Property BoundsPx As SKRect
            Friend ReadOnly Property PaddingPx As Single
        End Class

        Private ReadOnly _menus As New List(Of MenuEntry)()
        Private ReadOnly _overflowMenuIndices As New List(Of Integer)()
        Private _selectedIndex As Integer = -1
        Private _hoverIndex As Integer = -1
        Private _pressedIndex As Integer = -1
        Private _lastRenderedRtl As Boolean
        Private _overflowBoundsPx As SKRect = SKRect.Empty
        Private _overflowCommandOwnerById As New Dictionary(Of String, MenuEntry)(StringComparer.OrdinalIgnoreCase)
        Private _globalCommandHandler As Action(Of String)
        Private _dropDownPresenter As Action(Of SKRect, Action(Of MASDropDownMenuBuilder), Action(Of String), Action)
        Private _openDropDownSessionId As Long
        Private _hasOpenDropDown As Boolean
        ' Tracks the menu-bar pointer navigation session independently from visual focus.
        ' The session is armed by the first click/open and remains active until the
        ' dedicated dropdown close callback completes the current session. This lets
        ' top-level menu gates switch by hover even when FloatRuntime focus/hover
        ' ownership has temporarily moved to the floating dropdown surface.
        Private _dropDownSwitchArmed As Boolean
        Private _popupOpenIndex As Integer = -1
        Private _disposedLocal As Boolean

#End Region

#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()
        End Sub

        Public Shared Function Create() As MASMenuBar
            Return New MASMenuBar()
        End Function

#End Region

#Region "Public API"

        Public Event SelectedIndexChanged As EventHandler

        Public ReadOnly Property MenuCount As Integer
            Get
                Return _menus.Count
            End Get
        End Property

        Public Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
            Set(value As Integer)
                SetSelectedIndexInternal(value, True)
            End Set
        End Property

        Public Function AddMenu(text As String,
                                configure As Action(Of MASDropDownMenuBuilder),
                                Optional commandHandler As Action(Of String) = Nothing) As MASMenuBar
            If configure Is Nothing Then Throw New ArgumentNullException(NameOf(configure))

            Dim normalized As String = NormalizeMenuText(text)
            If normalized.Length = 0 Then Return Me

            _menus.Add(New MenuEntry(normalized, configure, commandHandler))
            NormalizeSelectionAfterCollectionChange()
            ResetPointerState()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASMenuBar.AddMenu")
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithMenu(text As String,
                                 configure As Action(Of MASDropDownMenuBuilder),
                                 Optional commandHandler As Action(Of String) = Nothing) As MASMenuBar
            Return AddMenu(text, configure, commandHandler)
        End Function

        Public Function AddAction(menuText As String,
                                  itemText As String,
                                  commandId As String,
                                  Optional handler As Action = Nothing,
                                  Optional shortcutText As String = "",
                                  Optional enabled As Boolean = True,
                                  Optional intent As MASDropDownMenuItemIntent = MASDropDownMenuItemIntent.Default) As MASMenuBar
            Dim normalizedMenuText As String = NormalizeMenuText(menuText)
            If normalizedMenuText.Length = 0 Then Return Me

            Dim entry As MenuEntry = FindMenuEntry(normalizedMenuText)
            If entry Is Nothing Then
                entry = New MenuEntry(normalizedMenuText, Sub(menu As MASDropDownMenuBuilder)
                                                          End Sub, Nothing)
                _menus.Add(entry)
            End If

            Dim previousConfigure As Action(Of MASDropDownMenuBuilder) = entry.Configure
            entry.Configure =
                Sub(menu As MASDropDownMenuBuilder)
                    previousConfigure?.Invoke(menu)
                    menu.AddAction(
                        text:=itemText,
                        commandId:=commandId,
                        handler:=handler,
                        enabled:=enabled,
                        shortcutText:=shortcutText,
                        intent:=intent)
                End Sub

            NormalizeSelectionAfterCollectionChange()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASMenuBar.AddAction")
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearMenus() As MASMenuBar
            If _menus.Count = 0 Then Return Me

            _menus.Clear()
            _selectedIndex = -1
            _popupOpenIndex = -1
            ResetPointerState()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASMenuBar.ClearMenus")
            InvalidateVisual()
            RaiseSelectedIndexChangedSafe()
            Return Me
        End Function

        Public Function GetMenuText(index As Integer) As String
            If index < 0 OrElse index >= _menus.Count Then Return String.Empty
            Return _menus(index).Text
        End Function

        Public Function OnCommand(handler As Action(Of String)) As MASMenuBar
            _globalCommandHandler = handler
            Return Me
        End Function

        Public Sub ShowSelectedMenu()
            ShowMenuAtIndex(_selectedIndex)
        End Sub

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASMenuBar
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region

#Region "Internal shell binding"

        Friend Sub BindDropDownPresenterInternal(presenter As Action(Of SKRect, Action(Of MASDropDownMenuBuilder), Action(Of String), Action))
            _dropDownPresenter = presenter
        End Sub

#End Region

#Region "Layout"

        Friend Function GetPreferredLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetPreferredLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).DesiredSize
        End Function

        Friend Function GetMinLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetMinLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).MinSize
        End Function

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim profileNeutralHeight As Single = MASIntrinsicControlSizeMetrics.ResolveMenuBarProfileNeutralHeight(safeContext.SizeProfile)
            Dim desiredWidth As Single = Math.Max(MenuTokens.MenuBarMinWidthDip, Math.Min(MenuTokens.MenuBarMaxWidthDip, MeasureMenusWidthDip(safeContext)))
            Dim desired As New SKSize(desiredWidth, profileNeutralHeight)
            Dim minimum As New SKSize(MenuTokens.MenuBarMinWidthDip, Math.Min(MenuTokens.MenuBarMinHeightDip, profileNeutralHeight))
            Dim contentInset As New MASLayoutInset(MenuTokens.MenuBarPaddingHorizontalDip,
                                                   MenuTokens.MenuBarPaddingVerticalDip,
                                                   MenuTokens.MenuBarPaddingHorizontalDip,
                                                   MenuTokens.MenuBarPaddingVerticalDip)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, profileNeutralHeight),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=contentInset,
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        End Function

        Private Function MeasureMenusWidthDip(context As MASSizeContext) As Single
            Dim width As Single = MenuTokens.MenuBarPaddingHorizontalDip * 2.0F

            If _menus.Count = 0 Then
                Return width + MenuTokens.MenuBarEmptyWidthDip
            End If

            For Each entry As MenuEntry In _menus
                Dim textSize As SKSize = MASLayoutTextMeasureGateway.Measure(
                    If(context Is Nothing, Nothing, context.IntegrationContext),
                    entry.Text,
                    MASTypography.MASTextStyle.Small,
                    Single.PositiveInfinity,
                    allowWrap:=False).Size

                width += Math.Max(MenuTokens.MenuBarItemMinWidthDip,
                                  textSize.Width + (MenuTokens.MenuBarItemPaddingHorizontalDip * 2.0F))
                width += MenuTokens.MenuBarItemGapDip
            Next

            If _menus.Count > 0 Then width -= MenuTokens.MenuBarItemGapDip
            Return width
        End Function

        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return MASIntrinsicControlSizeMetrics.EnsureContext(Nothing)
            Return context.ToSizeContext()
        End Function

#End Region

#Region "Rendering"

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return

            Dim pixelDpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, pixelDpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim plan As MASMenuBarLayoutPlan = MASMenuBarLayoutPlan.Create(ctx, bounds, SizeIntent)
            Dim dpi As Single = plan.MetricDpi
            Dim surfaceRadius As Single = ResolveMenuBarSurfaceCornerRadiusPx(dpi)
            Dim menuBarPalette As MASMenuBarPaletteTheme = ResolveMenuBarPalette(ctx)

            Dim surfaceMaterialDrawn As Boolean = MASSurfaceMaterialComponentGateway.TryDrawAuthoredMaterialSurface(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=bounds,
                materialKey:=MASSurfaceMaterialIds.MenuBarFlat,
                dpi:=dpi,
                cornerRadiusPx:=surfaceRadius,
                surfaceStrengthLevel:=3,
                surfaceRole:=MASSurfaceRole.ApplicationChrome)

            If Not surfaceMaterialDrawn Then
                DrawMenuBarStraightSurface(canvas, bounds, dpi, menuBarPalette)
            End If

            Dim content As SKRect = plan.ContentRectPx

            _lastRenderedRtl = ResolveRightToLeft()
            DrawMenuItems(canvas, ctx, content, dpi, _lastRenderedRtl, menuBarPalette)
        End Sub

        Private Shared Function ResolveMenuBarSurfaceCornerRadiusPx(dpi As Single) As Single
            Return 0.0F
        End Function

        Private Shared Sub DrawMenuBarStraightSurface(canvas As SKCanvas,
                                                      bounds As SKRect,
                                                      dpi As Single,
                                                      palette As MASMenuBarPaletteTheme)
            If canvas Is Nothing Then Return
            If bounds.IsEmpty OrElse bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Using shader As SKShader = SKShader.CreateLinearGradient(
                    New SKPoint(bounds.Left, bounds.Top),
                    New SKPoint(bounds.Left, bounds.Bottom),
                    New SKColor() {palette.BarTop, palette.BarMid, palette.BarBottom},
                    New Single() {0.0F, 0.46F, 1.0F},
                    SKShaderTileMode.Clamp),
                  surfacePaint As New SKPaint With {
                    .IsAntialias = False,
                    .IsDither = True,
                    .Style = SKPaintStyle.Fill,
                    .Shader = shader
                  }
                canvas.DrawRect(bounds, surfacePaint)
            End Using

            If palette.BarSeparator.Alpha > 0 Then
                Using separatorPaint As New SKPaint With {
                    .IsAntialias = False,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = Math.Max(1.0F, MenuTokens.MenuBarStrokeDip * dpi),
                    .Color = palette.BarSeparator
                }
                    canvas.DrawLine(bounds.Left, bounds.Bottom - 0.5F, bounds.Right, bounds.Bottom - 0.5F, separatorPaint)
                End Using
            End If
        End Sub

        Private Sub DrawMenuItems(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  content As SKRect,
                                  dpi As Single,
                                  isRtl As Boolean,
                                  palette As MASMenuBarPaletteTheme)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            ResetRenderedMenuBounds()

            If _menus.Count = 0 Then
                DrawEmptyText(canvas, ctx, content, dpi, palette)
                Return
            End If

            Dim metrics As List(Of MenuItemMetric) = MeasureMenuItemMetrics(ctx, dpi)
            Dim slots As List(Of MenuRenderSlot) = BuildMenuRenderSlots(ctx, content, dpi, isRtl, metrics)

            For Each slot As MenuRenderSlot In slots
                If slot Is Nothing OrElse slot.BoundsPx.Width <= 1.0F OrElse slot.BoundsPx.Height <= 1.0F Then Continue For

                If slot.Index = OverflowHitIndex Then
                    _overflowBoundsPx = slot.BoundsPx
                    DrawOverflowMenuItem(canvas, ctx, slot.BoundsPx, dpi, slot.PaddingPx, palette)
                ElseIf slot.Index >= 0 AndAlso slot.Index < _menus.Count Then
                    _menus(slot.Index).BoundsPx = slot.BoundsPx
                    DrawMenuItem(canvas, ctx, slot.BoundsPx, slot.Index, dpi, slot.PaddingPx, palette)
                End If
            Next
        End Sub

        Private Function MeasureMenuItemMetrics(ctx As MASThemeContext,
                                                dpi As Single) As List(Of MenuItemMetric)
            Dim result As New List(Of MenuItemMetric)(_menus.Count)
            Dim normalPaddingPx As Single = MenuTokens.MenuBarItemPaddingHorizontalDip * dpi
            Dim compactPaddingPx As Single = ResolveCompactItemPaddingPx(dpi)
            Dim minWidthPx As Single = MenuTokens.MenuBarItemMinWidthDip * dpi

            For i As Integer = 0 To _menus.Count - 1
                Dim textWidth As Single = MeasureTextWidthPx(ctx, _menus(i).Text)
                Dim naturalWidth As Single = Math.Max(minWidthPx, textWidth + (normalPaddingPx * 2.0F) + TextFitGuardPx)
                Dim compactWidth As Single = Math.Max(minWidthPx, textWidth + (compactPaddingPx * 2.0F) + TextFitGuardPx)
                result.Add(New MenuItemMetric(i, textWidth, naturalWidth, compactWidth))
            Next

            Return result
        End Function

        Private Function BuildMenuRenderSlots(ctx As MASThemeContext,
                                              content As SKRect,
                                              dpi As Single,
                                              isRtl As Boolean,
                                              metrics As List(Of MenuItemMetric)) As List(Of MenuRenderSlot)
            Dim visualOrder As List(Of Integer) = BuildVisualOrder(isRtl)

            Dim normalPaddingPx As Single = MenuTokens.MenuBarItemPaddingHorizontalDip * dpi
            Dim normalGapPx As Single = MenuTokens.MenuBarItemGapDip * dpi
            Dim fullSlots As List(Of MenuRenderSlot) = TryCreateDirectSlots(
                content:=content,
                visualOrder:=visualOrder,
                metrics:=metrics,
                useCompact:=False,
                paddingPx:=normalPaddingPx,
                gapPx:=normalGapPx,
                isRtl:=isRtl)

            If fullSlots IsNot Nothing Then Return fullSlots

            Dim compactPaddingPx As Single = ResolveCompactItemPaddingPx(dpi)
            Dim compactGapPx As Single = ResolveCompactGapPx(dpi)
            Dim compactSlots As List(Of MenuRenderSlot) = TryCreateDirectSlots(
                content:=content,
                visualOrder:=visualOrder,
                metrics:=metrics,
                useCompact:=True,
                paddingPx:=compactPaddingPx,
                gapPx:=compactGapPx,
                isRtl:=isRtl)

            If compactSlots IsNot Nothing Then Return compactSlots

            Return CreateOverflowSlots(
                ctx:=ctx,
                content:=content,
                dpi:=dpi,
                visualOrder:=visualOrder,
                metrics:=metrics,
                isRtl:=isRtl,
                paddingPx:=compactPaddingPx,
                gapPx:=compactGapPx)
        End Function

        Private Function TryCreateDirectSlots(content As SKRect,
                                              visualOrder As List(Of Integer),
                                              metrics As List(Of MenuItemMetric),
                                              useCompact As Boolean,
                                              paddingPx As Single,
                                              gapPx As Single,
                                              isRtl As Boolean) As List(Of MenuRenderSlot)
            Dim totalWidth As Single = ResolveTotalWidth(visualOrder, metrics, useCompact, gapPx)
            If totalWidth > content.Width + 0.25F Then Return Nothing

            Return PlaceSlots(content, visualOrder, metrics, useCompact, paddingPx, gapPx, isRtl, includeOverflow:=False, overflowWidthPx:=0.0F, overflowPaddingPx:=0.0F)
        End Function

        Private Function CreateOverflowSlots(ctx As MASThemeContext,
                                             content As SKRect,
                                             dpi As Single,
                                             visualOrder As List(Of Integer),
                                             metrics As List(Of MenuItemMetric),
                                             isRtl As Boolean,
                                             paddingPx As Single,
                                             gapPx As Single) As List(Of MenuRenderSlot)
            _overflowMenuIndices.Clear()

            Dim overflowMetric As MenuItemMetric = MeasureOverflowMetric(ctx, dpi)
            Dim visibleOrder As New List(Of Integer)(visualOrder)
            Dim hidden As New HashSet(Of Integer)()
            Dim protectedIndex As Integer = NormalizeIndex(_selectedIndex)
            Dim totalWidth As Single = ResolveTotalWidth(visibleOrder, metrics, useCompact:=True, gapPx:=gapPx) + gapPx + overflowMetric.CompactWidthPx

            While visibleOrder.Count > 0 AndAlso totalWidth > content.Width + 0.25F
                Dim removeAt As Integer = FindOverflowHideCandidatePosition(visibleOrder, protectedIndex)
                If removeAt < 0 Then Exit While

                hidden.Add(visibleOrder(removeAt))
                visibleOrder.RemoveAt(removeAt)
                totalWidth = ResolveTotalWidth(visibleOrder, metrics, useCompact:=True, gapPx:=gapPx) + If(visibleOrder.Count > 0, gapPx, 0.0F) + overflowMetric.CompactWidthPx
            End While

            If hidden.Count = 0 Then
                Return PlaceSlots(content, visibleOrder, metrics, True, paddingPx, gapPx, isRtl, includeOverflow:=False, overflowWidthPx:=0.0F, overflowPaddingPx:=0.0F)
            End If

            For i As Integer = 0 To _menus.Count - 1
                If hidden.Contains(i) Then _overflowMenuIndices.Add(i)
            Next

            If visibleOrder.Count = 0 Then
                Dim overflowOnly As New List(Of MenuRenderSlot)()
                Dim overflowRect As SKRect = ResolveOverflowOnlyRect(content, overflowMetric.CompactWidthPx, isRtl)
                overflowOnly.Add(New MenuRenderSlot(OverflowHitIndex, overflowRect, ResolveOverflowPaddingPx(dpi)))
                Return overflowOnly
            End If

            Return PlaceSlots(
                content:=content,
                visualOrder:=visibleOrder,
                metrics:=metrics,
                useCompact:=True,
                paddingPx:=paddingPx,
                gapPx:=gapPx,
                isRtl:=isRtl,
                includeOverflow:=True,
                overflowWidthPx:=overflowMetric.CompactWidthPx,
                overflowPaddingPx:=ResolveOverflowPaddingPx(dpi))
        End Function

        Private Function PlaceSlots(content As SKRect,
                                    visualOrder As List(Of Integer),
                                    metrics As List(Of MenuItemMetric),
                                    useCompact As Boolean,
                                    paddingPx As Single,
                                    gapPx As Single,
                                    isRtl As Boolean,
                                    includeOverflow As Boolean,
                                    overflowWidthPx As Single,
                                    overflowPaddingPx As Single) As List(Of MenuRenderSlot)
            Dim slots As New List(Of MenuRenderSlot)()
            Dim x As Single = If(isRtl, content.Right, content.Left)
            Dim y As Single = content.Top
            Dim itemHeight As Single = Math.Max(1.0F, content.Height)

            For Each menuIndex As Integer In visualOrder
                Dim metric As MenuItemMetric = ResolveMetric(metrics, menuIndex)
                If metric Is Nothing Then Continue For

                Dim width As Single = metric.ResolveWidth(useCompact)
                If isRtl Then x -= width

                Dim rect As New SKRect(x, y, x + width, y + itemHeight)
                slots.Add(New MenuRenderSlot(menuIndex, rect, paddingPx))

                If isRtl Then
                    x -= gapPx
                Else
                    x += width + gapPx
                End If
            Next

            If includeOverflow Then
                If visualOrder.Count > 0 Then
                    If isRtl Then
                        x += gapPx
                        x -= overflowWidthPx + gapPx
                    Else
                        x -= gapPx
                        x += gapPx
                    End If
                End If

                Dim overflowRect As New SKRect(x, y, x + overflowWidthPx, y + itemHeight)
                slots.Add(New MenuRenderSlot(OverflowHitIndex, overflowRect, overflowPaddingPx))
            End If

            Return slots
        End Function

        Private Function ResolveOverflowOnlyRect(content As SKRect,
                                                 overflowWidthPx As Single,
                                                 isRtl As Boolean) As SKRect
            Dim width As Single = Math.Min(content.Width, Math.Max(1.0F, overflowWidthPx))
            If isRtl Then
                Return New SKRect(content.Left, content.Top, content.Left + width, content.Bottom)
            End If

            Return New SKRect(content.Right - width, content.Top, content.Right, content.Bottom)
        End Function

        Private Function BuildVisualOrder(isRtl As Boolean) As List(Of Integer)
            Dim order As New List(Of Integer)(_menus.Count)
            For i As Integer = 0 To _menus.Count - 1
                order.Add(i)
            Next

            If isRtl Then order.Reverse()
            Return order
        End Function

        Private Shared Function ResolveTotalWidth(order As List(Of Integer),
                                                  metrics As List(Of MenuItemMetric),
                                                  useCompact As Boolean,
                                                  gapPx As Single) As Single
            If order Is Nothing OrElse order.Count = 0 Then Return 0.0F

            Dim width As Single = 0.0F
            For Each menuIndex As Integer In order
                Dim metric As MenuItemMetric = ResolveMetric(metrics, menuIndex)
                If metric IsNot Nothing Then width += metric.ResolveWidth(useCompact)
            Next

            width += Math.Max(0, order.Count - 1) * gapPx
            Return width
        End Function

        Private Shared Function ResolveMetric(metrics As List(Of MenuItemMetric),
                                              index As Integer) As MenuItemMetric
            If metrics Is Nothing Then Return Nothing

            For Each metric As MenuItemMetric In metrics
                If metric IsNot Nothing AndAlso metric.Index = index Then Return metric
            Next

            Return Nothing
        End Function

        Private Shared Function FindOverflowHideCandidatePosition(order As List(Of Integer),
                                                                  protectedIndex As Integer) As Integer
            If order Is Nothing OrElse order.Count = 0 Then Return -1

            For i As Integer = order.Count - 1 To 0 Step -1
                If order(i) <> protectedIndex Then Return i
            Next

            Return order.Count - 1
        End Function

        Private Function MeasureOverflowMetric(ctx As MASThemeContext,
                                               dpi As Single) As MenuItemMetric
            Dim textWidth As Single = MeasureTextWidthPx(ctx, OverflowText)
            Dim paddingPx As Single = ResolveOverflowPaddingPx(dpi)
            Dim minWidthPx As Single = Math.Max(MenuTokens.MenuBarItemMinWidthDip * dpi, 58.0F * dpi)
            Dim width As Single = Math.Max(minWidthPx, textWidth + (paddingPx * 2.0F) + TextFitGuardPx)
            Return New MenuItemMetric(OverflowHitIndex, textWidth, width, width)
        End Function

        Private Shared Function ResolveCompactItemPaddingPx(dpi As Single) As Single
            Return Math.Max(6.0F * dpi, MenuTokens.MenuBarItemPaddingHorizontalDip * dpi * 0.62F)
        End Function

        Private Shared Function ResolveCompactGapPx(dpi As Single) As Single
            Return Math.Max(1.0F, MenuTokens.MenuBarItemGapDip * dpi * 0.45F)
        End Function

        Private Shared Function ResolveOverflowPaddingPx(dpi As Single) As Single
            Return Math.Max(8.0F * dpi, MenuTokens.MenuBarItemPaddingHorizontalDip * dpi * 0.72F)
        End Function

        Private Sub ResetRenderedMenuBounds()
            For Each entry As MenuEntry In _menus
                entry.BoundsPx = SKRect.Empty
            Next

            _overflowBoundsPx = SKRect.Empty
            _overflowMenuIndices.Clear()
            _overflowCommandOwnerById.Clear()
        End Sub

        Private Sub DrawMenuItem(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 rect As SKRect,
                                 menuIndex As Integer,
                                 dpi As Single,
                                 paddingPx As Single,
                                 palette As MASMenuBarPaletteTheme)
            Dim selected As Boolean = (menuIndex = _selectedIndex)
            Dim popupOpen As Boolean = (menuIndex = _popupOpenIndex)
            Dim hovered As Boolean = (menuIndex = _hoverIndex)
            Dim pressed As Boolean = (menuIndex = _pressedIndex)
            DrawMenuItemInteraction(
                canvas:=canvas,
                rect:=rect,
                dpi:=dpi,
                palette:=palette,
                selected:=selected,
                popupOpen:=popupOpen,
                hovered:=hovered,
                pressed:=pressed,
                focused:=HasKeyboardFocus AndAlso selected AndAlso Not popupOpen)

            DrawMenuItemText(canvas, ctx, rect, _menus(menuIndex).Text, dpi, paddingPx, ResolveTextColor(ctx, palette, selected OrElse popupOpen, Enabled))
        End Sub

        Private Sub DrawOverflowMenuItem(canvas As SKCanvas,
                                         ctx As MASThemeContext,
                                         rect As SKRect,
                                         dpi As Single,
                                         paddingPx As Single,
                                         palette As MASMenuBarPaletteTheme)
            Dim popupOpen As Boolean = (_popupOpenIndex = OverflowHitIndex)
            Dim hovered As Boolean = (_hoverIndex = OverflowHitIndex)
            Dim pressed As Boolean = (_pressedIndex = OverflowHitIndex)
            DrawMenuItemInteraction(
                canvas:=canvas,
                rect:=rect,
                dpi:=dpi,
                palette:=palette,
                selected:=False,
                popupOpen:=popupOpen,
                hovered:=hovered,
                pressed:=pressed,
                focused:=False)

            DrawMenuItemText(canvas, ctx, rect, OverflowText, dpi, paddingPx, ResolveTextColor(ctx, palette, popupOpen, Enabled))
        End Sub

        Private Shared Sub DrawMenuItemInteraction(canvas As SKCanvas,
                                                   rect As SKRect,
                                                   dpi As Single,
                                                   palette As MASMenuBarPaletteTheme,
                                                   selected As Boolean,
                                                   popupOpen As Boolean,
                                                   hovered As Boolean,
                                                   pressed As Boolean,
                                                   focused As Boolean)
            If canvas Is Nothing Then Return
            If rect.IsEmpty OrElse rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return
            If Not (selected OrElse popupOpen OrElse hovered OrElse pressed OrElse focused) Then Return

            Dim interactionTheme As IMASInteractionTheme =
                MASMenuBarInteractionPaletteFactory.CreateBarItemPalette(palette, popupOpen)

            Dim state As MASInteractionState = ResolveMenuItemInteractionState(
                selected:=selected,
                popupOpen:=popupOpen,
                hovered:=hovered,
                pressed:=pressed)

            If state <> MASInteractionState.None Then
                Dim spec As MASVisualInteractionOverlaySpec =
                    MASInteractionResolver.BuildWithPalette(
                        contract:=_contract,
                        state:=state,
                        interactionTheme:=interactionTheme)

                MASSurfaceInteractionRing.DrawOverlay(
                    canvas:=canvas,
                    baseRectPx:=rect,
                    contract:=_contract,
                    spec:=spec,
                    dpi:=dpi,
                    fillInsetDipOverride:=MenuTokens.MenuBarItemInteractionFillInsetDip,
                    ringInsetDipOverride:=MenuTokens.MenuBarItemInteractionRingInsetDip)
            End If

            If focused Then
                Dim focusSpec As MASVisualInteractionOverlaySpec =
                    MASInteractionResolver.BuildWithPalette(
                        contract:=_contract,
                        state:=MASInteractionState.Focused,
                        interactionTheme:=interactionTheme)

                MASSurfaceInteractionRing.DrawOverlay(
                    canvas:=canvas,
                    baseRectPx:=rect,
                    contract:=_contract,
                    spec:=focusSpec,
                    dpi:=dpi,
                    fillInsetDipOverride:=MenuTokens.MenuBarItemInteractionFillInsetDip,
                    ringInsetDipOverride:=MenuTokens.MenuBarItemInteractionRingInsetDip,
                    ringStrokeDipOverride:=MenuTokens.MenuBarFocusStrokeDip)
            End If
        End Sub

        Private Shared Function ResolveMenuItemInteractionState(selected As Boolean,
                                                                popupOpen As Boolean,
                                                                hovered As Boolean,
                                                                pressed As Boolean) As MASInteractionState
            If pressed Then Return MASInteractionState.Pressed
            If popupOpen Then Return MASInteractionState.Selected
            If selected Then Return MASInteractionState.Selected
            If hovered Then Return MASInteractionState.Hovered
            Return MASInteractionState.None
        End Function

        Private Shared Sub DrawMenuItemText(canvas As SKCanvas,
                                            ctx As MASThemeContext,
                                            rect As SKRect,
                                            text As String,
                                            dpi As Single,
                                            paddingPx As Single,
                                            color As SKColor)
            Dim textRect As SKRect = rect
            textRect.Inflate(-Math.Max(0.0F, paddingPx), 0.0F)

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=text,
                bounds:=textRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Small,
                color:=color,
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

        Private Sub DrawEmptyText(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  content As SKRect,
                                  dpi As Single,
                                  palette As MASMenuBarPaletteTheme)
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:="Menu",
                bounds:=content,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Small,
                color:=palette.ItemMutedText.WithAlpha(CByte(Math.Min(CInt(palette.ItemMutedText.Alpha), CInt(MenuTokens.MenuBarEmptyTextAlpha)))),
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

#End Region

#Region "Input"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim hit As Integer = HitIndex(ptPx)

            If hit = _hoverIndex Then
                If TrySwitchOpenDropDownOnHover(hit) Then
                    InvalidateVisual()
                End If

                Return
            End If

            _hoverIndex = hit

            If TrySwitchOpenDropDownOnHover(hit) Then
                InvalidateVisual()
                Return
            End If

            InvalidateVisual()
        End Sub

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext,
                                                 ptPx As SKPoint,
                                                 button As Integer) As Boolean
            If button <> CInt(MouseButtons.Left) Then Return False

            Dim hit As Integer = HitIndex(ptPx)
            If hit = -1 Then Return False

            RequestFocus()
            _pressedIndex = hit
            _hoverIndex = hit

            ' Pointer activation is transient. Do not promote a mouse press to SelectedIndex;
            ' the open popup state is represented by _popupOpenIndex and is cleared when the
            ' dropdown closes. Keeping SelectedIndex out of pointer interaction prevents the
            ' MenuBar from looking focused/selected after a click.
            InvalidateVisual()
            Return True
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean
            If button <> CInt(MouseButtons.Left) Then Return False

            Dim pressed As Integer = _pressedIndex
            _pressedIndex = -1
            Dim released As Integer = HitIndex(ptPx)

            If pressed = released Then
                If released = OverflowHitIndex Then
                    ShowOverflowMenu()
                    InvalidateVisual()
                    Return True
                End If

                If released >= 0 Then
                    ShowMenuAtIndex(released)
                    InvalidateVisual()
                    Return True
                End If
            End If

            If pressed <> -1 Then
                If Not _hasOpenDropDown Then
                    ClearTransientMenuStateInternal(clearKeyboardFocus:=False)
                Else
                    InvalidateVisual()
                End If

                Return True
            End If

            Return False
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _hasOpenDropDown Then
                If _hoverIndex = -1 AndAlso _pressedIndex = -1 Then Return
                ResetPointerState()
                InvalidateVisual()
                Return
            End If

            ClearTransientMenuStateInternal(clearKeyboardFocus:=False)
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            If _hasOpenDropDown Then
                ' A menu-bar dropdown is a shell-owned continuation of the same navigation gesture.
                ' Opening it can move keyboard focus to the floating surface, but that must not end
                ' the menu-bar dropdown session; otherwise hover-switching between top-level gates
                ' stops while the original popup remains visible. Keep the popup session alive and
                ' clear only transient press/focus visuals. The real session is completed by the
                ' dropdown closed callback.
                Dim changed As Boolean = False

                If _pressedIndex <> -1 Then
                    _pressedIndex = -1
                    changed = True
                End If

                If HasKeyboardFocus Then
                    SetKeyboardFocusInternal(False)
                    changed = True
                End If

                If changed Then InvalidateVisual()
                Return
            End If

            ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If _menus.Count <= 0 Then Return False

            Select Case keyCode
                Case Keys.Left
                    MoveSelection(If(_lastRenderedRtl, 1, -1))
                    Return True

                Case Keys.Right
                    MoveSelection(If(_lastRenderedRtl, -1, 1))
                    Return True

                Case Keys.Home
                    SetSelectedIndexInternal(0, True)
                    Return True

                Case Keys.End
                    SetSelectedIndexInternal(_menus.Count - 1, True)
                    Return True

                Case Keys.Enter, Keys.Space
                    If _selectedIndex < 0 Then Return False
                    ShowMenuAtIndex(_selectedIndex)
                    Return True
            End Select

            Return False
        End Function

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return Visible AndAlso Enabled AndAlso _menus.Count > 0
        End Function

        Protected Overrides Function WantsPointerFocus() As Boolean
            Return WantsKeyboardFocus()
        End Function

#End Region

#Region "Helpers"

        Private Function HitIndex(ptPx As SKPoint) As Integer
            If _overflowBoundsPx.Width > 1.0F AndAlso _overflowBoundsPx.Height > 1.0F AndAlso _overflowBoundsPx.Contains(ptPx.X, ptPx.Y) Then
                Return OverflowHitIndex
            End If

            For i As Integer = 0 To _menus.Count - 1
                Dim rect As SKRect = _menus(i).BoundsPx
                If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Continue For
                If rect.Contains(ptPx.X, ptPx.Y) Then Return i
            Next

            Return -1
        End Function

        Private Function TrySwitchOpenDropDownOnHover(hit As Integer) As Boolean
            If Not IsDropDownSwitchSessionActive() Then Return False
            If _pressedIndex <> -1 Then Return False
            If hit = -1 Then Return False
            If hit = _popupOpenIndex Then Return False

            If hit = OverflowHitIndex Then
                ShowOverflowMenu()
                Return True
            End If

            If hit >= 0 AndAlso hit < _menus.Count Then
                ShowMenuAtIndex(hit)
                Return True
            End If

            Return False
        End Function

        Private Function IsDropDownSwitchSessionActive() As Boolean
            Return _hasOpenDropDown OrElse _dropDownSwitchArmed
        End Function

        Private Sub ShowMenuAtIndex(index As Integer)
            If index < 0 OrElse index >= _menus.Count Then Return

            If _dropDownPresenter Is Nothing Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            Dim entry As MenuEntry = _menus(index)
            If entry Is Nothing OrElse entry.Configure Is Nothing Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            If entry.BoundsPx.IsEmpty OrElse entry.BoundsPx.Width <= 1.0F OrElse entry.BoundsPx.Height <= 1.0F Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            Dim sessionId As Long = BeginDropDownSession(index)

            Try
                _dropDownPresenter.Invoke(
                    entry.BoundsPx,
                    entry.Configure,
                    Sub(commandId As String)
                        InvokeCommandHandlers(entry, commandId)
                    End Sub,
                    Sub()
                        CompleteDropDownSession(sessionId)
                    End Sub)
            Catch masCaughtException1 As Exception
                CompleteDropDownSession(sessionId)
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "MASMenuBar.ShowMenuAtIndex")
            End Try
        End Sub

        Private Sub ShowOverflowMenu()
            If _dropDownPresenter Is Nothing Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            If _overflowBoundsPx.IsEmpty OrElse _overflowBoundsPx.Width <= 1.0F OrElse _overflowBoundsPx.Height <= 1.0F Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            If _overflowMenuIndices.Count = 0 Then
                ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
                Return
            End If

            Dim hiddenIndices As New List(Of Integer)(_overflowMenuIndices)
            BuildOverflowCommandOwnershipSnapshot(hiddenIndices)

            Dim sessionId As Long = BeginDropDownSession(OverflowHitIndex)

            Try
                _dropDownPresenter.Invoke(
                    _overflowBoundsPx,
                    Sub(menu As MASDropDownMenuBuilder)
                        BuildOverflowMenu(menu, hiddenIndices)
                    End Sub,
                    Sub(commandId As String)
                        InvokeOverflowCommandHandlers(commandId)
                    End Sub,
                    Sub()
                        CompleteDropDownSession(sessionId)
                    End Sub)
            Catch masCaughtExceptionOverflow As Exception
                CompleteDropDownSession(sessionId)
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtExceptionOverflow, "MASMenuBar.ShowOverflowMenu")
            End Try
        End Sub

        Private Sub BuildOverflowMenu(menu As MASDropDownMenuBuilder,
                                      hiddenIndices As List(Of Integer))
            If menu Is Nothing OrElse hiddenIndices Is Nothing Then Return

            For Each index As Integer In hiddenIndices
                If index < 0 OrElse index >= _menus.Count Then Continue For

                Dim entry As MenuEntry = _menus(index)
                If entry Is Nothing OrElse entry.Configure Is Nothing Then Continue For

                Dim capturedEntry As MenuEntry = entry
                menu.AddSubMenu(
                    capturedEntry.Text,
                    Sub(child As MASDropDownMenuBuilder)
                        capturedEntry.Configure.Invoke(child)
                    End Sub)
            Next
        End Sub

        Private Sub BuildOverflowCommandOwnershipSnapshot(hiddenIndices As List(Of Integer))
            _overflowCommandOwnerById.Clear()
            If hiddenIndices Is Nothing Then Return

            For Each index As Integer In hiddenIndices
                If index < 0 OrElse index >= _menus.Count Then Continue For

                Dim entry As MenuEntry = _menus(index)
                If entry Is Nothing OrElse entry.Configure Is Nothing Then Continue For

                Try
                    Dim builder As MASDropDownMenuBuilder = MASDropDownMenuBuilder.Create()
                    entry.Configure.Invoke(builder)
                    Dim model As MASDropDownMenuModel = builder.Build()
                    AddOverflowCommandOwners(model.Root, entry)
                Catch masCaughtExceptionOverflowMap As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtExceptionOverflowMap, "MASMenuBar.OverflowCommandOwnership")
                End Try
            Next
        End Sub

        Private Sub AddOverflowCommandOwners(node As MASDropDownMenuNode,
                                             owner As MenuEntry)
            If node Is Nothing OrElse owner Is Nothing Then Return

            If node.Kind = MASDropDownMenuNodeKind.ActionItem Then
                Dim id As String = If(node.CommandId, String.Empty).Trim()
                If id.Length > 0 AndAlso Not _overflowCommandOwnerById.ContainsKey(id) Then
                    _overflowCommandOwnerById(id) = owner
                End If
            End If

            If node.Children Is Nothing Then Return

            For Each child As MASDropDownMenuNode In node.Children
                AddOverflowCommandOwners(child, owner)
            Next
        End Sub

        Private Sub InvokeOverflowCommandHandlers(commandId As String)
            Dim id As String = If(commandId, String.Empty).Trim()
            If id.Length = 0 Then Return

            Dim owner As MenuEntry = Nothing
            If _overflowCommandOwnerById.TryGetValue(id, owner) AndAlso owner IsNot Nothing Then
                InvokeCommandHandlers(owner, id)
                Return
            End If

            Try
                If _globalCommandHandler IsNot Nothing Then _globalCommandHandler.Invoke(id)
            Catch masCaughtExceptionOverflowCommand As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtExceptionOverflowCommand, "MASMenuBar.OverflowCommandHandler")
            End Try
        End Sub

        Private Sub InvokeCommandHandlers(entry As MenuEntry,
                                          commandId As String)
            Dim id As String = If(commandId, String.Empty).Trim()
            If id.Length = 0 Then Return

            Try
                If entry IsNot Nothing AndAlso entry.CommandHandler IsNot Nothing Then
                    entry.CommandHandler.Invoke(id)
                End If

                If _globalCommandHandler IsNot Nothing Then
                    _globalCommandHandler.Invoke(id)
                End If
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2, "MASMenuBar.CommandHandler")
            End Try
        End Sub

        Private Sub MoveSelection(delta As Integer)
            If _menus.Count <= 0 Then Return

            Dim current As Integer
            If _selectedIndex < 0 Then
                current = If(delta >= 0, -1, _menus.Count)
            Else
                current = NormalizeIndex(_selectedIndex)
            End If

            Dim nextIndex As Integer = current + delta
            If nextIndex < 0 Then nextIndex = 0
            If nextIndex >= _menus.Count Then nextIndex = _menus.Count - 1
            SetSelectedIndexInternal(nextIndex, True)
        End Sub

        Private Sub SetSelectedIndexInternal(value As Integer,
                                             raiseChanged As Boolean)
            Dim normalized As Integer = NormalizeIndex(value)
            If _selectedIndex = normalized Then Return

            _selectedIndex = normalized
            If raiseChanged Then RaiseSelectedIndexChangedSafe()
            InvalidateVisual()
        End Sub

        Private Function NormalizeIndex(value As Integer) As Integer
            If _menus.Count <= 0 Then Return -1
            If value < 0 Then Return -1
            If value >= _menus.Count Then Return _menus.Count - 1
            Return value
        End Function

        Private Sub NormalizeSelectionAfterCollectionChange()
            If _menus.Count <= 0 Then
                _selectedIndex = -1
                _popupOpenIndex = -1
                Return
            End If

            If _selectedIndex >= _menus.Count Then _selectedIndex = _menus.Count - 1
            If _popupOpenIndex >= _menus.Count Then _popupOpenIndex = -1
        End Sub

        Private Function BeginDropDownSession(activeIndex As Integer) As Long
            _openDropDownSessionId += 1L
            _hasOpenDropDown = True
            _dropDownSwitchArmed = True
            _popupOpenIndex = activeIndex

            ' Opening a top-level dropdown is a transient popup state, not a persistent
            ' selection. Keyboard navigation may keep SelectedIndex while focused, but
            ' opening with the pointer must not leave a selected/focused pill behind.
            Return _openDropDownSessionId
        End Function

        Private Sub CompleteDropDownSession(sessionId As Long)
            If Not _hasOpenDropDown Then Return
            If sessionId <> _openDropDownSessionId Then Return

            _hasOpenDropDown = False
            _dropDownSwitchArmed = False
            ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
        End Sub

        Friend Sub ClearTransientMenuStateInternal(Optional clearKeyboardFocus As Boolean = True)
            Dim changed As Boolean = False

            If _hoverIndex <> -1 Then
                _hoverIndex = -1
                changed = True
            End If

            If _pressedIndex <> -1 Then
                _pressedIndex = -1
                changed = True
            End If

            If _selectedIndex <> -1 Then
                _selectedIndex = -1
                changed = True
                RaiseSelectedIndexChangedSafe()
            End If

            If _popupOpenIndex <> -1 Then
                _popupOpenIndex = -1
                changed = True
            End If

            If clearKeyboardFocus AndAlso HasKeyboardFocus Then
                SetKeyboardFocusInternal(False)
                changed = True
            End If

            If changed Then InvalidateVisual()
        End Sub

        Private Function FindMenuEntry(text As String) As MenuEntry
            Dim normalized As String = NormalizeMenuText(text)
            If normalized.Length = 0 Then Return Nothing

            For Each entry As MenuEntry In _menus
                If entry IsNot Nothing AndAlso String.Equals(entry.Text, normalized, StringComparison.OrdinalIgnoreCase) Then
                    Return entry
                End If
            Next

            Return Nothing
        End Function

        Private Sub ResetPointerState()
            _hoverIndex = -1
            _pressedIndex = -1
        End Sub

        Private Sub RaiseSelectedIndexChangedSafe()
            Try
                RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException3, "MASMenuBar.SelectedIndexChanged")
            End Try
        End Sub

        Private Function MeasureTextWidthPx(ctx As MASThemeContext,
                                            text As String) As Single
            If ctx Is Nothing OrElse ctx.Typography Is Nothing Then Return Math.Max(1.0F, If(text, String.Empty).Length * 7.0F)

            Dim paint As SKPaint = ctx.Typography.GetPaint(MASTypography.MASTextStyle.Small, ResolvePrimaryTextColor(ctx))
            If paint Is Nothing Then Return Math.Max(1.0F, If(text, String.Empty).Length * 7.0F)
            Return Math.Max(1.0F, MASShapedText.MeasureWidth(If(text, String.Empty), paint))
        End Function

        Private Shared Function NormalizeMenuText(value As String) As String
            Dim normalized As String = If(value, String.Empty).Trim()
            If normalized.Length > MenuTokens.MenuBarMaxTextLength Then normalized = normalized.Substring(0, MenuTokens.MenuBarMaxTextLength)
            Return normalized
        End Function

        Private Shared Function ResolveRightToLeft() As Boolean
            Return Nexamas.UI.Localization.MASLocalization.IsCurrentRightToLeft()
        End Function

        Private Shared Function ResolveSurfaceColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.SurfaceTheme IsNot Nothing Then Return ctx.SurfaceTheme.SurfaceTop
            Return SKColors.White
        End Function

        Private Shared Function ResolvePrimaryTextColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.PrimaryText
            Return Nexamas.UI.Values.Color.Text.Primary
        End Function

        Private Shared Function ResolveMutedTextColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.MutedText
            Return SKColors.Gray
        End Function

        Private Shared Function ResolveAccentColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.BrandTheme IsNot Nothing Then Return ctx.BrandTheme.BrandPrimary
            If ctx IsNot Nothing AndAlso ctx.SemanticTheme IsNot Nothing Then Return ctx.SemanticTheme.Info
            Return SKColors.DodgerBlue
        End Function

        Private Shared Function ResolveMenuBarPalette(ctx As MASThemeContext) As MASMenuBarPaletteTheme
            If ctx IsNot Nothing AndAlso ctx.MenuTheme IsNot Nothing Then
                Return ctx.MenuTheme.MenuBar
            End If

            Dim surface As SKColor = ResolveSurfaceColor(ctx)
            Dim primaryText As SKColor = ResolvePrimaryTextColor(ctx)
            Dim mutedText As SKColor = ResolveMutedTextColor(ctx)
            Dim accent As SKColor = ResolveAccentColor(ctx)

            Return New MASMenuBarPaletteTheme(
                barTop:=surface.WithAlpha(255),
                barMid:=surface.WithAlpha(255),
                barBottom:=surface.WithAlpha(255),
                barBorder:=accent.WithAlpha(MenuTokens.MenuBarBorderAlpha),
                barSeparator:=accent.WithAlpha(MenuTokens.MenuBarBorderAlpha),
                itemText:=primaryText.WithAlpha(MenuTokens.MenuBarTextAlpha),
                itemMutedText:=mutedText.WithAlpha(MenuTokens.MenuBarEmptyTextAlpha),
                itemActiveText:=accent.WithAlpha(MenuTokens.MenuBarTextAlpha),
                itemDisabledText:=mutedText.WithAlpha(MenuTokens.MenuBarDisabledTextAlpha),
                itemHoverFill:=accent.WithAlpha(MenuTokens.MenuBarItemHoverAlpha),
                itemPressedFill:=accent.WithAlpha(MenuTokens.MenuBarItemPressedAlpha),
                itemSelectedFill:=accent.WithAlpha(MenuTokens.MenuBarItemSelectedAlpha),
                itemSelectedIndicator:=accent.WithAlpha(MenuTokens.MenuBarTextAlpha),
                itemPopupOpenFill:=accent.WithAlpha(MenuTokens.MenuBarItemSelectedAlpha),
                focusRing:=accent.WithAlpha(MenuTokens.MenuBarFocusAlpha),
                popupTop:=surface.WithAlpha(255),
                popupMid:=surface.WithAlpha(255),
                popupBottom:=surface.WithAlpha(255),
                popupBorder:=accent.WithAlpha(MenuTokens.MenuBarBorderAlpha),
                popupSeparator:=accent.WithAlpha(MenuTokens.MenuBarBorderAlpha),
                popupText:=primaryText.WithAlpha(MenuTokens.MenuBarTextAlpha),
                popupMutedText:=mutedText.WithAlpha(MenuTokens.MenuBarEmptyTextAlpha),
                popupHoverFill:=accent.WithAlpha(MenuTokens.MenuBarItemHoverAlpha),
                popupSelectedFill:=accent.WithAlpha(MenuTokens.MenuBarItemSelectedAlpha))
        End Function

        Private Shared Function ResolveTextColor(ctx As MASThemeContext,
                                                 palette As MASMenuBarPaletteTheme,
                                                 active As Boolean,
                                                 enabled As Boolean) As SKColor
            If Not enabled Then Return palette.ItemDisabledText
            If active Then Return palette.ItemActiveText
            Return palette.ItemText
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If _disposedLocal Then Return
            _disposedLocal = True
            _hasOpenDropDown = False
            _dropDownSwitchArmed = False
            ClearTransientMenuStateInternal(clearKeyboardFocus:=True)
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
