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.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public Nexamas UI breadcrumb navigation control for product pages, settings,
    ''' dashboards, and admin surfaces. It is a same-surface navigation indicator and
    ''' selection trigger only; page navigation decisions, trail lifetime, menus, floating overlays,
    ''' and command execution stay owned by their existing Nexamas UI systems.
    ''' </summary>
    Public NotInheritable Class MASBreadcrumb
        Inherits Nexamas.UI.Controls.MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private ReadOnly _items As New List(Of String)()
        Private _selectedIndex As Integer = BreadcrumbTokens.DefaultSelectedIndex
        Private _separatorText As String = BreadcrumbTokens.SeparatorText
        Private _hoverIndex As Integer = -1
        Private _pressedIndex As Integer = -1
        Private _lastItemRects() As SKRect = Array.Empty(Of SKRect)()
        Private _lastItemIndices() As Integer = Array.Empty(Of Integer)()
        Private _lastRenderedRtl As Boolean
        Private _lastLayoutPlan As MASBreadcrumbLayoutPlan
        Private _selectionMotionRunner As MASMotionTimelineRunner
        Private _selectionMotionFromIndex As Integer = -1
        Private _selectionMotionToIndex As Integer = -1
        Private _selectionMotionProgress As Single = 1.0F
        Private _hasSelectionMotion As Boolean
        Private _disposedLocal As Boolean

#End Region

#Region "Constructor"

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

#End Region

