Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Linq
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
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 premium chart legend surface. It owns legend item
    ''' presentation, color swatches, item selection, keyboard movement,
    ''' mouse selection, RTL-aware layout, and MAS Theme/Surface/Typography/
    ''' SizeLayout routing. Chart rendering, chart data preparation, analytics,
    ''' queries, external providers, storage, chart coupling, wrappers, adapters,
    ''' bridges, and background refresh remain outside this control.
    ''' </summary>
    Public NotInheritable Class MASChartLegend
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Nested state"

        Private NotInheritable Class LegendItemState
            Friend Sub New(itemKey As String,
                           label As String,
                           valueText As String,
                           colorArgb As UInteger)
                Me.Key = itemKey
                Me.Label = label
                Me.ValueText = valueText
                Me.ColorArgb = colorArgb
            End Sub

            Friend ReadOnly Property Key As String
            Friend ReadOnly Property Label As String
            Friend ReadOnly Property ValueText As String
            Friend ReadOnly Property ColorArgb As UInteger
        End Class

        Private NotInheritable Class LegendItemHit
            Friend Sub New(index As Integer,
                           rect As SKRect)
                Me.Index = index
                Me.Rect = rect
            End Sub

            Friend ReadOnly Property Index As Integer
            Friend ReadOnly Property Rect As SKRect
        End Class

#End Region

#Region "Fields"

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

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _items As New List(Of LegendItemState)()
        Private ReadOnly _hits As New List(Of LegendItemHit)()
        Private _title As String = ChartLegendTokens.DefaultTitle
        Private _selectedIndex As Integer = -1
        Private _hoverIndex As Integer = -1
        Private _pressedIndex As Integer = -1
        Private _topIndex As Integer
        Private _lastVisibleCount As Integer = ChartLegendTokens.MaxVisibleItems
        Private _contentRect As SKRect = SKRect.Empty

#End Region

#Region "Constructor / Factory"

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

        Public Shared Function Create(Optional title As String = Nothing) As MASChartLegend
            Dim control As New MASChartLegend()
            control._title = MASChartLegendPolicy.NormalizeTitle(title)
            Return control
        End Function

#End Region

#Region "Public API"

        Public Event LegendChanged As EventHandler
        Public Event SelectedItemChanged As EventHandler

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = MASChartLegendPolicy.NormalizeTitle(value)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return
                _title = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASChartLegend.Title")
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property ItemCount As Integer
            Get
                Return _items.Count
            End Get
        End Property

        Public ReadOnly Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
        End Property

        Public ReadOnly Property SelectedItemKey As String
            Get
                If _selectedIndex < 0 OrElse _selectedIndex >= _items.Count Then Return String.Empty
                Return _items(_selectedIndex).Key
            End Get
        End Property

        Public ReadOnly Property SummaryText As String
            Get
                Return MASChartLegendPolicy.BuildSummary(ItemCount, _selectedIndex)
            End Get
        End Property

        Public Function GetItemKeys() As IReadOnlyList(Of String)
            Dim keys As New List(Of String)()
            For Each item As LegendItemState In _items
                keys.Add(item.Key)
            Next
            Return New ReadOnlyCollection(Of String)(keys)
        End Function

        Public Function AddItem(itemKey As String,
                                label As String,
                                Optional valueText As String = Nothing,
                                Optional colorArgb As UInteger = 0UI) As MASChartLegend
            If _items.Count >= ChartLegendTokens.MaxItems Then Return Me
            Dim normalizedKey As String = MASChartLegendPolicy.NormalizeKey(itemKey, "legend", _items.Count + 1)
            If FindItemIndex(normalizedKey) >= 0 Then Return Me
            _items.Add(New LegendItemState(normalizedKey,
                                           MASChartLegendPolicy.NormalizeLabel(label),
                                           MASChartLegendPolicy.NormalizeValueText(valueText),
                                           colorArgb))
            If _selectedIndex < 0 Then _selectedIndex = 0
            RequestSizeLayoutRefreshForSizeAffectingChange("MASChartLegend.AddItem")
            InvalidateVisual()
            RaiseLegendChangedSafe()
            Return Me
        End Function

        Public Function ClearItems() As MASChartLegend
            If _items.Count = 0 Then Return Me
            _items.Clear()
            _hits.Clear()
            _selectedIndex = -1
            _hoverIndex = -1
            _pressedIndex = -1
            _topIndex = 0
            RequestSizeLayoutRefreshForSizeAffectingChange("MASChartLegend.ClearItems")
            InvalidateVisual()
            RaiseLegendChangedSafe()
            RaiseSelectedItemChangedSafe()
            Return Me
        End Function

        Public Function SelectItem(itemKey As String) As Boolean
            Dim index As Integer = FindItemIndex(itemKey)
            If index < 0 Then Return False
            SetSelectedIndex(index, True)
            Return True
        End Function

        Public Function MovePrevious() As MASChartLegend
            If _items.Count > 0 Then SetSelectedIndex(_selectedIndex - 1, True)
            Return Me
        End Function

        Public Function MoveNext() As MASChartLegend
            If _items.Count > 0 Then SetSelectedIndex(_selectedIndex + 1, True)
            Return Me
        End Function

        Public Function WithTitle(value As String) As MASChartLegend
            Title = value
            Return Me
        End Function

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

