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.FloatRuntime
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>
    ''' Public Nexamas UI avatar group surface for compact team/user clusters. It renders
    ''' application-provided avatars with overlap and overflow count only; people lookup,
    ''' presence transport, image acquisition, invitations, and security identity remain
    ''' outside this visual control.
    ''' </summary>
    Public NotInheritable Class MASAvatarGroup
        Inherits Nexamas.UI.Controls.MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract
        Implements IMASControlRuntimeServicesConsumer

#Region "Fields"

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

        Private ReadOnly _avatars As New List(Of MASAvatar)()
        Private _selectedIndex As Integer = -1
        Private _maxVisible As Integer = AvatarTokens.GroupMaxVisibleDefault
        Private _hoverIndex As Integer = -1
        Private _pressedIndex As Integer = -1
        Private _hoverOverflow As Boolean
        Private _pressedOverflow As Boolean
        Private _keyboardOverflowActive As Boolean
        Private _lastAvatarRects() As SKRect = Array.Empty(Of SKRect)()
        Private _lastOverflowRect As SKRect = SKRect.Empty
        Private _lastVisibleAvatarCount As Integer
        Private _lastRenderDpi As Single = 1.0F
        Private _lastRenderedRtl As Boolean
        Private _runtimeServices As MASControlRuntimeServices
        Private _overflowHandle As MASFloatHandle
        Private _overflowContent As MASAvatarOverflowFloatContent
        Private _disposedLocal As Boolean

#End Region

#Region "Public API"

        Public Event SelectedIndexChanged As EventHandler
        Public Event AvatarClicked As EventHandler

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

        Public Shared Function Create(Optional avatars As IEnumerable(Of MASAvatar) = Nothing) As MASAvatarGroup
            Dim group As New MASAvatarGroup()
            If avatars IsNot Nothing Then
                For Each avatar As MASAvatar In avatars
                    group.AddAvatar(avatar)
                Next
            End If
            Return group
        End Function

        Public ReadOnly Property AvatarCount As Integer
            Get
                Return _avatars.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 Property MaxVisible As Integer
            Get
                Return _maxVisible
            End Get
            Set(value As Integer)
                Dim normalized As Integer = MASAvatarGroupLayoutPolicy.NormalizeMaxVisible(value)
                If _maxVisible = normalized Then Return
                _maxVisible = normalized
                CloseOverflowSafe()
                RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatarGroup.MaxVisible")
                InvalidateVisual()
            End Set
        End Property

        Public Function AddAvatar(avatar As MASAvatar) As MASAvatarGroup
            If avatar Is Nothing Then Return Me
            _avatars.Add(avatar)
            CloseOverflowSafe()
            NormalizeSelectionAfterCollectionChange()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatarGroup.AddAvatar")
            InvalidateVisual()
            Return Me
        End Function

        Public Function AddAvatar(displayName As String,
                                  Optional initials As String = Nothing) As MASAvatarGroup
            Return AddAvatar(MASAvatar.Create(displayName, initials))
        End Function

        Public Function RemoveAvatarAt(index As Integer) As MASAvatarGroup
            If index < 0 OrElse index >= _avatars.Count Then Return Me
            _avatars.RemoveAt(index)
            CloseOverflowSafe()
            NormalizeSelectionAfterCollectionChange()
            ResetPointerState()
            _keyboardOverflowActive = False
            RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatarGroup.RemoveAvatarAt")
            RaiseSelectedIndexChangedSafe()
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearAvatars() As MASAvatarGroup
            If _avatars.Count = 0 Then Return Me
            _avatars.Clear()
            CloseOverflowSafe()
            _selectedIndex = -1
            ResetPointerState()
            _keyboardOverflowActive = False
            RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatarGroup.ClearAvatars")
            RaiseSelectedIndexChangedSafe()
            InvalidateVisual()
            Return Me
        End Function

        Public Function GetAvatar(index As Integer) As MASAvatar
            If index < 0 OrElse index >= _avatars.Count Then Return Nothing
            Return _avatars(index)
        End Function

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

        Public Function WithMaxVisible(value As Integer) As MASAvatarGroup
            MaxVisible = value
            Return Me
        End Function

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

#End Region

#Region "Runtime Services"

        Friend Sub BindControlRuntimeServices(services As MASControlRuntimeServices) Implements IMASControlRuntimeServicesConsumer.BindControlRuntimeServices
            If Object.ReferenceEquals(_runtimeServices, services) Then Return
            CloseOverflowSafe()
            _runtimeServices = services
            InvalidateVisual()
        End Sub

#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 width As Single = MASAvatarGroupLayoutPolicy.MeasureDesiredWidth(_avatars.Count, _maxVisible)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=New SKSize(width, AvatarTokens.GroupDefaultHeightDip),
                minSize:=New SKSize(AvatarTokens.GroupMinWidthDip, AvatarTokens.GroupDefaultHeightDip),
                maxSize:=New SKSize(Single.PositiveInfinity, AvatarTokens.GroupDefaultHeightDip),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=New MASLayoutInset(AvatarTokens.GroupPaddingHorizontalDip, AvatarTokens.GroupPaddingVerticalDip, AvatarTokens.GroupPaddingHorizontalDip, AvatarTokens.GroupPaddingVerticalDip),
                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)
            _lastRenderDpi = dpi
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            DrawGroupSurface(canvas, ctx, bounds, dpi)

            Dim content As New SKRect(bounds.Left + AvatarTokens.GroupPaddingHorizontalDip * dpi,
                                      bounds.Top + AvatarTokens.GroupPaddingVerticalDip * dpi,
                                      bounds.Right - AvatarTokens.GroupPaddingHorizontalDip * dpi,
                                      bounds.Bottom - AvatarTokens.GroupPaddingVerticalDip * dpi)
            DrawAvatars(canvas, ctx, content, dpi)
        End Sub

        Private Sub DrawGroupSurface(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     bounds As SKRect,
                                     dpi As Single)
            Dim radius As Single = Math.Max(1.0F, bounds.Height / 2.0F)
            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveSurfaceColor(ctx).WithAlpha(If(Enabled, AvatarTokens.SurfaceAlpha, AvatarTokens.DisabledAlpha))}
                canvas.DrawRoundRect(bounds, radius, radius, paint)
            End Using
        End Sub

        Private Sub DrawAvatars(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                content As SKRect,
                                dpi As Single)
            Dim isRtl As Boolean = ResolveRightToLeft()
            _lastRenderedRtl = isRtl

            Dim plan As MASAvatarGroupLayoutPlan = MASAvatarGroupLayoutPolicy.BuildLayoutPlan(content, _avatars.Count, _maxVisible, dpi, isRtl)
            Dim visibleCount As Integer = Math.Min(plan.VisibleAvatarCount, _avatars.Count)

            Dim activeTopLayerIndex As Integer = ResolveActiveTopLayerIndex(visibleCount)

            For i As Integer = 0 To visibleCount - 1
                If i <> activeTopLayerIndex Then DrawAvatarItem(canvas, ctx, plan.AvatarRects(i), dpi, i)
            Next

            If plan.HasOverflow Then
                DrawExtraCount(canvas, ctx, plan.OverflowRect, plan.HiddenAvatarCount, dpi, _hoverOverflow OrElse IsKeyboardOverflowActive(), _pressedOverflow)
            Else
                _keyboardOverflowActive = False
            End If

            If activeTopLayerIndex >= 0 Then
                DrawAvatarItem(canvas, ctx, plan.AvatarRects(activeTopLayerIndex), dpi, activeTopLayerIndex)
            End If

            _lastAvatarRects = plan.AvatarRects
            _lastOverflowRect = If(plan.HasOverflow, plan.OverflowRect, SKRect.Empty)
            _lastVisibleAvatarCount = visibleCount
        End Sub

        Private Sub DrawAvatarItem(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   bounds As SKRect,
                                   dpi As Single,
                                   index As Integer)
            If index < 0 OrElse index >= _avatars.Count Then Return

            Dim avatar As MASAvatar = _avatars(index)
            Dim keyboardActive As Boolean = HasKeyboardFocus AndAlso index = _selectedIndex
            avatar.RenderAvatarGlyph(canvas, ctx, bounds, False, index = _hoverIndex OrElse keyboardActive, index = _pressedIndex)
        End Sub

        Private Function ResolveActiveTopLayerIndex(visibleCount As Integer) As Integer
            Dim keyboardActiveIndex As Integer = If(HasKeyboardFocus, _selectedIndex, -1)
            Return MASAvatarGroupLayoutPolicy.ResolvePreferredPointerTopLayerIndex(_hoverIndex, _pressedIndex, keyboardActiveIndex, visibleCount)
        End Function

        Private Sub DrawExtraCount(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   bounds As SKRect,
                                   count As Integer,
                                   dpi As Single,
                                   hovered As Boolean,
                                   pressed As Boolean)
            Dim snapped As SKRect = PixelSnap.SnapRect(bounds, dpi)
            Dim radius As Single = Math.Max(1.0F, Math.Min(snapped.Width, snapped.Height) / 2.0F)
            Dim fillAlpha As Byte = AvatarTokens.SelectedAlpha
            If pressed Then
                fillAlpha = CByte(Math.Min(255, CInt(AvatarTokens.SelectedAlpha) + CInt(AvatarTokens.PressedAlpha)))
            ElseIf hovered Then
                fillAlpha = CByte(Math.Min(255, CInt(AvatarTokens.SelectedAlpha) + CInt(AvatarTokens.HoverAlpha)))
            End If

            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveAccentColor(ctx).WithAlpha(If(Enabled, fillAlpha, AvatarTokens.DisabledAlpha))}
                canvas.DrawOval(snapped, paint)
            End Using
            Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, AvatarTokens.RingStrokeDip * dpi), .Color = ResolveSurfaceColor(ctx).WithAlpha(AvatarTokens.RingAlpha)}
                canvas.DrawOval(snapped, stroke)
            End Using

            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=MASAvatarGroupLayoutPolicy.ResolveOverflowText(count),
                bounds:=snapped,
                dpi:=dpi,
                style:=MASTypography.MASTextStyle.Small,
                color:=ResolveTextColor(ctx).WithAlpha(If(Enabled, AvatarTokens.TextAlpha, AvatarTokens.DisabledAlpha)),
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

#End Region

#Region "Input"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim hit As Integer = HitIndex(ptPx)
            Dim overflowHit As Boolean = HitOverflow(ptPx)
            If hit = _hoverIndex AndAlso overflowHit = _hoverOverflow Then Return
            _hoverIndex = If(overflowHit, -1, hit)
            _hoverOverflow = overflowHit
            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 overflowHit As Boolean = HitOverflow(ptPx)
            If overflowHit Then
                _pressedIndex = -1
                _hoverIndex = -1
                _pressedOverflow = True
                _hoverOverflow = True
                _keyboardOverflowActive = True
                InvalidateVisual()
                Return True
            End If

            Dim hit As Integer = HitIndex(ptPx)
            _pressedIndex = hit
            _hoverIndex = hit
            _keyboardOverflowActive = False
            _pressedOverflow = False
            _hoverOverflow = False
            InvalidateVisual()
            Return hit >= 0
        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 wasOverflowPressed As Boolean = _pressedOverflow
            Dim pressed As Integer = _pressedIndex
            _pressedIndex = -1
            _pressedOverflow = False

            Dim overflowReleased As Boolean = HitOverflow(ptPx)
            If wasOverflowPressed Then
                _hoverOverflow = overflowReleased
                If overflowReleased Then ToggleOverflowPopover(ctx)
                InvalidateVisual()
                Return True
            End If

            Dim released As Integer = HitIndex(ptPx)
            If pressed >= 0 AndAlso pressed = released AndAlso pressed < _avatars.Count Then
                InvokeAvatarAtIndex(pressed)
                InvalidateVisual()
                Return True
            End If
            InvalidateVisual()
            Return pressed >= 0 OrElse released >= 0
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _hoverIndex = -1 AndAlso _pressedIndex = -1 AndAlso Not _hoverOverflow AndAlso Not _pressedOverflow 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()
            _keyboardOverflowActive = False
            InvalidateVisual()
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            Select Case keyCode
                Case Keys.Left
                    MoveKeyboardFocus(If(_lastRenderedRtl, 1, -1))
                    Return True
                Case Keys.Right
                    MoveKeyboardFocus(If(_lastRenderedRtl, -1, 1))
                    Return True
                Case Keys.Home
                    MoveKeyboardFocusToStart()
                    Return True
                Case Keys.End
                    MoveKeyboardFocusToEnd()
                    Return True
                Case Keys.Enter, Keys.Space
                    If _keyboardOverflowActive Then
                        ToggleOverflowPopover(ctx)
                    ElseIf _selectedIndex >= 0 Then
                        RaiseAvatarClickedSafe()
                    End If
                    Return True
                Case Keys.Escape
                    CloseOverflowSafe()
                    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 HitIndex(ptPx As SKPoint) As Integer
            Return MASAvatarGroupLayoutPolicy.ResolveHitIndex(_lastAvatarRects, ptPx, _pressedIndex, _lastRenderDpi)
        End Function

        Private Function HitOverflow(ptPx As SKPoint) As Boolean
            Return MASAvatarOverflowPopoverPolicy.ContainsOverflowVisualPoint(_lastOverflowRect, ptPx, _lastRenderDpi)
        End Function

        Private Sub MoveKeyboardFocus(delta As Integer)
            If _avatars.Count <= 0 Then Return

            Dim visibleCount As Integer = ResolveVisibleKeyboardAvatarCount()
            Dim hasOverflow As Boolean = HasVisibleOverflowChip()
            Dim focusSlots As Integer = visibleCount + If(hasOverflow, 1, 0)
            If focusSlots <= 0 Then Return

            Dim currentSlot As Integer = ResolveCurrentKeyboardSlot(visibleCount, hasOverflow)
            Dim nextSlot As Integer = currentSlot + delta
            If nextSlot < 0 Then nextSlot = focusSlots - 1
            If nextSlot >= focusSlots Then nextSlot = 0

            ApplyKeyboardSlot(nextSlot, visibleCount, hasOverflow)
        End Sub

        Private Sub MoveKeyboardFocusToStart()
            If _avatars.Count <= 0 Then Return
            _keyboardOverflowActive = False
            SetSelectedIndexInternal(0, True)
        End Sub

        Private Sub MoveKeyboardFocusToEnd()
            If _avatars.Count <= 0 Then Return
            Dim visibleCount As Integer = ResolveVisibleKeyboardAvatarCount()
            Dim hasOverflow As Boolean = HasVisibleOverflowChip()
            If hasOverflow Then
                _keyboardOverflowActive = True
                InvalidateVisual()
            Else
                _keyboardOverflowActive = False
                SetSelectedIndexInternal(Math.Max(0, visibleCount - 1), True)
            End If
        End Sub

        Private Function ResolveVisibleKeyboardAvatarCount() As Integer
            If _lastVisibleAvatarCount > 0 Then Return Math.Min(_lastVisibleAvatarCount, _avatars.Count)
            Return Math.Min(MASAvatarGroupLayoutPolicy.NormalizeMaxVisible(_maxVisible), _avatars.Count)
        End Function

        Private Function HasVisibleOverflowChip() As Boolean
            Return _avatars.Count > ResolveVisibleKeyboardAvatarCount()
        End Function

        Private Function ResolveCurrentKeyboardSlot(visibleCount As Integer,
                                                    hasOverflow As Boolean) As Integer
            If hasOverflow AndAlso _keyboardOverflowActive Then Return visibleCount
            If _selectedIndex >= 0 AndAlso _selectedIndex < visibleCount Then Return _selectedIndex
            If hasOverflow Then Return visibleCount
            Return Math.Max(0, Math.Min(_selectedIndex, Math.Max(0, visibleCount - 1)))
        End Function

        Private Sub ApplyKeyboardSlot(slot As Integer,
                                      visibleCount As Integer,
                                      hasOverflow As Boolean)
            If hasOverflow AndAlso slot >= visibleCount Then
                _keyboardOverflowActive = True
                InvalidateVisual()
                Return
            End If

            _keyboardOverflowActive = False
            SetSelectedIndexInternal(Math.Max(0, Math.Min(slot, Math.Max(0, visibleCount - 1))), True)
        End Sub

        Private Function IsKeyboardOverflowActive() As Boolean
            Return HasKeyboardFocus AndAlso _keyboardOverflowActive AndAlso HasVisibleOverflowChip()
        End Function

        Private Sub InvokeAvatarAtIndex(index As Integer)
            If index < 0 OrElse index >= _avatars.Count Then Return
            _keyboardOverflowActive = False
            SetSelectedIndexInternal(index, True)
            RaiseAvatarClickedSafe()
        End Sub

        Private Sub ToggleOverflowPopover(ctx As MASThemeContext)
            If IsOverflowOpen() Then
                CloseOverflowSafe()
            Else
                OpenOverflowPopover(ctx)
            End If
        End Sub

        Private Sub OpenOverflowPopover(ctx As MASThemeContext)
            If ctx Is Nothing Then Return
            If _avatars.Count <= 0 Then Return
            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()
            If runtime Is Nothing Then Return
            If Host Is Nothing Then Return
            If _lastOverflowRect.Width <= 1.0F OrElse _lastOverflowRect.Height <= 1.0F Then Return

            Dim items As List(Of MASAvatarOverflowItem) = MASAvatarOverflowPopoverPolicy.CreateOverflowItems(_avatars, _lastVisibleAvatarCount)
            If items.Count <= 0 Then Return

            CloseOverflowSafe()

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim boundsPx As SKRect = MASAvatarOverflowPopoverPolicy.ResolvePopupBoundsPx(_lastOverflowRect, items.Count, dpi, AddressOf ResolveSurfaceSizePx)
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            Dim content As New MASAvatarOverflowFloatContent(items, AddressOf InvokeAvatarFromOverflow)
            Dim request As New MASFloatRequest() With {
                .FloatKey = MASFloatKeys.DropDownMenu,
                .OwnerKey = BuildOverflowOwnerKey(),
                .Content = content,
                .RequestedSlot = MASFloatSlot.Popup,
                .RequestedScope = MASFloatScope.Owner,
                .RequestedLayer = MASFloatLayerLevel.Interactive,
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .AnchorRectPx = _lastOverflowRect,
                .InitialBoundsPx = boundsPx,
                .AvailableBoundsPx = ResolveAvailableBoundsPx(boundsPx),
                .RequestedPresentation = MASFloatPresentationProfiles.DropDownList(),
                .OnOpened = Sub(handle As MASFloatHandle)
                                If handle IsNot Nothing Then _overflowHandle = handle
                            End Sub,
                .OnClosed = Sub(args As MASFloatClosedEventArgs)
                                _overflowHandle = Nothing
                                _overflowContent = Nothing
                                _pressedOverflow = False
                                If Not _disposedLocal Then InvalidateVisual()
                            End Sub
            }

            Try
                _overflowContent = content
                _overflowHandle = runtime.Show(request)
            Catch masCaughtException1 As Exception
                _overflowHandle = Nothing
                _overflowContent = Nothing
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException1, "MASAvatarGroup.OpenOverflowPopover")
            End Try
        End Sub

        Private Sub InvokeAvatarFromOverflow(index As Integer)
            InvokeAvatarAtIndex(index)
            CloseOverflowSafe()
        End Sub

        Private Sub CloseOverflowSafe()
            Dim handle As MASFloatHandle = _overflowHandle
            _overflowHandle = Nothing
            _overflowContent = Nothing
            If handle Is Nothing Then Return

            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()
            If runtime Is Nothing Then Return

            Try
                runtime.Close(handle, MASFloatCloseReason.Programmatic)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2, "MASAvatarGroup.CloseOverflow")
            End Try
        End Sub

        Private Function IsOverflowOpen() As Boolean
            Dim handle As MASFloatHandle = _overflowHandle
            If handle Is Nothing Then Return False
            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()
            If runtime Is Nothing Then Return False
            Try
                Return runtime.IsOpen(handle)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException3, "MASAvatarGroup.IsOverflowOpen")
                Return False
            End Try
        End Function

        Private Function ResolveFloatRuntime() As MASFloatRuntime
            If _runtimeServices Is Nothing Then Return Nothing
            Return _runtimeServices.TryGetFloatRuntime()
        End Function

        Private Function ResolveSurfaceSizePx() As SKSizeI
            If _runtimeServices Is Nothing Then Return SKSizeI.Empty
            Return _runtimeServices.TryGetSurfaceSizePx()
        End Function

        Private Function ResolveAvailableBoundsPx(fallbackBounds As SKRect) As SKRect
            Dim size As SKSizeI = ResolveSurfaceSizePx()
            If size.Width > 0 AndAlso size.Height > 0 Then
                Return New SKRect(0.0F, 0.0F, CSng(size.Width), CSng(size.Height))
            End If
            Return New SKRect(0.0F, 0.0F, Math.Max(fallbackBounds.Right, _lastOverflowRect.Right), Math.Max(fallbackBounds.Bottom, _lastOverflowRect.Bottom))
        End Function

        Private Function BuildOverflowOwnerKey() As String
            Return String.Format(CultureInfo.InvariantCulture, "MAS.AvatarGroup.Overflow@{0}", Global.System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(Me))
        End Function

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

        Private Function NormalizeIndex(value As Integer) As Integer
            Return MASAvatarGroupLayoutPolicy.NormalizeSelectionIndex(value, _avatars.Count)
        End Function

        Private Sub NormalizeSelectionAfterCollectionChange()
            _selectedIndex = MASAvatarGroupLayoutPolicy.NormalizeSelectionAfterCollectionChange(_selectedIndex, _avatars.Count)
        End Sub

        Private Sub ResetPointerState()
            _hoverIndex = -1
            _pressedIndex = -1
            _hoverOverflow = False
            _pressedOverflow = False
        End Sub

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

        Private Sub RaiseAvatarClickedSafe()
            Try
                RaiseEvent AvatarClicked(Me, EventArgs.Empty)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2, "MASAvatarGroup.AvatarClicked")
            End Try
        End Sub

        Private Shared Function ResolveRightToLeft() As Boolean
            Return Nexamas.UI.Localization.MASLocalization.IsCurrentRightToLeft()
        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 ResolveTextColor(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 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

#End Region

#Region "Lifecycle"

        Protected Overrides Sub OnEnabledChanged()
            MyBase.OnEnabledChanged()
            If Not Enabled Then CloseOverflowSafe()
        End Sub

        Protected Overrides Sub OnVisibleChanged()
            MyBase.OnVisibleChanged()
            If Not Visible Then CloseOverflowSafe()
        End Sub

        Protected Overrides Sub OnDetached()
            CloseOverflowSafe()
            MyBase.OnDetached()
        End Sub

#End Region

#Region "Governance"

        Friend Function CreateAvatarGroupReadinessManifest() As MASAvatarGroupReadinessManifest
            Return MASAvatarGroupReadinessManifest.CreateCurrent()
        End Function

#End Region

#Region "Dispose"

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

#End Region

    End Class

End Namespace