#Region "Public API"

        Public Event SelectedIndexChanged As EventHandler

        Public Shared Function Create(Optional items As IEnumerable(Of String) = Nothing) As MASBreadcrumb
            Dim control As New MASBreadcrumb()
            If items IsNot Nothing Then control.SetItems(items)
            Return control
        End Function

        Public ReadOnly Property ItemCount As Integer
            Get
                Return _items.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 ReadOnly Property SelectedText As String
            Get
                If _selectedIndex < 0 OrElse _selectedIndex >= _items.Count Then Return String.Empty
                Return _items(_selectedIndex)
            End Get
        End Property

        Public Property SeparatorText As String
            Get
                Return _separatorText
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeSeparatorText(value)
                If String.Equals(_separatorText, normalized, StringComparison.Ordinal) Then Return

                _separatorText = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASBreadcrumb.SeparatorText")
                InvalidateVisual()
            End Set
        End Property

        Public Function SetItems(items As IEnumerable(Of String)) As MASBreadcrumb
            _items.Clear()

            If items IsNot Nothing Then
                For Each item As String In items
                    Dim normalized As String = NormalizeItemText(item)
                    If normalized.Length = 0 Then Continue For

                    _items.Add(normalized)
                    If _items.Count >= BreadcrumbTokens.MaxItems Then Exit For
                Next
            End If

            NormalizeSelectionAfterCollectionChange()
            ResetPointerState()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASBreadcrumb.SetItems")
            InvalidateVisual()
            Return Me
        End Function

        Public Function AddItem(text As String) As MASBreadcrumb
            Dim normalized As String = NormalizeItemText(text)
            If normalized.Length = 0 Then Return Me
            If _items.Count >= BreadcrumbTokens.MaxItems Then Return Me

            _items.Add(normalized)
            NormalizeSelectionAfterCollectionChange()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASBreadcrumb.AddItem")
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearItems() As MASBreadcrumb
            If _items.Count = 0 Then Return Me

            _items.Clear()
            SetSelectedIndexInternal(BreadcrumbTokens.DefaultSelectedIndex, True)
            ResetPointerState()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASBreadcrumb.ClearItems")
            InvalidateVisual()
            Return Me
        End Function

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

        Public Function WithSelectedIndex(index As Integer) As MASBreadcrumb
            SelectedIndex = index
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASBreadcrumb
            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

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim measuredWidth As Single = MeasureItemsWidthDip(safeContext)
            Dim desiredWidth As Single = Math.Max(BreadcrumbTokens.PreferredMinWidthDip,
                                                  Math.Min(BreadcrumbTokens.PreferredMaxWidthDip, measuredWidth))
            Dim desired As New SKSize(desiredWidth, BreadcrumbTokens.HeightDip)
            Dim minimum As New SKSize(BreadcrumbTokens.MinWidthDip, BreadcrumbTokens.MinHeightDip)
            Dim contentInset As New MASLayoutInset(BreadcrumbTokens.PaddingHorizontalDip,
                                                   BreadcrumbTokens.PaddingVerticalDip,
                                                   BreadcrumbTokens.PaddingHorizontalDip,
                                                   BreadcrumbTokens.PaddingVerticalDip)

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

        Private Function MeasureItemsWidthDip(context As MASSizeContext) As Single
            Dim width As Single = BreadcrumbTokens.PaddingHorizontalDip * 2.0F
            Dim count As Integer = Math.Max(1, _items.Count)

            For i As Integer = 0 To count - 1
                Dim text As String = If(_items.Count = 0, BreadcrumbTokens.EmptyText, _items(i))
                Dim textSize As SKSize = MASLayoutTextMeasureGateway.Measure(
                    If(context Is Nothing, Nothing, context.IntegrationContext),
                    text,
                    MASTypography.MASTextStyle.Small,
                    Single.PositiveInfinity,
                    allowWrap:=False).Size

                width += Math.Max(BreadcrumbTokens.ItemMinWidthDip,
                                  textSize.Width + (BreadcrumbTokens.ItemPaddingHorizontalDip * 2.0F))

                If i < count - 1 Then
                    width += BreadcrumbTokens.SeparatorWidthDip + (BreadcrumbTokens.SeparatorGapDip * 2.0F)
                End If
            Next

            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 plan As MASBreadcrumbLayoutPlan = MASBreadcrumbLayoutPlan.Create(ctx, pixelBounds, SizeIntent)
            If plan Is Nothing Then Return
            _lastLayoutPlan = plan
            Dim dpi As Single = plan.Dpi
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim content As New SKRect(bounds.Left + plan.PaddingHorizontalPx,
                                      bounds.Top + plan.PaddingVerticalPx,
                                      bounds.Right - plan.PaddingHorizontalPx,
                                      bounds.Bottom - plan.PaddingVerticalPx)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            Dim isRtl As Boolean = ResolveRightToLeft()
            _lastRenderedRtl = isRtl

            Dim entries As List(Of RenderEntry) = BuildRenderEntries(ctx, content.Width, plan)
            DrawEntries(canvas, ctx, content, entries, plan, isRtl)
            DrawFocusRing(canvas, ctx, content, plan)
        End Sub

        Private Function BuildRenderEntries(ctx As MASThemeContext,
                                            maxWidthPx As Single,
                                            plan As MASBreadcrumbLayoutPlan) As List(Of RenderEntry)
            If plan Is Nothing Then Return New List(Of RenderEntry)()
            Dim source As List(Of RenderEntry) = BuildAllEntries(ctx, plan)
            If source.Count <= 0 Then Return source

            If plan.CollapseToTailOnly AndAlso source.Count > 1 Then
                Dim tailOnly As New List(Of RenderEntry)()
                tailOnly.Add(CreateEntry(BreadcrumbTokens.OverflowText, -1, ctx, plan))
                tailOnly.Add(source(source.Count - 1))
                If CalculateEntryWidth(tailOnly, plan) <= maxWidthPx OrElse maxWidthPx > 1.0F Then Return tailOnly
            End If

            Dim totalWidth As Single = CalculateEntryWidth(source, plan)
            If totalWidth <= maxWidthPx Then Return source

            Dim collapsed As New List(Of RenderEntry)()
            Dim overflow As RenderEntry = CreateEntry(BreadcrumbTokens.OverflowText, -1, ctx, plan)
            collapsed.Add(overflow)

            Dim available As Single = Math.Max(0.0F, maxWidthPx - overflow.WidthPx - plan.SeparatorWidthPx - (plan.SeparatorGapPx * 2.0F))
            Dim trailing As New List(Of RenderEntry)()
            Dim used As Single = 0.0F

            For i As Integer = source.Count - 1 To 0 Step -1
                Dim candidateWidth As Single = source(i).WidthPx
                If trailing.Count > 0 Then candidateWidth += plan.SeparatorWidthPx + (plan.SeparatorGapPx * 2.0F)

                If used + candidateWidth > available AndAlso trailing.Count > 0 Then Exit For
                If used + candidateWidth > available AndAlso trailing.Count = 0 Then
                    trailing.Insert(0, source(i))
                    Exit For
                End If

                trailing.Insert(0, source(i))
                used += candidateWidth
            Next

            collapsed.AddRange(trailing)
            Return collapsed
        End Function

        Private Function BuildAllEntries(ctx As MASThemeContext,
                                         plan As MASBreadcrumbLayoutPlan) As List(Of RenderEntry)
            Dim entries As New List(Of RenderEntry)()

            If _items.Count = 0 Then
                entries.Add(CreateEntry(BreadcrumbTokens.EmptyText, -1, ctx, plan))
                Return entries
            End If

            For i As Integer = 0 To _items.Count - 1
                entries.Add(CreateEntry(_items(i), i, ctx, plan))
            Next

            Return entries
        End Function

        Private Function CreateEntry(text As String,
                                     index As Integer,
                                     ctx As MASThemeContext,
                                     plan As MASBreadcrumbLayoutPlan) As RenderEntry
            If plan Is Nothing Then Return New RenderEntry With {.Text = If(text, String.Empty), .Index = index, .WidthPx = 1.0F}
            Dim widthPx As Single = MeasureTextWidthPx(ctx, text)
            widthPx = Math.Max(BreadcrumbTokens.ItemMinWidthDip * plan.MetricDpi,
                               widthPx + plan.ItemPaddingHorizontalPx * 2.0F)

            Return New RenderEntry With {
                .Text = If(text, String.Empty),
                .Index = index,
                .WidthPx = widthPx
            }
        End Function

        Private Shared Function CalculateEntryWidth(entries As List(Of RenderEntry),
                                                    plan As MASBreadcrumbLayoutPlan) As Single
            If entries Is Nothing OrElse entries.Count = 0 Then Return 0.0F

            Dim width As Single = 0.0F
            For i As Integer = 0 To entries.Count - 1
                width += entries(i).WidthPx
                If i < entries.Count - 1 Then width += If(plan Is Nothing, 0.0F, plan.SeparatorWidthPx + (plan.SeparatorGapPx * 2.0F))
            Next

            Return width
        End Function

        Private Sub DrawEntries(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                content As SKRect,
                                entries As List(Of RenderEntry),
                                plan As MASBreadcrumbLayoutPlan,
                                isRtl As Boolean)
            _lastItemRects = Array.Empty(Of SKRect)()
            _lastItemIndices = Array.Empty(Of Integer)()
            If entries Is Nothing OrElse entries.Count = 0 Then Return

            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            Dim rects As New List(Of SKRect)()
            Dim indices As New List(Of Integer)()
            Dim separatorText As String = ResolveSeparatorForDirection(isRtl)

            If isRtl Then
                Dim x As Single = content.Right
                For i As Integer = 0 To entries.Count - 1
                    Dim itemRect As New SKRect(x - entries(i).WidthPx, content.Top, x, content.Bottom)
                    DrawItem(canvas, ctx, itemRect, entries(i), plan)
                    rects.Add(itemRect)
                    indices.Add(entries(i).Index)
                    x = itemRect.Left

                    If i < entries.Count - 1 Then
                        Dim sepRect As New SKRect(x - (plan.SeparatorWidthPx + plan.SeparatorGapPx * 2.0F),
                                                  content.Top,
                                                  x,
                                                  content.Bottom)
                        DrawSeparator(canvas, ctx, sepRect, separatorText, plan)
                        x = sepRect.Left
                    End If
                Next
            Else
                Dim x As Single = content.Left
                For i As Integer = 0 To entries.Count - 1
                    Dim itemRect As New SKRect(x, content.Top, x + entries(i).WidthPx, content.Bottom)
                    DrawItem(canvas, ctx, itemRect, entries(i), plan)
                    rects.Add(itemRect)
                    indices.Add(entries(i).Index)
                    x = itemRect.Right

                    If i < entries.Count - 1 Then
                        Dim sepRect As New SKRect(x,
                                                  content.Top,
                                                  x + (plan.SeparatorWidthPx + plan.SeparatorGapPx * 2.0F),
                                                  content.Bottom)
                        DrawSeparator(canvas, ctx, sepRect, separatorText, plan)
                        x = sepRect.Right
                    End If
                Next
            End If

            DrawSelectionMotionIndicator(canvas, ctx, rects, indices, plan)

            _lastItemRects = rects.ToArray()
            _lastItemIndices = indices.ToArray()
        End Sub

        Private Sub DrawItem(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             itemRect As SKRect,
                             entry As RenderEntry,
                             plan As MASBreadcrumbLayoutPlan)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            If itemRect.Width <= 1.0F OrElse itemRect.Height <= 1.0F Then Return

            Dim snapped As SKRect = PixelSnap.SnapRect(itemRect, dpi)
            Dim selectionMotionActive As Boolean = IsSelectionMotionActive()
            Dim active As Boolean = entry.Index >= 0 AndAlso entry.Index = _selectedIndex AndAlso Not selectionMotionActive
            Dim hovered As Boolean = entry.Index >= 0 AndAlso entry.Index = _hoverIndex
            Dim pressed As Boolean = entry.Index >= 0 AndAlso entry.Index = _pressedIndex

            Dim fillAlpha As Byte = 0
            If pressed Then
                fillAlpha = BreadcrumbTokens.ItemPressedAlpha
            ElseIf active Then
                fillAlpha = BreadcrumbTokens.ItemSelectedAlpha
            ElseIf hovered Then
                fillAlpha = BreadcrumbTokens.ItemHoverAlpha
            End If

            If fillAlpha > 0 Then
                Dim fillColor As SKColor = ResolveAccentColor(ctx).WithAlpha(fillAlpha)
                Dim radius As Single = PixelSnap.SafeRadius(plan.ItemRadiusPx)
                Using paint As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Fill,
                    .Color = fillColor
                }
                    canvas.DrawRoundRect(snapped, radius, radius, paint)
                End Using
            End If

            Dim textRect As New SKRect(snapped.Left + plan.ItemPaddingHorizontalPx,
                                       snapped.Top,
                                       snapped.Right - plan.ItemPaddingHorizontalPx,
                                       snapped.Bottom)
            If textRect.Width <= 1.0F Then Return

            Dim color As SKColor = ResolveTextColor(ctx, active, entry.Index < 0, Me.Enabled)
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=entry.Text,
                bounds:=textRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Small,
                color:=color,
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

        Private Sub DrawSeparator(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  bounds As SKRect,
                                  separatorText As String,
                                  plan As MASBreadcrumbLayoutPlan)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

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

        Private Sub DrawSelectionMotionIndicator(canvas As SKCanvas,
                                                     ctx As MASThemeContext,
                                                     rects As List(Of SKRect),
                                                     indices As List(Of Integer),
                                                     plan As MASBreadcrumbLayoutPlan)
            If canvas Is Nothing OrElse ctx Is Nothing OrElse plan Is Nothing Then Return
            If Not IsSelectionMotionActive() Then Return
            If rects Is Nothing OrElse indices Is Nothing Then Return

            Dim fromRect As SKRect = FindRenderedItemRect(rects, indices, _selectionMotionFromIndex)
            Dim toRect As SKRect = FindRenderedItemRect(rects, indices, _selectionMotionToIndex)
            If fromRect.IsEmpty AndAlso toRect.IsEmpty Then Return
            If fromRect.IsEmpty Then fromRect = toRect
            If toRect.IsEmpty Then toRect = fromRect

            Dim progress As Single = MASMotionSystem.ClampProgress(_selectionMotionProgress)
            Dim rect As SKRect = PixelSnap.SnapRect(LerpRect(fromRect, toRect, progress), plan.Dpi)
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = ResolveAccentColor(ctx).WithAlpha(BreadcrumbTokens.ItemSelectedAlpha)
            }
                canvas.DrawRoundRect(rect, PixelSnap.SafeRadius(plan.ItemRadiusPx), PixelSnap.SafeRadius(plan.ItemRadiusPx), paint)
            End Using
        End Sub

        Private Sub DrawFocusRing(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  content As SKRect,
                                  plan As MASBreadcrumbLayoutPlan)
            If plan Is Nothing Then Return
            Dim dpi As Single = plan.Dpi
            If Not HasKeyboardFocus Then Return
            If _selectedIndex < 0 OrElse _lastItemRects Is Nothing Then Return

            For i As Integer = 0 To _lastItemRects.Length - 1
                If i >= _lastItemIndices.Length Then Continue For
                If _lastItemIndices(i) <> _selectedIndex Then Continue For

                Dim rect As SKRect = PixelSnap.SnapRect(_lastItemRects(i), dpi)
                rect.Inflate(plan.FocusInflatePx, plan.FocusInflatePx)
                Dim radius As Single = PixelSnap.SafeRadius(plan.ItemRadiusPx + plan.FocusInflatePx)

                Using paint As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = plan.FocusStrokePx,
                    .Color = ResolveAccentColor(ctx).WithAlpha(BreadcrumbTokens.FocusAlpha)
                }
                    canvas.DrawRoundRect(rect, radius, radius, paint)
                End Using

                Exit For
            Next
        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 Return

            _hoverIndex = hit
            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 < 0 Then Return False

            RequestFocus()
            _pressedIndex = hit
            _hoverIndex = hit
            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 >= 0 AndAlso pressed = released Then
                SetSelectedIndexInternal(released, True)
                InvalidateVisual()
                Return True
            End If

            If pressed >= 0 Then
                InvalidateVisual()
                Return True
            End If

            Return False
        End Function

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

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            ResetPointerState()
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            ResetPointerState()
            InvalidateVisual()
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If _items.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(_items.Count - 1, True)
                    Return True

                Case Keys.Enter, Keys.Space
                    SetSelectedIndexInternal(_selectedIndex, True)
                    Return True
            End Select

            Return False
        End Function

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

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