#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

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

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Return MASSizeResolver.FromMeasured(
                desiredSize:=New SKSize(ChartLegendTokens.PreferredWidthDip, ChartLegendTokens.PreferredHeightDip),
                minSize:=New SKSize(ChartLegendTokens.ContractMinWidthDip, ChartLegendTokens.ContractMinHeightDip),
                maxSize:=New SKSize(ChartLegendTokens.MaxWidthDip, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=True,
                contentInset:=New MASLayoutInset(ChartLegendTokens.PaddingDip,
                                                 ChartLegendTokens.PaddingDip,
                                                 ChartLegendTokens.PaddingDip,
                                                 ChartLegendTokens.PaddingDip),
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        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 dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim responsive As MASResponsiveLayoutProfile = MASResponsiveLayoutResolver.FromTheme(ctx, bounds, SizeIntent, MASChartLegendLayoutPlan.Thresholds)
            Dim plan As MASChartLegendLayoutPlan = MASChartLegendLayoutPlan.Create(responsive)

            _primitives.DrawCardSurface(canvas, ctx, bounds, dpi, _contract, MASCardVisualStyle.Secondary)
            _contentRect = PixelSnap.SnapRect(Inset(bounds, plan.PaddingPx), dpi)
            If _contentRect.Width <= 1.0F OrElse _contentRect.Height <= 1.0F Then Return

            _hits.Clear()
            Dim isRtl As Boolean = MASChartLegendPolicy.ResolveRightToLeft()
            Dim accent As SKColor = MASChartLegendPolicy.ResolveAccentColor(ctx)
            DrawHeader(canvas, ctx, _contentRect, plan, isRtl)
            DrawItems(canvas, ctx, _contentRect, plan, accent, isRtl)
            DrawFooter(canvas, ctx, _contentRect, plan, isRtl)
        End Sub

        Private Sub DrawHeader(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               content As SKRect,
                               plan As MASChartLegendLayoutPlan,
                               isRtl As Boolean)
            If Not plan.ShowHeader OrElse plan.HeaderHeightPx <= 0.0F Then Return
            Dim header As New SKRect(content.Left, content.Top, content.Right, content.Top + plan.HeaderHeightPx)
            Dim titleWidth As Single = If(plan.ShowSummary, Math.Max(1.0F, header.Width * 0.58F), header.Width)
            Dim titleRect As SKRect = If(isRtl,
                                         New SKRect(header.Right - titleWidth, header.Top, header.Right, header.Bottom),
                                         New SKRect(header.Left, header.Top, header.Left + titleWidth, header.Bottom))
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _title, titleRect, plan.Dpi, MASTypography.MASTextStyle.Title, MASChartLegendPolicy.ResolvePrimaryTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
            If Not plan.ShowSummary Then Return
            Dim summaryRect As SKRect = If(isRtl,
                                           New SKRect(header.Left, header.Top, titleRect.Left - 6.0F * plan.Dpi, header.Bottom),
                                           New SKRect(titleRect.Right + 6.0F * plan.Dpi, header.Top, header.Right, header.Bottom))
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, SummaryText, summaryRect, plan.Dpi, MASTypography.MASTextStyle.Micro, MASChartLegendPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Left, TypographyTextPrimitives.TextAlign.Right), False)
        End Sub

        Private Sub DrawItems(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              content As SKRect,
                              plan As MASChartLegendLayoutPlan,
                              accent As SKColor,
                              isRtl As Boolean)
            Dim top As Single = content.Top + plan.HeaderHeightPx + 8.0F * plan.Dpi * plan.ResponsiveProfile.GapScale
            Dim bottom As Single = content.Bottom - plan.FooterHeightPx - 8.0F * plan.Dpi * plan.ResponsiveProfile.GapScale
            Dim listRect As New SKRect(content.Left, top, content.Right, bottom)
            If listRect.Width <= 1.0F OrElse listRect.Height <= 1.0F Then Return

            If _items.Count = 0 Then
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, ChartLegendTokens.EmptyText, listRect, plan.Dpi, MASTypography.MASTextStyle.Body, MASChartLegendPolicy.ResolveMutedTextColor(ctx).WithAlpha(ChartLegendTokens.EmptyTextAlpha), TypographyTextPrimitives.TextAlign.Center, False)
                Return
            End If

            NormalizeViewState()
            Dim rowHeight As Single = plan.RowHeightPx
            Dim rowGap As Single = plan.RowGapPx
            Dim bySpace As Integer = Math.Max(1, CInt(Math.Floor((listRect.Height + rowGap) / Math.Max(1.0F, rowHeight + rowGap))))
            _lastVisibleCount = Math.Min(plan.MaxVisibleItems, bySpace)
            _topIndex = MASChartLegendPolicy.EnsureVisibleIndex(_selectedIndex, _topIndex, _items.Count, _lastVisibleCount)
            Dim visibleCount As Integer = Math.Min(_lastVisibleCount, _items.Count - _topIndex)
            For visualIndex As Integer = 0 To visibleCount - 1
                Dim itemIndex As Integer = _topIndex + visualIndex
                Dim rowTop As Single = listRect.Top + CSng(visualIndex) * (rowHeight + rowGap)
                Dim rowRect As SKRect = PixelSnap.SnapRect(New SKRect(listRect.Left, rowTop, listRect.Right, rowTop + rowHeight), plan.Dpi)
                DrawLegendRow(canvas, ctx, rowRect, itemIndex, plan, accent, isRtl)
            Next
        End Sub

        Private Sub DrawLegendRow(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  rect As SKRect,
                                  index As Integer,
                                  plan As MASChartLegendLayoutPlan,
                                  accent As SKColor,
                                  isRtl As Boolean)
            Dim selected As Boolean = index = _selectedIndex
            Dim hovered As Boolean = index = _hoverIndex
            Dim pressed As Boolean = index = _pressedIndex
            Dim fillColor As SKColor = accent.WithAlpha(ChartLegendTokens.RowFillAlpha)
            If selected Then fillColor = accent.WithAlpha(ChartLegendTokens.RowSelectedAlpha)
            If hovered Then fillColor = accent.WithAlpha(ChartLegendTokens.RowHoverAlpha)
            If pressed Then fillColor = accent.WithAlpha(ChartLegendTokens.RowPressedAlpha)
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = fillColor}
                canvas.DrawRoundRect(rect, plan.RowRadiusPx, plan.RowRadiusPx, fill)
            End Using
            If selected Then
                Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, plan.Dpi), .Color = accent.WithAlpha(ChartLegendTokens.RowSelectedStrokeAlpha)}
                    canvas.DrawRoundRect(rect, plan.RowRadiusPx, plan.RowRadiusPx, stroke)
                End Using
            End If

            Dim item As LegendItemState = _items(index)
            Dim pad As Single = plan.RowPaddingPx
            Dim swatchSize As Single = plan.SwatchSizePx
            Dim swatchTop As Single = rect.MidY - swatchSize / 2.0F
            Dim swatchRect As SKRect = If(isRtl,
                                          New SKRect(rect.Right - pad - swatchSize, swatchTop, rect.Right - pad, swatchTop + swatchSize),
                                          New SKRect(rect.Left + pad, swatchTop, rect.Left + pad + swatchSize, swatchTop + swatchSize))
            Dim swatchColor As SKColor = MASChartLegendPolicy.ResolveSwatchColor(item.ColorArgb, index, accent)
            Using swatch As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = swatchColor.WithAlpha(230)}
                canvas.DrawRoundRect(swatchRect, plan.SwatchRadiusPx, plan.SwatchRadiusPx, swatch)
            End Using
            Using swatchStroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, plan.Dpi), .Color = swatchColor.WithAlpha(ChartLegendTokens.SwatchStrokeAlpha)}
                canvas.DrawRoundRect(swatchRect, plan.SwatchRadiusPx, plan.SwatchRadiusPx, swatchStroke)
            End Using
            If selected Then
                Using ring As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, plan.Dpi), .Color = swatchColor.WithAlpha(220)}
                    canvas.DrawRoundRect(Inset(swatchRect, -3.0F * plan.Dpi), plan.SwatchRadiusPx + 3.0F * plan.Dpi, plan.SwatchRadiusPx + 3.0F * plan.Dpi, ring)
                End Using
            End If

            Dim valueWidth As Single = If(plan.ShowValues AndAlso item.ValueText.Length > 0, plan.ValueWidthPx, 0.0F)
            Dim labelLeft As Single
            Dim labelRight As Single
            Dim valueRect As SKRect = SKRect.Empty
            If isRtl Then
                labelRight = swatchRect.Left - pad
                labelLeft = rect.Left + pad + valueWidth
                If valueWidth > 0.0F Then valueRect = New SKRect(rect.Left + pad, rect.Top, rect.Left + pad + valueWidth, rect.Bottom)
            Else
                labelLeft = swatchRect.Right + pad
                labelRight = rect.Right - pad - valueWidth
                If valueWidth > 0.0F Then valueRect = New SKRect(rect.Right - pad - valueWidth, rect.Top, rect.Right - pad, rect.Bottom)
            End If
            Dim labelRect As New SKRect(labelLeft, rect.Top, Math.Max(labelLeft + 1.0F, labelRight), rect.Bottom)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.Label, labelRect, plan.Dpi, MASTypography.MASTextStyle.Body, MASChartLegendPolicy.ResolvePrimaryTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
            If Not valueRect.IsEmpty Then
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.ValueText, valueRect, plan.Dpi, MASTypography.MASTextStyle.Micro, MASChartLegendPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Left, TypographyTextPrimitives.TextAlign.Right), False)
            End If
            If plan.ShowValueBars Then DrawLegendValueBar(canvas, rect, swatchColor, item.ValueText, plan, isRtl)
            _hits.Add(New LegendItemHit(index, rect))
        End Sub

        Private Sub DrawLegendValueBar(canvas As SKCanvas,
                                       row As SKRect,
                                       swatchColor As SKColor,
                                       valueText As String,
                                       plan As MASChartLegendLayoutPlan,
                                       isRtl As Boolean)
            Dim ratio As Single = ResolveLegendValueRatio(valueText)
            If ratio <= 0.0F Then Return
            Dim trackWidth As Single = Math.Min(row.Width * 0.28F, 96.0F * plan.Dpi)
            Dim trackHeight As Single = 3.0F * plan.Dpi
            Dim left As Single = If(isRtl, row.Left + 10.0F * plan.Dpi, row.Right - 10.0F * plan.Dpi - trackWidth)
            Dim track As New SKRect(left, row.Bottom - 8.0F * plan.Dpi, left + trackWidth, row.Bottom - 8.0F * plan.Dpi + trackHeight)
            Using bg As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = swatchColor.WithAlpha(34)}
                canvas.DrawRoundRect(track, trackHeight * 0.5F, trackHeight * 0.5F, bg)
            End Using
            Dim fill As SKRect = If(isRtl,
                                    New SKRect(track.Right - track.Width * ratio, track.Top, track.Right, track.Bottom),
                                    New SKRect(track.Left, track.Top, track.Left + track.Width * ratio, track.Bottom))
            Using fp As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = swatchColor.WithAlpha(180)}
                canvas.DrawRoundRect(fill, trackHeight * 0.5F, trackHeight * 0.5F, fp)
            End Using
        End Sub

        Private Shared Function ResolveLegendValueRatio(valueText As String) As Single
            If String.IsNullOrWhiteSpace(valueText) Then Return 0.0F
            Dim digits As String = New String(valueText.Where(Function(ch As Char) Char.IsDigit(ch) OrElse ch = "."c OrElse ch = ","c).ToArray())
            If digits.Length = 0 Then Return 0.0F
            Dim normalized As String = digits.Replace(","c, "."c)
            Dim value As Single
            If Not Single.TryParse(normalized, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, value) Then Return 0.0F
            If value > 100.0F Then value = value Mod 100.0F
            Return Math.Max(0.08F, Math.Min(1.0F, value / 100.0F))
        End Function

        Private Sub DrawFooter(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               content As SKRect,
                               plan As MASChartLegendLayoutPlan,
                               isRtl As Boolean)
            If Not plan.ShowFooter OrElse plan.FooterHeightPx <= 0.0F Then Return
            Dim footer As New SKRect(content.Left, content.Bottom - plan.FooterHeightPx, content.Right, content.Bottom)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, ChartLegendTokens.BoundaryText, footer, plan.Dpi, MASTypography.MASTextStyle.Micro, MASChartLegendPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
        End Sub

