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.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 visual timeline surface for activity logs, order history, audit
    ''' trails, workflow progress, and business event summaries. It renders application-owned
    ''' items with premium Nexamas UI material, typography, focus, keyboard, pointer, and RTL
    ''' behavior. Activity persistence, audit storage, workflow execution, schedulers, and live
    ''' feeds remain outside this visual control.
    ''' </summary>
    Public NotInheritable Class MASTimeline
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private NotInheritable Class TimelineEntry
            Friend Sub New(title As String,
                           detail As String,
                           timestampText As String,
                           state As MASTimelineItemState)
                Me.Title = NormalizeText(title, "Timeline item")
                Me.Detail = NormalizeText(detail, String.Empty)
                Me.TimestampText = NormalizeText(timestampText, String.Empty)
                Me.State = MASTimelineVisualPolicy.NormalizeState(state)
            End Sub

            Friend ReadOnly Property Title As String
            Friend ReadOnly Property Detail As String
            Friend ReadOnly Property TimestampText As String
            Friend ReadOnly Property State As MASTimelineItemState
        End Class

        Private ReadOnly _items As New List(Of TimelineEntry)()
        Private ReadOnly _hitRects As New List(Of SKRect)()
        Private _title As String = TimelineTokens.DefaultTitle
        Private _description As String = TimelineTokens.DefaultDescription
        Private _compact As Boolean
        Private _selectedIndex As Integer = -1
        Private _hoveredIndex As Integer = -1
        Private _pressedIndex As Integer = -1
        Private _disposedLocal As Boolean

#End Region

#Region "Constructor / Factory"

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

        Public Shared Function Create(Optional title As String = Nothing,
                                      Optional description As String = Nothing) As MASTimeline
            Dim control As New MASTimeline()
            control._title = NormalizeText(title, TimelineTokens.DefaultTitle)
            control._description = NormalizeText(description, TimelineTokens.DefaultDescription)
            Return control
        End Function

#End Region

#Region "Public API"

        Public Event ItemsChanged As EventHandler
        Public Event SelectedIndexChanged As EventHandler
        Public Event ItemClicked As EventHandler

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeText(value, TimelineTokens.DefaultTitle)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return
                _title = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.Title")
                InvalidateVisual()
            End Set
        End Property

        Public Property Description As String
            Get
                Return _description
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeText(value, String.Empty)
                If String.Equals(_description, normalized, StringComparison.Ordinal) Then Return
                _description = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.Description")
                InvalidateVisual()
            End Set
        End Property

        Public Property Compact As Boolean
            Get
                Return _compact
            End Get
            Set(value As Boolean)
                If _compact = value Then Return
                _compact = value
                RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.Compact")
                InvalidateVisual()
            End Set
        End Property

        Public Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
            Set(value As Integer)
                Dim normalized As Integer = NormalizeSelection(value)
                If _selectedIndex = normalized Then Return
                _selectedIndex = normalized
                RaiseSelectedIndexChangedSafe()
                InvalidateVisual()
            End Set
        End Property

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

        Public Function AddItem(title As String,
                                Optional detail As String = "",
                                Optional timestampText As String = "",
                                Optional state As MASTimelineItemState = MASTimelineItemState.Normal) As MASTimeline
            If _items.Count >= MASTimelineVisualPolicy.NormalizeMaxItems(TimelineTokens.MaxItems) Then Return Me
            _items.Add(New TimelineEntry(title, detail, timestampText, state))
            If _selectedIndex < 0 AndAlso _items.Count = 1 Then _selectedIndex = 0
            RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.AddItem")
            RaiseItemsChangedSafe()
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearItems() As MASTimeline
            If _items.Count = 0 Then Return Me
            _items.Clear()
            _hitRects.Clear()
            _hoveredIndex = -1
            _pressedIndex = -1
            If _selectedIndex <> -1 Then
                _selectedIndex = -1
                RaiseSelectedIndexChangedSafe()
            End If
            RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.ClearItems")
            RaiseItemsChangedSafe()
            InvalidateVisual()
            Return Me
        End Function

        Public Function RemoveItemAt(index As Integer) As MASTimeline
            If index < 0 OrElse index >= _items.Count Then Return Me
            _items.RemoveAt(index)
            _hoveredIndex = -1
            _pressedIndex = -1
            Dim normalized As Integer = NormalizeSelection(_selectedIndex)
            If _selectedIndex = index Then
                _selectedIndex = Math.Min(index, _items.Count - 1)
                If _items.Count = 0 Then _selectedIndex = -1
                RaiseSelectedIndexChangedSafe()
            ElseIf _selectedIndex <> normalized Then
                _selectedIndex = normalized
                RaiseSelectedIndexChangedSafe()
            End If
            RequestSizeLayoutRefreshForSizeAffectingChange("MASTimeline.RemoveItemAt")
            RaiseItemsChangedSafe()
            InvalidateVisual()
            Return Me
        End Function

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

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

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

        Public Function GetItemState(index As Integer) As MASTimelineItemState
            If index < 0 OrElse index >= _items.Count Then Return MASTimelineItemState.Normal
            Return _items(index).State
        End Function

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

        Public Function WithDescription(value As String) As MASTimeline
            Description = value
            Return Me
        End Function

        Public Function WithCompact(Optional value As Boolean = True) As MASTimeline
            Compact = value
            Return Me
        End Function

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

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASTimeline
            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 desiredHeight As Single = CalculateDesiredHeightDip()
            Dim desired As New SKSize(TimelineTokens.PreferredWidthDip, desiredHeight)
            Dim minimum As New SKSize(TimelineTokens.MinWidthDip, TimelineTokens.MinHeightDip)
            Dim contentInset As New MASLayoutInset(TimelineTokens.PaddingHorizontalDip,
                                                   TimelineTokens.PaddingVerticalDip,
                                                   TimelineTokens.PaddingHorizontalDip,
                                                   TimelineTokens.PaddingVerticalDip)

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

        Private Function CalculateDesiredHeightDip() As Single
            Dim height As Single = TimelineTokens.PaddingVerticalDip * 2.0F
            height += 24.0F
            If _description.Length > 0 Then height += TimelineTokens.HeaderGapDip + 20.0F
            height += TimelineTokens.HeaderBottomGapDip
            Dim count As Integer = Math.Max(1, _items.Count)
            height += count * ResolveRowHeightDip()
            height += Math.Max(0, count - 1) * TimelineTokens.RowGapDip
            Return Math.Max(TimelineTokens.MinHeightDip, height)
        End Function

        Private Function ResolveRowHeightDip() As Single
            Return MASTimelineVisualPolicy.ResolveRowHeightDip(_compact)
        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

            _contract.AssertConsumable()

            Dim pixelDpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, pixelDpi)
            Dim plan As MASTimelineLayoutPlan = MASTimelineLayoutPlan.Create(ctx, bounds, SizeIntent)
            Dim dpi As Single = plan.MetricDpi
            Dim radius As Single = PixelSnap.SafeRadius(TimelineTokens.SurfaceRadiusDip * dpi)

            DrawSurface(canvas, ctx, bounds, radius, dpi)

            Dim content As SKRect = plan.ContentRectPx
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            Dim y As Single = DrawHeader(canvas, ctx, content, dpi)
            DrawItems(canvas, ctx, New SKRect(content.Left, y, content.Right, content.Bottom), dpi, ResolveRightToLeft())
        End Sub

        Private Sub DrawSurface(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                bounds As SKRect,
                                radius As Single,
                                dpi As Single)
            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = ResolveSurfaceColor(ctx).WithAlpha(TimelineTokens.SurfaceAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, paint)
            End Using

            Using stroke As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, TimelineTokens.BorderStrokeDip * dpi),
                .Color = ResolveAccentColor(ctx).WithAlpha(TimelineTokens.BorderAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, stroke)
            End Using
        End Sub

        Private Function DrawHeader(canvas As SKCanvas,
                                    ctx As MASThemeContext,
                                    content As SKRect,
                                    dpi As Single) As Single
            Dim titleRect As New SKRect(content.Left, content.Top, content.Right, content.Top + 26.0F * dpi)
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=_title,
                bounds:=titleRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Title,
                color:=ResolvePrimaryTextColor(ctx).WithAlpha(TimelineTokens.TextAlpha),
                align:=TypographyTextPrimitives.TextAlign.Left,
                drawShadow:=False)

            Dim y As Single = titleRect.Bottom
            If _description.Length > 0 Then
                y += TimelineTokens.HeaderGapDip * dpi
                Dim descriptionRect As New SKRect(content.Left, y, content.Right, y + 20.0F * dpi)
                TypographyTextPrimitives.DrawSingleLineText(
                    canvas:=canvas,
                    ctx:=ctx,
                    text:=_description,
                    bounds:=descriptionRect,
                    dpi:=dpi,
                    style:=MASTypography.MASTextStyle.Small,
                    color:=ResolveMutedTextColor(ctx).WithAlpha(TimelineTokens.MutedTextAlpha),
                    align:=TypographyTextPrimitives.TextAlign.Left,
                    drawShadow:=False)
                y = descriptionRect.Bottom
            End If

            Return y + TimelineTokens.HeaderBottomGapDip * dpi
        End Function

        Private Sub DrawItems(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              itemsBounds As SKRect,
                              dpi As Single,
                              isRtl As Boolean)
            _hitRects.Clear()

            Dim rowHeight As Single = ResolveRowHeightDip() * dpi
            Dim y As Single = itemsBounds.Top

            If _items.Count = 0 Then
                Dim emptyRect As New SKRect(itemsBounds.Left, y, itemsBounds.Right, Math.Min(itemsBounds.Bottom, y + rowHeight))
                _hitRects.Add(emptyRect)
                DrawEmptyRow(canvas, ctx, emptyRect, dpi)
                Return
            End If

            Dim railX As Single = If(isRtl, itemsBounds.Right - TimelineTokens.RailOffsetDip * dpi, itemsBounds.Left + TimelineTokens.RailOffsetDip * dpi)
            Dim firstCenterY As Single = y + rowHeight / 2.0F
            Dim lastCenterY As Single = firstCenterY + Math.Max(0, _items.Count - 1) * (rowHeight + TimelineTokens.RowGapDip * dpi)
            DrawRail(canvas, ctx, railX, firstCenterY, Math.Min(lastCenterY, itemsBounds.Bottom - rowHeight / 2.0F), dpi)

            For i As Integer = 0 To _items.Count - 1
                If y + rowHeight > itemsBounds.Bottom + 0.5F Then Exit For
                Dim rowRect As New SKRect(itemsBounds.Left, y, itemsBounds.Right, y + rowHeight)
                _hitRects.Add(rowRect)
                DrawTimelineItem(canvas, ctx, rowRect, railX, _items(i), i, dpi, isRtl)
                y = rowRect.Bottom + TimelineTokens.RowGapDip * dpi
            Next
        End Sub

        Private Sub DrawRail(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             railX As Single,
                             topY As Single,
                             bottomY As Single,
                             dpi As Single)
            If bottomY <= topY Then Return
            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, TimelineTokens.RailWidthDip * dpi),
                .StrokeCap = SKStrokeCap.Round,
                .Color = ResolveAccentColor(ctx).WithAlpha(TimelineTokens.RailAlpha)
            }
                canvas.DrawLine(railX, topY, railX, bottomY, paint)
            End Using
        End Sub

        Private Sub DrawTimelineItem(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     rowRect As SKRect,
                                     railX As Single,
                                     entry As TimelineEntry,
                                     index As Integer,
                                     dpi As Single,
                                     isRtl As Boolean)
            Dim snapped As SKRect = PixelSnap.SnapRect(rowRect, dpi)
            Dim selected As Boolean = index = _selectedIndex
            Dim hovered As Boolean = index = _hoveredIndex
            Dim rowAlpha As Byte = If(selected, TimelineTokens.RowSelectedAlpha, If(hovered, TimelineTokens.RowHoverAlpha, TimelineTokens.RowFillAlpha))
            Dim radius As Single = PixelSnap.SafeRadius(TimelineTokens.RowRadiusDip * dpi)

            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveSurfaceColor(ctx).WithAlpha(rowAlpha)}
                canvas.DrawRoundRect(snapped, radius, radius, paint)
            End Using

            Dim markerCenter As New SKPoint(railX, snapped.MidY)
            DrawMarker(canvas, ctx, markerCenter, entry.State, selected, dpi)

            Dim contentLeft As Single
            Dim contentRight As Single
            If isRtl Then
                contentLeft = snapped.Left + TimelineTokens.RowPaddingHorizontalDip * dpi
                contentRight = railX - TimelineTokens.ContentGapDip * dpi
            Else
                contentLeft = railX + TimelineTokens.ContentGapDip * dpi
                contentRight = snapped.Right - TimelineTokens.RowPaddingHorizontalDip * dpi
            End If

            If contentRight <= contentLeft + 4.0F * dpi Then Return

            Dim inner As New SKRect(contentLeft,
                                    snapped.Top + TimelineTokens.RowPaddingVerticalDip * dpi,
                                    contentRight,
                                    snapped.Bottom - TimelineTokens.RowPaddingVerticalDip * dpi)
            Dim timestampWidth As Single = If(entry.TimestampText.Length > 0, Math.Min(TimelineTokens.TimestampWidthDip * dpi, inner.Width * 0.34F), 0.0F)
            Dim titleRect As SKRect
            Dim stampRect As SKRect
            If isRtl Then
                stampRect = New SKRect(inner.Left, inner.Top, inner.Left + timestampWidth, inner.Top + 19.0F * dpi)
                titleRect = New SKRect(inner.Left + timestampWidth + 8.0F * dpi, inner.Top, inner.Right, inner.Top + 22.0F * dpi)
            Else
                titleRect = New SKRect(inner.Left, inner.Top, inner.Right - timestampWidth - 8.0F * dpi, inner.Top + 22.0F * dpi)
                stampRect = New SKRect(inner.Right - timestampWidth, inner.Top, inner.Right, inner.Top + 19.0F * dpi)
            End If

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=entry.Title,
                bounds:=titleRect,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=ResolveItemTextColor(ctx, entry.State),
                align:=If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left),
                drawShadow:=False)

            If entry.TimestampText.Length > 0 AndAlso timestampWidth > 1.0F Then
                TypographyTextPrimitives.DrawSingleLineText(
                    canvas:=canvas,
                    ctx:=ctx,
                    text:=entry.TimestampText,
                    bounds:=stampRect,
                    dpi:=dpi,
                    style:=MASTypography.MASTextStyle.Micro,
                    color:=ResolveMutedTextColor(ctx).WithAlpha(TimelineTokens.MutedTextAlpha),
                    align:=If(isRtl, TypographyTextPrimitives.TextAlign.Left, TypographyTextPrimitives.TextAlign.Right),
                    drawShadow:=False)
            End If

            If entry.Detail.Length > 0 AndAlso Not _compact Then
                Dim detailRect As New SKRect(inner.Left, titleRect.Bottom + 2.0F * dpi, inner.Right, inner.Bottom)
                TypographyTextPrimitives.DrawSingleLineText(
                    canvas:=canvas,
                    ctx:=ctx,
                    text:=entry.Detail,
                    bounds:=detailRect,
                    dpi:=dpi,
                    style:=MASTypography.MASTextStyle.Small,
                    color:=ResolveMutedTextColor(ctx).WithAlpha(If(entry.State = MASTimelineItemState.Disabled, TimelineTokens.DisabledTextAlpha, TimelineTokens.MutedTextAlpha)),
                    align:=If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left),
                    drawShadow:=False)
            End If
        End Sub

        Private Sub DrawMarker(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               center As SKPoint,
                               state As MASTimelineItemState,
                               selected As Boolean,
                               dpi As Single)
            Dim sizeDip As Single = If(state = MASTimelineItemState.Current OrElse selected, TimelineTokens.CurrentMarkerSizeDip, TimelineTokens.MarkerSizeDip)
            Dim radius As Single = Math.Max(3.0F, sizeDip * dpi / 2.0F)
            Dim color As SKColor = ResolveStateColor(ctx, state)

            If MASTimelineVisualPolicy.ShouldDrawActiveMarkerRing(state, selected) Then
                DrawActiveMarkerRing(canvas, ctx, center, color, sizeDip, dpi)
            End If

            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = color.WithAlpha(TimelineTokens.MarkerFillAlpha)}
                canvas.DrawCircle(center, radius, fill)
            End Using

            Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, TimelineTokens.MarkerStrokeDip * dpi), .Color = ResolveSurfaceColor(ctx).WithAlpha(TimelineTokens.MarkerStrokeAlpha)}
                canvas.DrawCircle(center, radius, stroke)
            End Using
        End Sub

        Private Sub DrawActiveMarkerRing(canvas As SKCanvas,
                                         ctx As MASThemeContext,
                                         center As SKPoint,
                                         markerColor As SKColor,
                                         markerSizeDip As Single,
                                         dpi As Single)
            Dim ringSizeDip As Single = MASTimelineVisualPolicy.ResolveActiveMarkerRingSizeDip(markerSizeDip)
            Dim ringRadius As Single = Math.Max(5.0F, ringSizeDip * dpi / 2.0F)
            Dim ringStroke As Single = Math.Max(1.0F, TimelineTokens.ActiveMarkerRingStrokeDip * dpi)

            Using railCutout As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = ResolveSurfaceColor(ctx).WithAlpha(TimelineTokens.ActiveMarkerRailCutoutAlpha)
            }
                canvas.DrawCircle(center, ringRadius + ringStroke, railCutout)
            End Using

            Using ring As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = ringStroke,
                .Color = markerColor.WithAlpha(TimelineTokens.ActiveMarkerRingAlpha)
            }
                canvas.DrawCircle(center, ringRadius, ring)
            End Using
        End Sub

        Private Sub DrawEmptyRow(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 rowRect As SKRect,
                                 dpi As Single)
            Dim snapped As SKRect = PixelSnap.SnapRect(rowRect, dpi)
            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveSurfaceColor(ctx).WithAlpha(TimelineTokens.RowFillAlpha)}
                canvas.DrawRoundRect(snapped, TimelineTokens.RowRadiusDip * dpi, TimelineTokens.RowRadiusDip * dpi, paint)
            End Using

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=TimelineTokens.EmptyText,
                bounds:=snapped,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Body,
                color:=ResolveMutedTextColor(ctx).WithAlpha(TimelineTokens.MutedTextAlpha),
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

#End Region

#Region "Input"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim index As Integer = HitTest(ptPx)
            If _hoveredIndex = index Then Return
            _hoveredIndex = 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
            _pressedIndex = HitTest(ptPx)
            If _pressedIndex >= 0 Then
                InvalidateVisual()
                Return True
            End If
            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 index As Integer = HitTest(ptPx)
            Dim shouldClick As Boolean = index >= 0 AndAlso index = _pressedIndex
            _pressedIndex = -1
            If shouldClick Then
                SelectedIndex = index
                RaiseItemClickedSafe()
            End If
            InvalidateVisual()
            Return True
        End Function

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

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

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

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If _items.Count = 0 Then Return False
            Dim selectionDelta As Integer = MASTimelineVisualPolicy.ResolveKeyboardSelectionDelta(keyCode, ResolveRightToLeft())
            If selectionDelta <> 0 Then
                SelectedIndex = MASTimelineVisualPolicy.NormalizeSelectedIndex(Math.Max(0, _selectedIndex) + selectionDelta, _items.Count)
                Return True
            End If

            Select Case keyCode
                Case Keys.Home
                    SelectedIndex = 0
                    Return True
                Case Keys.End
                    SelectedIndex = _items.Count - 1
                    Return True
                Case Keys.Enter, Keys.Space
                    If _selectedIndex >= 0 Then RaiseItemClickedSafe()
                    Return True
            End Select
            Return False
        End Function

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return MASCompositeContentFocusPolicy.CanReceiveKeyboardFocus(_contract, Visible, Enabled)
        End Function

        Protected Overrides Function WantsPointerFocus() As Boolean
            Return MASCompositeContentFocusPolicy.AllowsPointerFocusForCompositeContent(_contract, Visible, Enabled)
        End Function

#End Region

#Region "Helpers"

        Private Function HitTest(ptPx As SKPoint) As Integer
            If _items.Count = 0 Then Return -1
            For i As Integer = 0 To _hitRects.Count - 1
                If i >= _items.Count Then Exit For
                Dim rect As SKRect = _hitRects(i)
                If ptPx.X >= rect.Left AndAlso ptPx.X <= rect.Right AndAlso ptPx.Y >= rect.Top AndAlso ptPx.Y <= rect.Bottom Then Return i
            Next
            Return -1
        End Function

        Private Function NormalizeSelection(value As Integer) As Integer
            Return MASTimelineVisualPolicy.NormalizeSelectedIndex(value, _items.Count)
        End Function

        Private Sub RaiseItemsChangedSafe()
            Try
                RaiseEvent ItemsChanged(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException1, "MASTimeline.ItemsChanged")
            End Try
        End Sub

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

        Private Sub RaiseItemClickedSafe()
            Try
                RaiseEvent ItemClicked(Me, EventArgs.Empty)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException3, "MASTimeline.ItemClicked")
            End Try
        End Sub

        Friend Shared Function CreateTimelineReadinessManifest() As MASTimelineReadinessManifest
            Return MASTimelineReadinessManifest.CreateCurrent()
        End Function

        Private Shared Function NormalizeText(value As String,
                                              fallback As String) As String
            Return MASTimelineVisualPolicy.NormalizeText(value, fallback)
        End Function

        Private Shared Function ResolveRightToLeft() As Boolean
            Return MASTimelineVisualPolicy.ResolveTextIsRtl(String.Empty)
        End Function

        Private Shared Function ResolveSurfaceColor(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.SurfaceTheme IsNot Nothing Then Return ctx.SurfaceTheme.SurfaceMid
            Return New SKColor(255, 255, 255)
        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 ResolveStateColor(ctx As MASThemeContext,
                                                  state As MASTimelineItemState) As SKColor
            If ctx IsNot Nothing AndAlso ctx.SemanticTheme IsNot Nothing Then
                Select Case state
                    Case MASTimelineItemState.Completed
                        Return ctx.SemanticTheme.Success
                    Case MASTimelineItemState.Current
                        Return ResolveAccentColor(ctx)
                    Case MASTimelineItemState.Warning
                        Return ctx.SemanticTheme.Warning
                    Case MASTimelineItemState.[Error]
                        Return ctx.SemanticTheme.Danger
                    Case MASTimelineItemState.Disabled
                        Return ResolveMutedTextColor(ctx)
                    Case Else
                        Return ResolveAccentColor(ctx)
                End Select
            End If

            Select Case state
                Case MASTimelineItemState.Completed
                    Return SKColors.SeaGreen
                Case MASTimelineItemState.Warning
                    Return SKColors.Goldenrod
                Case MASTimelineItemState.[Error]
                    Return SKColors.IndianRed
                Case MASTimelineItemState.Disabled
                    Return SKColors.Gray
                Case Else
                    Return SKColors.DodgerBlue
            End Select
        End Function

        Private Shared Function ResolveItemTextColor(ctx As MASThemeContext,
                                                     state As MASTimelineItemState) As SKColor
            If state = MASTimelineItemState.Disabled Then Return ResolveMutedTextColor(ctx).WithAlpha(TimelineTokens.DisabledTextAlpha)
            Return ResolvePrimaryTextColor(ctx).WithAlpha(TimelineTokens.TextAlpha)
        End Function

#End Region

#Region "Dispose"

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

#End Region

    End Class

End Namespace
