Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
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.TextInput
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Defines whether a MASFormValidationSummary is only a passive information surface
    ''' or an actively navigable summary where row focus/selection feedback is meaningful.
    ''' </summary>
    Public Enum MASFormValidationSummaryInteractionMode
        Passive = 0
        Navigable = 1
    End Enum

    ''' <summary>
    ''' Official Nexamas UI premium form-validation summary surface. It owns visual
    ''' summary presentation, semantic message rows, counts, RTL-aware layout, and
    ''' MAS surface / typography routing. Validation rules, binding, submit flow,
    ''' persistence, native validation hosts, and mouse-click outer focus rings remain
    ''' outside this control.
    ''' </summary>
    Partial Public NotInheritable Class MASFormValidationSummary
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Nested state"

        Private NotInheritable Class SummaryMessageState
            Friend Sub New(fieldLabel As String,
                           message As String,
                           state As MASTextValidationState)
                Me.FieldLabel = fieldLabel
                Me.Message = message
                Me.State = state
            End Sub

            Friend ReadOnly Property FieldLabel As String
            Friend ReadOnly Property Message As String
            Friend ReadOnly Property State As MASTextValidationState
        End Class

#End Region

#Region "Fields"

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

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _messages As New List(Of SummaryMessageState)()
        Private _title As String = FormValidationSummaryTokens.DefaultTitle
        Private _interactionMode As MASFormValidationSummaryInteractionMode = MASFormValidationSummaryInteractionMode.Navigable
        Private _topMessageIndex As Integer
        Private _selectedMessageIndex As Integer = -1

#End Region

#Region "Constructor / Factory"

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

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

#End Region

#Region "Public API"

        Public Event FormValidationSummaryChanged As EventHandler

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

        Public ReadOnly Property MessageCount As Integer
            Get
                Return _messages.Count
            End Get
        End Property

        Public ReadOnly Property ErrorCount As Integer
            Get
                Dim count As Integer = 0
                For Each item As SummaryMessageState In _messages
                    If item.State = MASTextValidationState.Error Then count += 1
                Next
                Return count
            End Get
        End Property

        Public ReadOnly Property WarningCount As Integer
            Get
                Dim count As Integer = 0
                For Each item As SummaryMessageState In _messages
                    If item.State = MASTextValidationState.Warning Then count += 1
                Next
                Return count
            End Get
        End Property

        Public ReadOnly Property SummaryText As String
            Get
                Return MASFormValidationSummaryPolicy.BuildSummary(MessageCount, ErrorCount, WarningCount)
            End Get
        End Property

        Public Property InteractionMode As MASFormValidationSummaryInteractionMode
            Get
                Return _interactionMode
            End Get
            Set(value As MASFormValidationSummaryInteractionMode)
                Dim normalized As MASFormValidationSummaryInteractionMode = NormalizeInteractionMode(value)
                If _interactionMode = normalized Then Return
                _interactionMode = normalized
                If _interactionMode = MASFormValidationSummaryInteractionMode.Passive Then
                    _selectedMessageIndex = -1
                End If
                NormalizeScrollState()
                InvalidateVisual()
                RaiseFormValidationSummaryChangedSafe()
            End Set
        End Property

        Friend ReadOnly Property SelectedMessageIndex As Integer
            Get
                NormalizeScrollState()
                Return _selectedMessageIndex
            End Get
        End Property

        Friend Function ScrollToMessage(index As Integer) As Boolean
            If _messages.Count = 0 Then Return False
            Dim normalized As Integer = MASFormValidationSummaryPolicy.ClampIndex(index, _messages.Count)
            If normalized < 0 Then Return False
            Dim previousIndex As Integer = _selectedMessageIndex
            Dim changed As Boolean = normalized <> previousIndex
            _selectedMessageIndex = normalized
            _topMessageIndex = MASFormValidationSummaryPolicy.EnsureVisibleIndex(_selectedMessageIndex, _topMessageIndex, _messages.Count, FormValidationSummaryTokens.MaxVisibleMessages)
            If changed Then StartFormValidationSummarySelectionMotion(previousIndex, _selectedMessageIndex)
            InvalidateVisual()
            Return changed
        End Function

        Friend Function ScrollNext() As Boolean
            NormalizeScrollState()
            Return ScrollToMessage(_selectedMessageIndex + 1)
        End Function

        Friend Function ScrollPrevious() As Boolean
            NormalizeScrollState()
            Return ScrollToMessage(_selectedMessageIndex - 1)
        End Function

        Public Function AddMessage(fieldLabel As String,
                                   message As String,
                                   Optional state As MASTextValidationState = MASTextValidationState.Error) As MASFormValidationSummary
            If _messages.Count >= FormValidationSummaryTokens.MaxMessages Then Return Me
            _messages.Add(New SummaryMessageState(
                MASFormValidationSummaryPolicy.NormalizeFieldLabel(fieldLabel),
                MASFormValidationSummaryPolicy.NormalizeMessage(message),
                MASFormValidationSummaryPolicy.NormalizeState(state)))
            If _selectedMessageIndex < 0 Then _selectedMessageIndex = 0
            NormalizeScrollState()
            StartFormValidationSummaryMutationMotion()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASFormValidationSummary.AddMessage")
            InvalidateVisual()
            RaiseFormValidationSummaryChangedSafe()
            Return Me
        End Function

        Public Function ClearMessages() As MASFormValidationSummary
            If _messages.Count = 0 Then Return Me
            _messages.Clear()
            StopFormValidationSummarySelectionMotion(resetSelection:=True)
            StopFormValidationSummaryMutationMotion(resetMutation:=True)
            _topMessageIndex = 0
            _selectedMessageIndex = -1
            RequestSizeLayoutRefreshForSizeAffectingChange("MASFormValidationSummary.ClearMessages")
            InvalidateVisual()
            RaiseFormValidationSummaryChangedSafe()
            Return Me
        End Function

        Friend Function ApplyFormValidationSummaryInternal(summary As Nexamas.UI.FormValidation.MASFormValidationSummary) As MASFormValidationSummary
            _messages.Clear()
            StopFormValidationSummarySelectionMotion(resetSelection:=True)
            StopFormValidationSummaryMutationMotion(resetMutation:=True)
            _topMessageIndex = 0
            _selectedMessageIndex = -1

            If summary IsNot Nothing Then
                For Each message As Nexamas.UI.FormValidation.MASFormValidationMessage In summary.Messages
                    If message Is Nothing Then Continue For
                    If _messages.Count >= FormValidationSummaryTokens.MaxMessages Then Exit For
                    Dim state As MASTextValidationState = If(message.IsError, MASTextValidationState.Error, MASTextValidationState.Warning)
                    _messages.Add(New SummaryMessageState(
                        MASFormValidationSummaryPolicy.NormalizeFieldLabel(message.FieldLabel),
                        MASFormValidationSummaryPolicy.NormalizeMessage(message.Message),
                        state))
                Next
            End If

            If _messages.Count > 0 Then _selectedMessageIndex = 0
            NormalizeScrollState()
            StartFormValidationSummaryMutationMotion()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASFormValidationSummary.ApplyFormValidationSummary")
            InvalidateVisual()
            RaiseFormValidationSummaryChangedSafe()
            Return Me
        End Function

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

        Public Function WithInteractionMode(mode As MASFormValidationSummaryInteractionMode) As MASFormValidationSummary
            InteractionMode = mode
            Return Me
        End Function

        Public Function AsPassiveInformation() As MASFormValidationSummary
            Return WithInteractionMode(MASFormValidationSummaryInteractionMode.Passive)
        End Function

        Public Function AsNavigableSummary() As MASFormValidationSummary
            Return WithInteractionMode(MASFormValidationSummaryInteractionMode.Navigable)
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASFormValidationSummary
            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 = FormValidationSummaryTokens.ResolveDesiredHeightDip(MessageCount)
            Dim minimumHeight As Single = Math.Max(FormValidationSummaryTokens.MinHeightDip,
                                                   FormValidationSummaryTokens.ResolveDesiredHeightDip(0))
            Dim desired As New SKSize(FormValidationSummaryTokens.PreferredWidthDip, Math.Max(FormValidationSummaryTokens.PreferredHeightDip, desiredHeight))
            Dim minimum As New SKSize(FormValidationSummaryTokens.MinWidthDip, minimumHeight)
            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(FormValidationSummaryTokens.MaxWidthDip, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=New MASLayoutInset(FormValidationSummaryTokens.PaddingDip,
                                                 FormValidationSummaryTokens.PaddingDip,
                                                 FormValidationSummaryTokens.PaddingDip,
                                                 FormValidationSummaryTokens.PaddingDip),
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        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 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

            _primitives.DrawCardSurface(canvas, ctx, bounds, dpi, _contract, MASCardVisualStyle.Secondary)
            Dim content As SKRect = PixelSnap.SnapRect(Inset(bounds, FormValidationSummaryTokens.PaddingDip * dpi), dpi)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            Dim isRtl As Boolean = MASFormValidationSummaryPolicy.ResolveRightToLeft()
            DrawHeader(canvas, ctx, content, dpi, isRtl)
            DrawMetrics(canvas, ctx, content, dpi, isRtl)
            DrawMessages(canvas, ctx, content, dpi, isRtl)
            DrawFooter(canvas, ctx, content, dpi, isRtl)
        End Sub

        Private Sub DrawHeader(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               content As SKRect,
                               dpi As Single,
                               isRtl As Boolean)
            Dim header As New SKRect(content.Left, content.Top, content.Right, content.Top + FormValidationSummaryTokens.HeaderHeightDip * dpi)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _title, header, dpi, MASTypography.MASTextStyle.Title, MASFormValidationSummaryPolicy.ResolvePrimaryTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)

            Dim summaryWidth As Single = Math.Min(230.0F * dpi, header.Width * 0.48F)
            Dim summaryRect As SKRect = If(isRtl,
                                           New SKRect(header.Left, header.Top, header.Left + summaryWidth, header.Bottom),
                                           New SKRect(header.Right - summaryWidth, header.Top, header.Right, header.Bottom))
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, SummaryText, summaryRect, dpi, MASTypography.MASTextStyle.Micro, MASFormValidationSummaryPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Left, TypographyTextPrimitives.TextAlign.Right), False)
        End Sub

        Private Sub DrawMetrics(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                content As SKRect,
                                dpi As Single,
                                isRtl As Boolean)
            Dim top As Single = content.Top + FormValidationSummaryTokens.HeaderHeightDip * dpi
            Dim rect As New SKRect(content.Left, top, content.Right, top + FormValidationSummaryTokens.MetricsHeightDip * dpi)
            Dim chipWidth As Single = Math.Min(140.0F * dpi, Math.Max(72.0F * dpi, rect.Width / 3.0F - FormValidationSummaryTokens.ChipGapDip * dpi))
            Dim x As Single = If(isRtl, rect.Right - chipWidth, rect.Left)
            DrawMetricChip(canvas, ctx, New SKRect(x, rect.Top + 4.0F * dpi, x + chipWidth, rect.Bottom - 4.0F * dpi), "Messages", MessageCount.ToString(CultureInfo.CurrentCulture), MASFormValidationSummaryPolicy.ResolveAccentColor(ctx), dpi, isRtl)
            If isRtl Then x -= chipWidth + FormValidationSummaryTokens.ChipGapDip * dpi Else x += chipWidth + FormValidationSummaryTokens.ChipGapDip * dpi
            DrawMetricChip(canvas, ctx, New SKRect(x, rect.Top + 4.0F * dpi, x + chipWidth, rect.Bottom - 4.0F * dpi), "Errors", ErrorCount.ToString(CultureInfo.CurrentCulture), MASFormValidationSummaryPolicy.ResolveStateColor(ctx, MASTextValidationState.Error), dpi, isRtl)
            If isRtl Then x -= chipWidth + FormValidationSummaryTokens.ChipGapDip * dpi Else x += chipWidth + FormValidationSummaryTokens.ChipGapDip * dpi
            DrawMetricChip(canvas, ctx, New SKRect(x, rect.Top + 4.0F * dpi, x + chipWidth, rect.Bottom - 4.0F * dpi), "Warnings", WarningCount.ToString(CultureInfo.CurrentCulture), MASFormValidationSummaryPolicy.ResolveStateColor(ctx, MASTextValidationState.Warning), dpi, isRtl)
        End Sub

        Private Sub DrawMetricChip(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   rect As SKRect,
                                   label As String,
                                   value As String,
                                   color As SKColor,
                                   dpi As Single,
                                   isRtl As Boolean)
            Dim snapped As SKRect = PixelSnap.SnapRect(rect, dpi)
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = color.WithAlpha(FormValidationSummaryTokens.ChipFillAlpha)}
                canvas.DrawRoundRect(snapped, FormValidationSummaryTokens.ChipRadiusDip * dpi, FormValidationSummaryTokens.ChipRadiusDip * dpi, fill)
            End Using
            Dim valueRect As SKRect = If(isRtl,
                                         New SKRect(snapped.Left + 8.0F * dpi, snapped.Top, snapped.Left + 42.0F * dpi, snapped.Bottom),
                                         New SKRect(snapped.Right - 42.0F * dpi, snapped.Top, snapped.Right - 8.0F * dpi, snapped.Bottom))
            Dim labelRect As SKRect = If(isRtl,
                                         New SKRect(valueRect.Right, snapped.Top, snapped.Right - 8.0F * dpi, snapped.Bottom),
                                         New SKRect(snapped.Left + 8.0F * dpi, snapped.Top, valueRect.Left, snapped.Bottom))
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, label, labelRect, dpi, MASTypography.MASTextStyle.Micro, MASFormValidationSummaryPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, value, valueRect, dpi, MASTypography.MASTextStyle.Body, color.WithAlpha(FormValidationSummaryTokens.TextAlpha), If(isRtl, TypographyTextPrimitives.TextAlign.Left, TypographyTextPrimitives.TextAlign.Right), False)
        End Sub

        Private Sub DrawMessages(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 content As SKRect,
                                 dpi As Single,
                                 isRtl As Boolean)
            Dim top As Single = content.Top + (FormValidationSummaryTokens.HeaderHeightDip + FormValidationSummaryTokens.MetricsHeightDip) * dpi + FormValidationSummaryTokens.MessagesTopGapDip * dpi
            Dim bottom As Single = content.Bottom - FormValidationSummaryTokens.FooterHeightDip * dpi - FormValidationSummaryTokens.MessagesFooterGapDip * dpi
            Dim rect As SKRect = PixelSnap.SnapRect(New SKRect(content.Left, top, content.Right, bottom), dpi)
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            If _messages.Count = 0 Then
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, FormValidationSummaryTokens.EmptySummaryText, rect, dpi, MASTypography.MASTextStyle.Body, MASFormValidationSummaryPolicy.ResolveMutedTextColor(ctx).WithAlpha(FormValidationSummaryTokens.EmptyTextAlpha), TypographyTextPrimitives.TextAlign.Center, False)
                Return
            End If

            NormalizeScrollState()
            Dim rowHeight As Single = FormValidationSummaryTokens.RowHeightDip * dpi
            Dim rowGap As Single = FormValidationSummaryTokens.RowGapDip * dpi
            Dim visibleCapacity As Integer = ResolveVisibleMessageCapacity(rect.Height, dpi)
            If visibleCapacity <= 0 Then Return
            Dim maxRows As Integer = Math.Min(visibleCapacity, _messages.Count - _topMessageIndex)
            For i As Integer = 0 To maxRows - 1
                Dim messageIndex As Integer = _topMessageIndex + i
                Dim rowTop As Single = rect.Top + CSng(i) * (rowHeight + rowGap)
                Dim rowRect As SKRect = PixelSnap.SnapRect(New SKRect(rect.Left, rowTop, rect.Right, rowTop + rowHeight), dpi)
                Dim isNavigable As Boolean = _interactionMode = MASFormValidationSummaryInteractionMode.Navigable
                Dim isSelected As Boolean = isNavigable AndAlso messageIndex = _selectedMessageIndex AndAlso Not HasActiveFormValidationSummarySelectionMotion()
                Dim displayRect As SKRect = ResolveFormValidationSummaryMutationRowRect(rowRect, dpi, i)
                DrawMessageRow(canvas, ctx, displayRect, _messages(messageIndex), isSelected, isNavigable, dpi, isRtl)
            Next

            DrawFormValidationSummarySelectionMotionOverlay(canvas, ctx, rect, dpi, isRtl, visibleCapacity)
            DrawScrollIndicator(canvas, rect, dpi, MASFormValidationSummaryPolicy.ResolveAccentColor(ctx), isRtl, visibleCapacity)
        End Sub

        Private Shared Function ResolveVisibleMessageCapacity(messagesHeightPx As Single,
                                                              dpi As Single) As Integer
            Dim safeDpi As Single = Math.Max(0.01F, dpi)
            Dim rowHeight As Single = FormValidationSummaryTokens.RowHeightDip * safeDpi
            Dim rowGap As Single = FormValidationSummaryTokens.RowGapDip * safeDpi
            If messagesHeightPx < rowHeight Then Return 0
            Dim capacity As Integer = CInt(Math.Floor((messagesHeightPx + rowGap) / (rowHeight + rowGap)))
            Return Math.Max(0, Math.Min(FormValidationSummaryTokens.MaxVisibleMessages, capacity))
        End Function

        Private Sub DrawMessageRow(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   rect As SKRect,
                                   item As SummaryMessageState,
                                   selected As Boolean,
                                   navigable As Boolean,
                                   dpi As Single,
                                   isRtl As Boolean)
            Dim tone As SKColor = MASFormValidationSummaryPolicy.ResolveStateColor(ctx, item.State)
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = tone.WithAlpha(FormValidationSummaryTokens.RowFillAlpha)}
                canvas.DrawRoundRect(rect, FormValidationSummaryTokens.RowRadiusDip * dpi, FormValidationSummaryTokens.RowRadiusDip * dpi, fill)
            End Using
            If navigable Then
                Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, dpi), .Color = tone.WithAlpha(FormValidationSummaryTokens.RowStrokeAlpha)}
                    canvas.DrawRoundRect(rect, FormValidationSummaryTokens.RowRadiusDip * dpi, FormValidationSummaryTokens.RowRadiusDip * dpi, stroke)
                End Using
            End If
            If selected Then
                Using selectedStroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, 1.4F * dpi), .Color = tone.WithAlpha(150)}
                    canvas.DrawRoundRect(rect, FormValidationSummaryTokens.RowRadiusDip * dpi, FormValidationSummaryTokens.RowRadiusDip * dpi, selectedStroke)
                End Using
            End If

            Dim railWidth As Single = FormValidationSummaryTokens.ToneRailWidthDip * dpi
            Dim rail As SKRect = If(isRtl,
                                    New SKRect(rect.Right - railWidth - 8.0F * dpi, rect.Top + 8.0F * dpi, rect.Right - 8.0F * dpi, rect.Bottom - 8.0F * dpi),
                                    New SKRect(rect.Left + 8.0F * dpi, rect.Top + 8.0F * dpi, rect.Left + 8.0F * dpi + railWidth, rect.Bottom - 8.0F * dpi))
            Using railPaint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = tone.WithAlpha(FormValidationSummaryTokens.ToneFillAlpha)}
                canvas.DrawRoundRect(rail, railWidth, railWidth, railPaint)
            End Using

            Dim fieldWidth As Single = Math.Min(150.0F * dpi, rect.Width * 0.36F)
            Dim fieldRect As SKRect = If(isRtl,
                                         New SKRect(rect.Right - fieldWidth - 18.0F * dpi, rect.Top, rect.Right - 18.0F * dpi - railWidth, rect.Bottom),
                                         New SKRect(rect.Left + 18.0F * dpi + railWidth, rect.Top, rect.Left + 18.0F * dpi + railWidth + fieldWidth, rect.Bottom))
            Dim messageRect As SKRect = If(isRtl,
                                           New SKRect(rect.Left + 12.0F * dpi, rect.Top, fieldRect.Left - 8.0F * dpi, rect.Bottom),
                                           New SKRect(fieldRect.Right + 8.0F * dpi, rect.Top, rect.Right - 12.0F * dpi, rect.Bottom))

            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.FieldLabel, fieldRect, dpi, MASTypography.MASTextStyle.Micro, tone.WithAlpha(FormValidationSummaryTokens.TextAlpha), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.Message, messageRect, dpi, MASTypography.MASTextStyle.Small, MASFormValidationSummaryPolicy.ResolvePrimaryTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
        End Sub

        Private Sub DrawFooter(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               content As SKRect,
                               dpi As Single,
                               isRtl As Boolean)
            Dim footer As New SKRect(content.Left, content.Bottom - FormValidationSummaryTokens.FooterHeightDip * dpi, content.Right, content.Bottom)
            Dim text As String = FormValidationSummaryTokens.BoundaryText
            If _messages.Count > FormValidationSummaryTokens.MaxVisibleMessages Then
                NormalizeScrollState()
                Dim firstVisible As Integer = Math.Min(_messages.Count, _topMessageIndex + 1)
                Dim lastVisible As Integer = Math.Min(_messages.Count, _topMessageIndex + FormValidationSummaryTokens.MaxVisibleMessages)
                Dim navigationHint As String = If(_interactionMode = MASFormValidationSummaryInteractionMode.Navigable, "Wheel / keyboard navigates messages", "Wheel scrolls messages")
                text = "Showing " & firstVisible.ToString(CultureInfo.CurrentCulture) & "-" & lastVisible.ToString(CultureInfo.CurrentCulture) & " of " & _messages.Count.ToString(CultureInfo.CurrentCulture) & " • " & navigationHint
            End If
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, text, footer, dpi, MASTypography.MASTextStyle.Micro, MASFormValidationSummaryPolicy.ResolveMutedTextColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
        End Sub

        Private Sub DrawScrollIndicator(canvas As SKCanvas,
                                        messagesRect As SKRect,
                                        dpi As Single,
                                        accent As SKColor,
                                        isRtl As Boolean,
                                        visibleRows As Integer)
            Dim safeVisibleRows As Integer = Math.Max(1, Math.Min(FormValidationSummaryTokens.MaxVisibleMessages, visibleRows))
            If _messages.Count <= safeVisibleRows Then Return
            Dim trackWidth As Single = FormValidationSummaryTokens.ScrollTrackWidthDip * dpi
            Dim x1 As Single = If(isRtl, messagesRect.Left + 1.0F * dpi, messagesRect.Right - trackWidth - 1.0F * dpi)
            Dim track As SKRect = PixelSnap.SnapRect(New SKRect(x1, messagesRect.Top, x1 + trackWidth, messagesRect.Bottom), dpi)
            Using trackPaint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(38)}
                canvas.DrawRoundRect(track, trackWidth * 0.5F, trackWidth * 0.5F, trackPaint)
            End Using

            Dim visibleRatio As Single = CSng(safeVisibleRows) / CSng(Math.Max(1, _messages.Count))
            Dim thumbHeight As Single = Math.Max(FormValidationSummaryTokens.ScrollThumbMinHeightDip * dpi, track.Height * visibleRatio)
            Dim maxTop As Integer = Math.Max(1, _messages.Count - safeVisibleRows)
            Dim topRatio As Single = CSng(Math.Max(0, _topMessageIndex)) / CSng(maxTop)
            Dim thumbTop As Single = track.Top + (track.Height - thumbHeight) * topRatio
            Dim thumb As SKRect = PixelSnap.SnapRect(New SKRect(track.Left, thumbTop, track.Right, thumbTop + thumbHeight), dpi)
            Using thumbPaint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(132)}
                canvas.DrawRoundRect(thumb, trackWidth * 0.5F, trackWidth * 0.5F, thumbPaint)
            End Using
        End Sub

#End Region

#Region "Input"

        Friend Overrides Function WantsPointerWheel(ctx As MASThemeContext,
                                                      ptPx As SKPoint) As Boolean
            Return Visible AndAlso Enabled AndAlso
                   _messages.Count > FormValidationSummaryTokens.MaxVisibleMessages AndAlso
                   GetPixelHitBounds(ctx).Contains(ptPx.X, ptPx.Y)
        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 CanScrollMessages(delta)
        End Function

        Protected Overrides Sub OnMouseWheel(ctx As MASThemeContext,
                                             delta As Integer,
                                             ptPx As SKPoint)
            If Not CanScrollMessages(delta) Then Return
            If _interactionMode = MASFormValidationSummaryInteractionMode.Passive Then
                ScrollPassive(delta)
                Return
            End If
            If delta < 0 Then
                ScrollNext()
            ElseIf delta > 0 Then
                ScrollPrevious()
            End If
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If _interactionMode <> MASFormValidationSummaryInteractionMode.Navigable Then Return False
            If Not Enabled OrElse _messages.Count = 0 Then Return False
            NormalizeScrollState()
            Select Case keyCode
                Case Keys.Up
                    Return ScrollPrevious()
                Case Keys.Down
                    Return ScrollNext()
                Case Keys.PageUp
                    Return ScrollToMessage(_selectedMessageIndex - FormValidationSummaryTokens.MaxVisibleMessages)
                Case Keys.PageDown
                    Return ScrollToMessage(_selectedMessageIndex + FormValidationSummaryTokens.MaxVisibleMessages)
                Case Keys.Home
                    Return ScrollToMessage(0)
                Case Keys.End
                    Return ScrollToMessage(_messages.Count - 1)
            End Select
            Return False
        End Function

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return _interactionMode = MASFormValidationSummaryInteractionMode.Navigable AndAlso
                   Visible AndAlso Enabled AndAlso _messages.Count > FormValidationSummaryTokens.MaxVisibleMessages
        End Function

#End Region

#Region "State helpers"

        Private Function CanScrollMessages(delta As Integer) As Boolean
            NormalizeScrollState()
            If _messages.Count <= FormValidationSummaryTokens.MaxVisibleMessages Then Return False
            If _interactionMode = MASFormValidationSummaryInteractionMode.Passive Then
                If delta < 0 Then Return _topMessageIndex < Math.Max(0, _messages.Count - FormValidationSummaryTokens.MaxVisibleMessages)
                If delta > 0 Then Return _topMessageIndex > 0
                Return False
            End If
            If delta < 0 Then Return _selectedMessageIndex < _messages.Count - 1
            If delta > 0 Then Return _selectedMessageIndex > 0
            Return False
        End Function

        Private Sub ScrollPassive(delta As Integer)
            If delta = 0 Then Return
            NormalizeScrollState()
            Dim maxTop As Integer = Math.Max(0, _messages.Count - FormValidationSummaryTokens.MaxVisibleMessages)
            Dim nextTop As Integer = _topMessageIndex
            If delta < 0 Then
                nextTop += 1
            ElseIf delta > 0 Then
                nextTop -= 1
            End If
            nextTop = Math.Max(0, Math.Min(nextTop, maxTop))
            If nextTop = _topMessageIndex Then Return
            _topMessageIndex = nextTop
            InvalidateVisual()
        End Sub

        Private Sub NormalizeScrollState()
            If _messages.Count <= 0 Then
                _topMessageIndex = 0
                _selectedMessageIndex = -1
                Return
            End If

            If _interactionMode = MASFormValidationSummaryInteractionMode.Passive Then
                _selectedMessageIndex = -1
                _topMessageIndex = Math.Max(0, Math.Min(_topMessageIndex, Math.Max(0, _messages.Count - FormValidationSummaryTokens.MaxVisibleMessages)))
                Return
            End If

            _selectedMessageIndex = MASFormValidationSummaryPolicy.ClampIndex(_selectedMessageIndex, _messages.Count)
            If _messages.Count <= FormValidationSummaryTokens.MaxVisibleMessages Then
                _topMessageIndex = 0
                Return
            End If
            _topMessageIndex = MASFormValidationSummaryPolicy.EnsureVisibleIndex(_selectedMessageIndex, _topMessageIndex, _messages.Count, FormValidationSummaryTokens.MaxVisibleMessages)
        End Sub

        Private Shared Function NormalizeInteractionMode(value As MASFormValidationSummaryInteractionMode) As MASFormValidationSummaryInteractionMode
            Select Case value
                Case MASFormValidationSummaryInteractionMode.Navigable
                    Return MASFormValidationSummaryInteractionMode.Navigable
                Case Else
                    Return MASFormValidationSummaryInteractionMode.Passive
            End Select
        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

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

        Private Sub RaiseFormValidationSummaryChangedSafe()
            Try
                RaiseEvent FormValidationSummaryChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASFormValidationSummary.FormValidationSummaryChanged")
            End Try
        End Sub

        Friend Function CreateFormValidationSummaryReadinessManifest() As MASFormValidationSummaryReadinessManifest
            Return MASFormValidationSummaryReadinessManifest.CreateDefault()
        End Function

#End Region

    End Class

End Namespace