#End Region

#Region "Helpers"

        Private Structure RenderEntry
            Friend Text As String
            Friend Index As Integer
            Friend WidthPx As Single
        End Structure

        Private Function HitIndex(ptPx As SKPoint) As Integer
            If _lastItemRects Is Nothing OrElse _lastItemIndices Is Nothing Then Return -1

            For i As Integer = 0 To _lastItemRects.Length - 1
                If i >= _lastItemIndices.Length Then Exit For
                If _lastItemIndices(i) < 0 Then Continue For
                If _lastItemRects(i).Contains(ptPx.X, ptPx.Y) Then Return _lastItemIndices(i)
            Next

            Return -1
        End Function

        Private Sub MoveSelection(delta As Integer)
            If _items.Count <= 0 Then Return
            SetSelectedIndexInternal(MASBreadcrumbPathPolicy.ResolveMoveIndex(_selectedIndex, delta, _items.Count), True)
        End Sub

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

            Dim previous As Integer = _selectedIndex
            _selectedIndex = normalized
            StartSelectionMotion(previous, normalized)
            If raiseChanged Then RaiseSelectedIndexChangedSafe()
            InvalidateVisual()
        End Sub

        Private Sub StartSelectionMotion(previousIndex As Integer,
                                         nextIndex As Integer)
            StopSelectionMotion()

            If previousIndex < 0 OrElse nextIndex < 0 OrElse previousIndex = nextIndex Then
                _hasSelectionMotion = False
                _selectionMotionProgress = 1.0F
                Return
            End If

            _selectionMotionFromIndex = previousIndex
            _selectionMotionToIndex = nextIndex
            _selectionMotionProgress = 0.0F
            _hasSelectionMotion = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.IndicatorMove, 0.0F, 1.0F)
            If Not plan.ShouldAnimate Then
                CompleteSelectionMotionFrame(Nothing)
                Return
            End If

            _selectionMotionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASBreadcrumb.SelectionIndicator",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplySelectionMotionFrame,
                completed:=AddressOf CompleteSelectionMotionFrame)
            _selectionMotionRunner.Start()
        End Sub

        Private Sub ApplySelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _selectionMotionProgress = MASMotionSystem.ClampProgress(frame.Value)
            InvalidateVisual()
        End Sub

        Private Sub CompleteSelectionMotionFrame(frame As MASMotionFrame)
            _selectionMotionProgress = 1.0F
            _hasSelectionMotion = False
            _selectionMotionRunner = Nothing
            InvalidateVisual()
        End Sub

        Private Sub StopSelectionMotion()
            If _selectionMotionRunner IsNot Nothing Then
                _selectionMotionRunner.Dispose()
                _selectionMotionRunner = Nothing
            End If

            _hasSelectionMotion = False
            _selectionMotionProgress = 1.0F
        End Sub

        Private Function IsSelectionMotionActive() As Boolean
            Return _hasSelectionMotion AndAlso _selectionMotionRunner IsNot Nothing AndAlso _selectionMotionRunner.IsRunning
        End Function

        Private Shared Function FindRenderedItemRect(rects As List(Of SKRect),
                                                     indices As List(Of Integer),
                                                     targetIndex As Integer) As SKRect
            If rects Is Nothing OrElse indices Is Nothing Then Return SKRect.Empty
            If targetIndex < 0 Then Return SKRect.Empty

            Dim count As Integer = Math.Min(rects.Count, indices.Count)
            For i As Integer = 0 To count - 1
                If indices(i) = targetIndex Then Return rects(i)
            Next

            Return SKRect.Empty
        End Function

        Private Shared Function LerpRect(fromRect As SKRect,
                                         toRect As SKRect,
                                         progress As Single) As SKRect
            progress = MASMotionSystem.ClampProgress(progress)
            Return New SKRect(
                LerpSingle(fromRect.Left, toRect.Left, progress),
                LerpSingle(fromRect.Top, toRect.Top, progress),
                LerpSingle(fromRect.Right, toRect.Right, progress),
                LerpSingle(fromRect.Bottom, toRect.Bottom, progress))
        End Function

        Private Shared Function LerpSingle(fromValue As Single,
                                           toValue As Single,
                                           progress As Single) As Single
            Return fromValue + ((toValue - fromValue) * progress)
        End Function

        Private Function NormalizeIndex(value As Integer) As Integer
            Return MASBreadcrumbPathPolicy.NormalizeSelectedIndex(value, _items.Count)
        End Function

        Private Sub NormalizeSelectionAfterCollectionChange()
            _selectedIndex = MASBreadcrumbPathPolicy.NormalizeSelectedIndex(_selectedIndex, _items.Count)
        End Sub

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

        Private Sub RaiseSelectedIndexChangedSafe()
            Try
                RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException1, "MASBreadcrumb.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

        Friend Shared Function CreateBreadcrumbReadinessManifest() As MASBreadcrumbReadinessManifest
            Return MASBreadcrumbReadinessManifest.CreateCurrent()
        End Function

        Private Shared Function ResolveRightToLeft() As Boolean
            Return MASBreadcrumbPathPolicy.ResolveRightToLeft()
        End Function

        Private Function ResolveSeparatorForDirection(isRtl As Boolean) As String
            Return MASBreadcrumbPathPolicy.ResolveSeparatorForDirection(_separatorText, isRtl)
        End Function

        Private Shared Function NormalizeItemText(value As String) As String
            Return MASBreadcrumbPathPolicy.NormalizeItemText(value)
        End Function

        Private Shared Function NormalizeSeparatorText(value As String) As String
            Return MASBreadcrumbPathPolicy.NormalizeSeparatorText(value)
        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 ResolveTextColor(ctx As MASThemeContext,
                                                 active As Boolean,
                                                 overflow As Boolean,
                                                 enabled As Boolean) As SKColor
            If ctx Is Nothing Then Return SKColors.Gray
            If Not enabled Then Return ResolveMutedTextColor(ctx).WithAlpha(BreadcrumbTokens.DisabledTextAlpha)
            If overflow Then Return ResolveMutedTextColor(ctx).WithAlpha(BreadcrumbTokens.OverflowTextAlpha)
            If active Then Return ResolveAccentColor(ctx).WithAlpha(BreadcrumbTokens.TextAlpha)
            Return ResolvePrimaryTextColor(ctx).WithAlpha(BreadcrumbTokens.TextAlpha)
        End Function

        Private Shared Function ResolveSeparatorColor(ctx As MASThemeContext) As SKColor
            Return ResolveMutedTextColor(ctx).WithAlpha(BreadcrumbTokens.SeparatorAlpha)
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If _disposedLocal Then Return
            _disposedLocal = True
            StopSelectionMotion()
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