#End Region

#Region "Input"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext,
                                            ptPx As SKPoint)
            Dim hit As LegendItemHit = HitTest(ptPx)
            Dim index As Integer = If(hit Is Nothing, -1, hit.Index)
            If index = _hoverIndex Then Return
            _hoverIndex = index
            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 LegendItemHit = HitTest(ptPx)
            If hit Is Nothing Then Return _contentRect.Contains(ptPx.X, ptPx.Y)
            _pressedIndex = hit.Index
            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 hit As LegendItemHit = HitTest(ptPx)
            If hit IsNot Nothing AndAlso hit.Index = pressed Then
                SetSelectedIndex(hit.Index, True)
                Return True
            End If
            InvalidateVisual()
            Return False
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            _hoverIndex = -1
            _pressedIndex = -1
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            _pressedIndex = -1
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            _pressedIndex = -1
            InvalidateVisual()
        End Sub

        Friend Overrides Function WantsPointerWheel(ctx As MASThemeContext,
                                                      ptPx As SKPoint) As Boolean
            Return Visible AndAlso Enabled AndAlso
                   _contentRect.Contains(ptPx.X, ptPx.Y) AndAlso
                   HasScrollableItemViewport()
        End Function

        Friend Overrides Function CanHandlePointerWheel(ctx As MASThemeContext,
                                                        delta As Integer,
                                                        ptPx As SKPoint) As Boolean
            If delta = 0 OrElse Not WantsPointerWheel(ctx, ptPx) Then Return False
            Return CanScrollItemViewport(delta)
        End Function

        Protected Overrides Sub OnMouseWheel(ctx As MASThemeContext,
                                             delta As Integer,
                                             ptPx As SKPoint)
            If Not CanScrollItemViewport(delta) Then Return
            If delta < 0 Then
                _topIndex = Math.Min(ResolveItemViewportMaxTop(), _topIndex + 1)
            ElseIf delta > 0 Then
                _topIndex = Math.Max(0, _topIndex - 1)
            End If
            InvalidateVisual()
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If Not Enabled OrElse _items.Count = 0 Then Return False
            Select Case keyCode
                Case Keys.Up, Keys.Left
                    SetSelectedIndex(_selectedIndex + If(MASChartLegendPolicy.ResolveRightToLeft() AndAlso keyCode = Keys.Left, 1, -1), True)
                    Return True
                Case Keys.Down, Keys.Right
                    SetSelectedIndex(_selectedIndex + If(MASChartLegendPolicy.ResolveRightToLeft() AndAlso keyCode = Keys.Right, -1, 1), True)
                    Return True
                Case Keys.Home
                    SetSelectedIndex(0, True)
                    Return True
                Case Keys.End
                    SetSelectedIndex(_items.Count - 1, True)
                    Return True
            End Select
            Return False
        End Function

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return Visible AndAlso Enabled
        End Function

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

