Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components.DropDownMenu

    ''' <summary>
    ''' Renderer dedicated to MASMenuBar dropdowns. It intentionally does not use
    ''' DropDownMenuRenderer / IMASMenuTheme.ItemSurface so the shell navigation popup can
    ''' consume MASMenuBarPaletteTheme without changing context menus, split buttons,
    ''' data-grid menus, command-overflow menus, or MASMenuButton.
    ''' </summary>
    Friend NotInheritable Class MASMenuBarDropDownRenderer
        Implements IDisposable

        Private ReadOnly _icon As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke,
            .StrokeCap = SKStrokeCap.Round,
            .StrokeJoin = SKStrokeJoin.Round
        }

        Private ReadOnly _shadowPainter As New MASShadowPainter()

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

        Private Shared ReadOnly _scope As MASContractRenderScope =
            MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASDropDownMenuFloatContract)(
                NameOf(MASMenuBarDropDownRenderer),
                _contract,
                MASRenderLayer.Surface,
                MASRenderLayer.FloatingChrome,
                MASRenderLayer.Shadow,
                MASRenderLayer.Text,
                MASRenderLayer.Hover,
                MASRenderLayer.Pressed,
                MASRenderLayer.Focus)

        Private ReadOnly _interactionResolver As New DropDownMenuInteractionResolver()
        Private _disposed As Boolean

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True

            Try
                _icon.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try

            Try
                _shadowPainter.Dispose()
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
            End Try
        End Sub

        Friend Sub Render(canvas As SKCanvas,
                          ctx As MASThemeContext,
                          dpi As Single,
                          m As DropDownMenuState)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing OrElse m Is Nothing Then Return

            _scope.RequireAnyLayer(
                MASRenderLayer.Surface,
                MASRenderLayer.FloatingChrome,
                MASRenderLayer.Shadow,
                MASRenderLayer.Text)

            dpi = PixelSnap.SafeDpi(dpi)
            If m.IsOpen Then DrawPanels(canvas, ctx, dpi, m)
        End Sub

        Private Sub DrawPanels(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               dpi As Single,
                               m As DropDownMenuState)

            If canvas Is Nothing OrElse ctx Is Nothing OrElse m Is Nothing Then Return
            If m.PanelRects Is Nothing OrElse m.PanelRects.Count = 0 Then Return

            Dim palette As MASMenuBarPaletteTheme = ResolvePalette(ctx)
            Dim padX As Single = MenuTokens.DropDownPanelPadX * dpi
            Dim padY As Single = MenuTokens.DropDownPanelPadY * dpi
            Dim rowH As Single = MenuTokens.DropDownRowHeight * dpi
            Dim sepH As Single = MenuTokens.DropDownSeparatorHeight * dpi
            Dim glyphReserve As Single = MenuTokens.DropDownRightGlyphReserve * dpi
            Dim mm As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(dpi)

            For level As Integer = 0 To m.PanelRects.Count - 1
                Dim pr As SKRect = m.PanelRects(level)
                If pr.IsEmpty OrElse pr.Width <= 1.0F OrElse pr.Height <= 1.0F Then Continue For

                pr = MASSurfaceInteractionRing.SnapBaseRect(pr, mm)
                If pr.IsEmpty OrElse pr.Width <= 1.0F OrElse pr.Height <= 1.0F Then Continue For

                Dim panelCorner As Single = MASRadiusResolver.ResolveSurfacePx(_contract, pr, dpi)
                panelCorner = PixelSnap.SafeRadius(panelCorner)

                Dim rows As List(Of MASDropDownMenuNode) = ResolveRowsForLevel(m, level)
                Dim firstAction As Integer = FindFirstNonSeparatorRow(rows)
                Dim lastAction As Integer = FindLastNonSeparatorRow(rows)

                DrawPopupSurface(canvas, ctx, pr, panelCorner, dpi, palette)

                canvas.Save()
                Try
                    Dim rrPanel As New SKRoundRect()
                    rrPanel.SetRectRadii(
                        rect:=pr,
                        radii:=New SKPoint() {
                            New SKPoint(panelCorner, panelCorner),
                            New SKPoint(panelCorner, panelCorner),
                            New SKPoint(panelCorner, panelCorner),
                            New SKPoint(panelCorner, panelCorner)
                        })
                    canvas.ClipRoundRect(rrPanel)

                    Dim scrollOffset As Single = m.GetScrollOffsetPx(level)
                    Dim visibleTop As Single = pr.Top + padY
                    Dim visibleBottom As Single = pr.Bottom - padY
                    Dim y As Single = visibleTop - scrollOffset

                    For i As Integer = 0 To rows.Count - 1
                        Dim row As MASDropDownMenuNode = rows(i)

                        If row Is Nothing Then
                            y += rowH
                            Continue For
                        End If

                        If row.Kind = MASDropDownMenuNodeKind.Separator Then
                            Dim separatorBottom As Single = y + sepH
                            If separatorBottom >= visibleTop AndAlso y <= visibleBottom Then
                                DrawSeparator(canvas, pr, y, dpi, palette)
                            End If

                            y += sepH
                            Continue For
                        End If

                        Dim rowTop As Single = y
                        Dim rowBot As Single = y + rowH

                        If rowBot < visibleTop Then
                            y += rowH
                            Continue For
                        End If

                        If rowTop > visibleBottom Then Exit For

                        Dim rowDisabled As Boolean = Not m.Enabled OrElse Not row.Enabled
                        Dim rowState As MASInteractionState =
                            If(rowDisabled,
                               MASInteractionState.Disabled,
                               _interactionResolver.ResolveRowState(m, level, i))

                        Dim overlayRowRect As SKRect = BuildMenuRowOverlayRect(
                            panelRect:=pr,
                            rowTop:=rowTop,
                            rowBottom:=rowBot,
                            isFirst:=(i = firstAction),
                            isLast:=(i = lastAction),
                            dpi:=dpi,
                            separatorHeight:=sepH)

                        DrawRowInteraction(canvas, overlayRowRect, dpi, rowState, rowDisabled, palette)
                        DrawContentMotionPulse(
                            canvas:=canvas,
                            dpi:=dpi,
                            m:=m,
                            level:=level,
                            rowIndex:=i,
                            r:=overlayRowRect,
                            radius:=Math.Max(4.0F * dpi, Math.Min(panelCorner, overlayRowRect.Height * 0.36F)),
                            palette:=palette)

                        Dim needsRightGlyph As Boolean = row.Kind = MASDropDownMenuNodeKind.SubMenu OrElse row.IsChecked
                        Dim rightReserve As Single = If(needsRightGlyph, glyphReserve, 0.0F)
                        Dim shortcutReserve As Single = If(String.IsNullOrWhiteSpace(row.ShortcutText), 0.0F, 72.0F * dpi)

                        Dim textBounds As SKRect = PixelSnap.SnapRectHalf(
                            New SKRect(
                                pr.Left + padX,
                                rowTop,
                                pr.Right - padX - rightReserve - shortcutReserve,
                                rowBot))

                        Dim shortcutBounds As SKRect = PixelSnap.SnapRectHalf(
                            New SKRect(
                                Math.Max(pr.Left + padX, pr.Right - padX - rightReserve - shortcutReserve),
                                rowTop,
                                pr.Right - padX - rightReserve,
                                rowBot))

                        DrawRowText(canvas, ctx, dpi, rowState, Not rowDisabled, palette, row, textBounds)
                        DrawShortcutText(canvas, ctx, dpi, rowState, Not rowDisabled, palette, row.ShortcutText, shortcutBounds)

                        If row.Kind = MASDropDownMenuNodeKind.SubMenu Then
                            DrawChevron(canvas, dpi, rowState, Not rowDisabled, palette, pr, rowTop, rowH, padX)
                        ElseIf row.IsChecked Then
                            DrawCheckMark(canvas, dpi, rowState, Not rowDisabled, palette, pr, rowTop, rowH, padX)
                        End If

                        y += rowH
                    Next

                    DrawScrollEdgeIndicators(
                        canvas:=canvas,
                        panelRect:=pr,
                        dpi:=dpi,
                        palette:=palette,
                        scrollOffsetPx:=scrollOffset,
                        contentHeightPx:=ResolveContentHeightForLevel(m, level))
                Finally
                    canvas.Restore()
                End Try
            Next
        End Sub

        Private Sub DrawPopupSurface(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     panelRect As SKRect,
                                     radius As Single,
                                     dpi As Single,
                                     palette As MASMenuBarPaletteTheme)
            If canvas Is Nothing Then Return
            If ctx Is Nothing Then Return
            If panelRect.IsEmpty OrElse panelRect.Width <= 1.0F OrElse panelRect.Height <= 1.0F Then Return

            _shadowPainter.DrawLayeredShadow(
                canvas:=canvas,
                r:=panelRect,
                contract:=_contract,
                dpi:=dpi,
                surfaceTheme:=ctx.SurfaceTheme)

            Using shader As SKShader = SKShader.CreateLinearGradient(
                    New SKPoint(panelRect.Left, panelRect.Top),
                    New SKPoint(panelRect.Left, panelRect.Bottom),
                    New SKColor() {palette.PopupTop, palette.PopupMid, palette.PopupBottom},
                    New Single() {0.0F, 0.5F, 1.0F},
                    SKShaderTileMode.Clamp),
                  fill As New SKPaint With {
                    .IsAntialias = True,
                    .IsDither = True,
                    .Style = SKPaintStyle.Fill,
                    .Shader = shader
                  }
                canvas.DrawRoundRect(panelRect, radius, radius, fill)
            End Using

            If palette.PopupBorder.Alpha > 0 Then
                Using border As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = Math.Max(1.0F, 1.0F * dpi),
                    .Color = palette.PopupBorder
                }
                    Dim borderRect As SKRect = panelRect
                    borderRect.Inflate(-0.5F * dpi, -0.5F * dpi)
                    canvas.DrawRoundRect(borderRect, Math.Max(0.0F, radius - (0.5F * dpi)), Math.Max(0.0F, radius - (0.5F * dpi)), border)
                End Using
            End If
        End Sub

        Private Shared Sub DrawRowInteraction(canvas As SKCanvas,
                                              rect As SKRect,
                                              dpi As Single,
                                              rowState As MASInteractionState,
                                              rowDisabled As Boolean,
                                              palette As MASMenuBarPaletteTheme)
            If canvas Is Nothing Then Return
            If rect.IsEmpty OrElse rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return
            If rowDisabled OrElse rowState = MASInteractionState.None Then Return

            Dim interactionTheme As IMASInteractionTheme =
                MASMenuBarInteractionPaletteFactory.CreateDropDownRowPalette(palette)

            Dim spec As MASVisualInteractionOverlaySpec =
                MASInteractionResolver.BuildWithPalette(
                    contract:=_contract,
                    state:=rowState,
                    interactionTheme:=interactionTheme)

            MASSurfaceInteractionRing.DrawOverlay(
                canvas:=canvas,
                baseRectPx:=rect,
                contract:=_contract,
                spec:=spec,
                dpi:=dpi,
                fillInsetDipOverride:=MenuTokens.DropDownRowInteractionFillInsetDip,
                ringInsetDipOverride:=MenuTokens.DropDownRowInteractionRingInsetDip)
        End Sub


        Private Shared Sub DrawContentMotionPulse(canvas As SKCanvas,
                                                  dpi As Single,
                                                  m As DropDownMenuState,
                                                  level As Integer,
                                                  rowIndex As Integer,
                                                  r As SKRect,
                                                  radius As Single,
                                                  palette As MASMenuBarPaletteTheme)
            If canvas Is Nothing OrElse m Is Nothing Then Return
            If Not m.HasContentMotion Then Return
            If m.ContentMotionLevel <> level OrElse m.ContentMotionRowIndex <> rowIndex Then Return
            If r.Width <= 1.0F OrElse r.Height <= 1.0F Then Return

            Dim progress As Single = Math.Max(0.0F, Math.Min(1.0F, m.ContentMotionProgress))
            If progress >= 1.0F Then Return

            Dim alpha As Byte = CByte(Math.Max(0, Math.Min(255, CInt(Math.Round(42.0R * CDbl(1.0F - progress))))))
            If alpha <= 0 Then Return

            Dim pulseRect As SKRect = PixelSnap.SnapRectHalf(New SKRect(r.Left + (2.0F * dpi), r.Top + (1.0F * dpi), r.Right - (2.0F * dpi), r.Bottom - (1.0F * dpi)))
            If pulseRect.Width <= 1.0F OrElse pulseRect.Height <= 1.0F Then Return

            Dim pulseColor As SKColor = palette.PopupText.WithAlpha(alpha)
            Using paint As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = pulseColor,
                .BlendMode = SKBlendMode.SrcOver
            }
                canvas.DrawRoundRect(pulseRect, radius, radius, paint)
            End Using
        End Sub

        Private Shared Sub DrawSeparator(canvas As SKCanvas,
                                         panelRect As SKRect,
                                         y As Single,
                                         dpi As Single,
                                         palette As MASMenuBarPaletteTheme)
            If canvas Is Nothing Then Return
            If palette.PopupSeparator.Alpha <= 0 Then Return

            Dim x1 As Single = panelRect.Left + (10.0F * dpi)
            Dim x2 As Single = panelRect.Right - (10.0F * dpi)
            Dim yy As Single = PixelSnap.SnapHalf(y + (MenuTokens.DropDownSeparatorHeight * dpi * 0.5F))

            Using p As New SKPaint With {
                .IsAntialias = False,
                .IsDither = False,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, 1.0F * dpi),
                .Color = palette.PopupSeparator
            }
                canvas.DrawLine(x1, yy, x2, yy, p)
            End Using
        End Sub

        Private Shared Sub DrawRowText(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       dpi As Single,
                                       rowState As MASInteractionState,
                                       menuEnabled As Boolean,
                                       palette As MASMenuBarPaletteTheme,
                                       row As MASDropDownMenuNode,
                                       bounds As SKRect)
            If row Is Nothing Then Return
            If String.IsNullOrEmpty(row.Text) Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return

            Dim colorToDraw As SKColor = ResolveRowTextColor(ctx, palette, rowState, menuEnabled, row.Intent)

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=row.Text,
                bounds:=bounds,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=colorToDraw,
                align:=TypographyTextPrimitives.TextAlign.Left,
                drawShadow:=False)
        End Sub

        Private Shared Sub DrawShortcutText(canvas As SKCanvas,
                                            ctx As MASThemeContext,
                                            dpi As Single,
                                            rowState As MASInteractionState,
                                            menuEnabled As Boolean,
                                            palette As MASMenuBarPaletteTheme,
                                            text As String,
                                            bounds As SKRect)
            If String.IsNullOrWhiteSpace(text) Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If bounds.Width <= 4.0F OrElse bounds.Height <= 4.0F Then Return

            Dim colorToDraw As SKColor = ResolveGlyphTextColor(palette, rowState, menuEnabled)

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=text,
                bounds:=bounds,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=colorToDraw,
                align:=TypographyTextPrimitives.TextAlign.Right,
                drawShadow:=False)
        End Sub

        Private Sub DrawChevron(canvas As SKCanvas,
                                dpi As Single,
                                rowState As MASInteractionState,
                                menuEnabled As Boolean,
                                palette As MASMenuBarPaletteTheme,
                                pr As SKRect,
                                rowTop As Single,
                                rowH As Single,
                                padX As Single)
            If canvas Is Nothing Then Return

            Dim size As Single = Math.Max(7.0F, 8.0F * dpi)
            Dim strokeW As Single = Math.Max(SurfaceTokens.FxRingMinStrokePx, 1.35F * dpi)
            Dim cx As Single = PixelSnap.SnapHalf(pr.Right - padX - (MenuTokens.DropDownRightGlyphReserve * 0.5F * dpi))
            Dim cy As Single = PixelSnap.SnapHalf(rowTop + (rowH * 0.5F))
            Dim col As SKColor = ResolveGlyphTextColor(palette, rowState, menuEnabled)

            _icon.StrokeWidth = strokeW
            _icon.Shader = Nothing
            _icon.Color = col

            Dim a As New SKPoint(cx - (size * 0.35F), cy - (size * 0.35F))
            Dim b As New SKPoint(cx + (size * 0.2F), cy)
            Dim c As New SKPoint(cx - (size * 0.35F), cy + (size * 0.35F))

            canvas.DrawLine(a, b, _icon)
            canvas.DrawLine(b, c, _icon)
        End Sub

        Private Sub DrawCheckMark(canvas As SKCanvas,
                                  dpi As Single,
                                  rowState As MASInteractionState,
                                  menuEnabled As Boolean,
                                  palette As MASMenuBarPaletteTheme,
                                  pr As SKRect,
                                  rowTop As Single,
                                  rowH As Single,
                                  padX As Single)
            If canvas Is Nothing Then Return

            Dim size As Single = Math.Max(7.0F, 8.0F * dpi)
            Dim strokeW As Single = Math.Max(SurfaceTokens.FxRingMinStrokePx, 1.45F * dpi)
            Dim cx As Single = PixelSnap.SnapHalf(pr.Right - padX - (MenuTokens.DropDownRightGlyphReserve * 0.5F * dpi))
            Dim cy As Single = PixelSnap.SnapHalf(rowTop + (rowH * 0.5F))
            Dim col As SKColor = ResolveGlyphTextColor(palette, rowState, menuEnabled)

            _icon.StrokeWidth = strokeW
            _icon.Shader = Nothing
            _icon.Color = col

            Dim a As New SKPoint(cx - (size * 0.42F), cy + (size * 0.02F))
            Dim b As New SKPoint(cx - (size * 0.12F), cy + (size * 0.32F))
            Dim c As New SKPoint(cx + (size * 0.42F), cy - (size * 0.34F))

            canvas.DrawLine(a, b, _icon)
            canvas.DrawLine(b, c, _icon)
        End Sub

        Private Shared Function ResolveRowTextColor(ctx As MASThemeContext,
                                                    palette As MASMenuBarPaletteTheme,
                                                    rowState As MASInteractionState,
                                                    menuEnabled As Boolean,
                                                    intent As MASDropDownMenuItemIntent) As SKColor
            Dim colorToDraw As SKColor = If(menuEnabled, palette.PopupText, palette.PopupMutedText)

            If menuEnabled Then
                Select Case rowState
                    Case MASInteractionState.Hovered, MASInteractionState.Selected, MASInteractionState.Pressed
                        colorToDraw = palette.ItemActiveText
                End Select
            End If

            If intent = MASDropDownMenuItemIntent.Danger AndAlso ctx IsNot Nothing AndAlso ctx.Theme IsNot Nothing Then
                Dim dangerCol As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                If dangerCol.Alpha > 0 Then
                    colorToDraw = If(menuEnabled,
                                     ColorMath.Mix(colorToDraw, dangerCol, 0.28F),
                                     ColorMath.Mix(colorToDraw, dangerCol, 0.14F))
                End If
            End If

            Return colorToDraw
        End Function

        Private Shared Function ResolveGlyphTextColor(palette As MASMenuBarPaletteTheme,
                                                      rowState As MASInteractionState,
                                                      menuEnabled As Boolean) As SKColor
            If Not menuEnabled Then Return palette.PopupMutedText

            Select Case rowState
                Case MASInteractionState.Hovered, MASInteractionState.Selected, MASInteractionState.Pressed
                    Return palette.ItemActiveText
                Case Else
                    Return palette.PopupMutedText
            End Select
        End Function

        Private Shared Sub DrawScrollEdgeIndicators(canvas As SKCanvas,
                                                    panelRect As SKRect,
                                                    dpi As Single,
                                                    palette As MASMenuBarPaletteTheme,
                                                    scrollOffsetPx As Single,
                                                    contentHeightPx As Single)
            If canvas Is Nothing Then Return
            If panelRect.IsEmpty OrElse panelRect.Width <= 1.0F OrElse panelRect.Height <= 1.0F Then Return
            If palette.PopupSeparator.Alpha <= 0 Then Return

            dpi = PixelSnap.SafeDpi(dpi)
            Dim padY As Single = MenuTokens.DropDownPanelPadY * dpi
            Dim visibleHeight As Single = Math.Max(0.0F, panelRect.Height - (padY * 2.0F))
            If contentHeightPx <= visibleHeight + 0.5F Then Return

            Dim canScrollUp As Boolean = scrollOffsetPx > 0.5F
            Dim canScrollDown As Boolean = scrollOffsetPx < (contentHeightPx - visibleHeight - 0.5F)
            If Not canScrollUp AndAlso Not canScrollDown Then Return

            Dim x1 As Single = panelRect.Left + (12.0F * dpi)
            Dim x2 As Single = panelRect.Right - (12.0F * dpi)
            Dim topY As Single = PixelSnap.SnapHalf(panelRect.Top + padY - (1.0F * dpi))
            Dim bottomY As Single = PixelSnap.SnapHalf(panelRect.Bottom - padY + (1.0F * dpi))

            Using p As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeCap = SKStrokeCap.Round,
                .StrokeWidth = Math.Max(1.0F, 1.0F * dpi),
                .Color = palette.PopupSeparator
            }
                If canScrollUp Then canvas.DrawLine(x1, topY, x2, topY, p)
                If canScrollDown Then canvas.DrawLine(x1, bottomY, x2, bottomY, p)
            End Using
        End Sub

        Private Shared Function ResolveContentHeightForLevel(m As DropDownMenuState,
                                                             level As Integer) As Single
            If m Is Nothing OrElse m.LevelContentHeightsPx Is Nothing Then Return 0.0F
            If level < 0 OrElse level >= m.LevelContentHeightsPx.Count Then Return 0.0F
            Return Math.Max(0.0F, m.LevelContentHeightsPx(level))
        End Function

        Private Shared Function ResolveRowsForLevel(m As DropDownMenuState,
                                                    level As Integer) As List(Of MASDropDownMenuNode)
            If m Is Nothing Then Return New List(Of MASDropDownMenuNode)()
            If m.LevelRows Is Nothing Then Return New List(Of MASDropDownMenuNode)()
            If level < 0 OrElse level >= m.LevelRows.Count Then Return New List(Of MASDropDownMenuNode)()

            Dim rows As List(Of MASDropDownMenuNode) = m.LevelRows(level)
            If rows Is Nothing Then Return New List(Of MASDropDownMenuNode)()
            Return rows
        End Function

        Private Shared Function ResolvePalette(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 = SKColors.White
            Dim text As SKColor = Nexamas.UI.Values.Color.Text.Primary
            Dim muted As SKColor = SKColors.Gray
            Dim accent As SKColor = SKColors.DodgerBlue

            If ctx IsNot Nothing AndAlso ctx.SurfaceTheme IsNot Nothing Then surface = ctx.SurfaceTheme.SurfaceTop
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then
                text = ctx.TextColorTheme.PrimaryText
                muted = ctx.TextColorTheme.MutedText
            End If
            If ctx IsNot Nothing AndAlso ctx.BrandTheme IsNot Nothing Then accent = ctx.BrandTheme.BrandPrimary

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

        Private Shared Function FindFirstNonSeparatorRow(rows As List(Of MASDropDownMenuNode)) As Integer
            If rows Is Nothing OrElse rows.Count = 0 Then Return -1

            For i As Integer = 0 To rows.Count - 1
                Dim n As MASDropDownMenuNode = rows(i)
                If n Is Nothing Then Continue For
                If n.Kind <> MASDropDownMenuNodeKind.Separator Then Return i
            Next

            Return -1
        End Function

        Private Shared Function FindLastNonSeparatorRow(rows As List(Of MASDropDownMenuNode)) As Integer
            If rows Is Nothing OrElse rows.Count = 0 Then Return -1

            For i As Integer = rows.Count - 1 To 0 Step -1
                Dim n As MASDropDownMenuNode = rows(i)
                If n Is Nothing Then Continue For
                If n.Kind <> MASDropDownMenuNodeKind.Separator Then Return i
            Next

            Return -1
        End Function

        Private Shared Function BuildMenuRowOverlayRect(panelRect As SKRect,
                                                        rowTop As Single,
                                                        rowBottom As Single,
                                                        isFirst As Boolean,
                                                        isLast As Boolean,
                                                        dpi As Single,
                                                        separatorHeight As Single) As SKRect
            dpi = PixelSnap.SafeDpi(dpi)

            Dim separatorHalf As Single = Math.Max(0.0F, separatorHeight) * 0.5F
            Dim left As Single = panelRect.Left
            Dim right As Single = panelRect.Right
            Dim top As Single = If(isFirst, panelRect.Top, rowTop - separatorHalf)
            Dim bottom As Single = If(isLast, panelRect.Bottom, rowBottom + separatorHalf)

            If right <= left Then right = left + 1.0F
            If bottom <= top Then bottom = top + 1.0F

            Return PixelSnap.SnapRectHalf(New SKRect(left, top, right, bottom))
        End Function

    End Class

End Namespace