#End Region

#Region "Helpers"

        Private Function HasScrollableItemViewport() As Boolean
            Return _items.Count > Math.Max(0, _lastVisibleCount)
        End Function

        Private Function ResolveItemViewportMaxTop() As Integer
            Return Math.Max(0, _items.Count - Math.Max(1, _lastVisibleCount))
        End Function

        Private Function CanScrollItemViewport(delta As Integer) As Boolean
            If Not HasScrollableItemViewport() Then Return False
            Dim maxTop As Integer = ResolveItemViewportMaxTop()
            If delta < 0 Then Return _topIndex < maxTop
            If delta > 0 Then Return _topIndex > 0
            Return False
        End Function

        Private Sub SetSelectedIndex(index As Integer,
                                     shouldRaiseSelectionEvent As Boolean)
            Dim normalized As Integer = MASChartLegendPolicy.ClampIndex(index, _items.Count)
            If normalized < 0 Then
                _selectedIndex = -1
                Return
            End If
            Dim changed As Boolean = normalized <> _selectedIndex
            _selectedIndex = normalized
            _topIndex = MASChartLegendPolicy.EnsureVisibleIndex(_selectedIndex, _topIndex, _items.Count, _lastVisibleCount)
            InvalidateVisual()
            If changed AndAlso shouldRaiseSelectionEvent Then RaiseSelectedItemChangedSafe()
        End Sub

        Private Sub NormalizeViewState()
            If _items.Count = 0 Then
                _selectedIndex = -1
                _topIndex = 0
                Return
            End If
            _selectedIndex = MASChartLegendPolicy.ClampIndex(_selectedIndex, _items.Count)
            _topIndex = MASChartLegendPolicy.EnsureVisibleIndex(_selectedIndex, _topIndex, _items.Count, _lastVisibleCount)
        End Sub

        Private Function FindItemIndex(itemKey As String) As Integer
            Dim normalized As String = MASChartLegendPolicy.NormalizeKey(itemKey, "legend", 1)
            For i As Integer = 0 To _items.Count - 1
                If String.Equals(_items(i).Key, normalized, StringComparison.Ordinal) Then Return i
            Next
            Return -1
        End Function

        Private Function HitTest(point As SKPoint) As LegendItemHit
            For Each hit As LegendItemHit In _hits
                If hit.Rect.Contains(point.X, point.Y) Then Return hit
            Next
            Return Nothing
        End Function

        Private Shared Function Inset(rect As SKRect,
                                      amount As Single) As SKRect
            Return New SKRect(rect.Left + amount, rect.Top + amount, rect.Right - amount, rect.Bottom - amount)
        End Function

        Private Sub RaiseLegendChangedSafe()
            Try
                RaiseEvent LegendChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASChartLegend.LegendChanged")
            End Try
        End Sub

        Private Sub RaiseSelectedItemChangedSafe()
            Try
                RaiseEvent SelectedItemChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASChartLegend.SelectedItemChanged")
            End Try
        End Sub

        Friend Function CreateChartLegendReadinessManifest() As MASChartLegendReadinessManifest
            Return MASChartLegendReadinessManifest.CreateDefault()
        End Function

#End Region

    End Class

End Namespace
