Option Strict On
Option Explicit On

Imports System
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.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public Nexamas UI avatar surface for compact person/team identity visuals. It owns
    ''' only initials, color, selected/focus/presence rendering. People directories, image
    ''' loading, account lookup, online-presence transport, and contact persistence stay with
    ''' the host application or existing Nexamas UI systems.
    ''' </summary>
    Public NotInheritable Class MASAvatar
        Inherits Nexamas.UI.Controls.MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private _displayName As String = String.Empty
        Private _initials As String = AvatarTokens.DefaultInitials
        Private _presenceText As String = String.Empty
        Private _accentColor As SKColor = New SKColor(54, 104, 224)
        Private _usesDeterministicAccent As Boolean = True
        Private _selected As Boolean
        Private _showPresence As Boolean
        Private _hovered As Boolean
        Private _pressed As Boolean
        Private _lastAvatarRect As SKRect = SKRect.Empty
        Private _lastRenderDpi As Single = 1.0F
        Private _disposedLocal As Boolean

#End Region

#Region "Public API"

        Public Event Clicked As EventHandler
        Public Event SelectedChanged As EventHandler

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

        Public Shared Function Create(Optional displayName As String = Nothing,
                                      Optional initials As String = Nothing) As MASAvatar
            Dim avatar As New MASAvatar()
            avatar._displayName = MASAvatarIdentityPolicy.NormalizeDisplayName(displayName)
            avatar._initials = MASAvatarIdentityPolicy.ResolveInitials(avatar._displayName, initials)
            avatar._accentColor = MASAvatarIdentityPolicy.ResolveDeterministicAccent(avatar._displayName, avatar._accentColor)
            avatar._usesDeterministicAccent = True
            Return avatar
        End Function

        Public Property DisplayName As String
            Get
                Return _displayName
            End Get
            Set(value As String)
                Dim normalized As String = NormalizeDisplayName(value)
                If String.Equals(_displayName, normalized, StringComparison.Ordinal) Then Return
                _displayName = normalized
                If _usesDeterministicAccent Then _accentColor = MASAvatarIdentityPolicy.ResolveDeterministicAccent(_displayName, _accentColor)
                If String.IsNullOrWhiteSpace(_initials) OrElse String.Equals(_initials, AvatarTokens.DefaultInitials, StringComparison.Ordinal) Then
                    _initials = MASAvatarIdentityPolicy.ResolveInitials(_displayName, Nothing)
                End If
                RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatar.DisplayName")
                InvalidateVisual()
            End Set
        End Property

        Public Property Initials As String
            Get
                Return _initials
            End Get
            Set(value As String)
                Dim normalized As String = MASAvatarIdentityPolicy.ResolveInitials(_displayName, value)
                If String.Equals(_initials, normalized, StringComparison.Ordinal) Then Return
                _initials = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASAvatar.Initials")
                InvalidateVisual()
            End Set
        End Property

        Public Property PresenceText As String
            Get
                Return _presenceText
            End Get
            Set(value As String)
                Dim normalized As String = MASAvatarIdentityPolicy.NormalizePresence(value)
                If String.Equals(_presenceText, normalized, StringComparison.Ordinal) Then Return
                _presenceText = normalized
                InvalidateVisual()
            End Set
        End Property

        Public Property AccentColor As SKColor
            Get
                Return _accentColor
            End Get
            Set(value As SKColor)
                If _accentColor.Equals(value) AndAlso Not _usesDeterministicAccent Then Return
                _accentColor = value
                _usesDeterministicAccent = False
                InvalidateVisual()
            End Set
        End Property

        Public Property Selected As Boolean
            Get
                Return _selected
            End Get
            Set(value As Boolean)
                If _selected = value Then Return
                _selected = value
                RaiseSelectedChangedSafe()
                InvalidateVisual()
            End Set
        End Property

        Public Property ShowPresence As Boolean
            Get
                Return _showPresence
            End Get
            Set(value As Boolean)
                If _showPresence = value Then Return
                _showPresence = value
                InvalidateVisual()
            End Set
        End Property

        Public Function WithDisplayName(value As String) As MASAvatar
            DisplayName = value
            Return Me
        End Function

        Public Function WithInitials(value As String) As MASAvatar
            Initials = value
            Return Me
        End Function

        Public Function WithPresence(value As String,
                                     Optional visible As Boolean = True) As MASAvatar
            PresenceText = value
            ShowPresence = visible
            Return Me
        End Function

        Public Function WithAccentColor(value As SKColor) As MASAvatar
            AccentColor = value
            Return Me
        End Function

        Public Function WithSelected(Optional value As Boolean = True) As MASAvatar
            Selected = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASAvatar
            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 desired As New SKSize(AvatarTokens.DefaultSizeDip, AvatarTokens.DefaultSizeDip)
            Dim minimum As New SKSize(AvatarTokens.MinSizeDip, AvatarTokens.MinSizeDip)
            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(AvatarTokens.LargeSizeDip, AvatarTokens.LargeSizeDip),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=False,
                canGrowHeight:=False,
                contentInset:=MASLayoutInset.Empty,
                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)
            Dim size As Single = Math.Max(1.0F, Math.Min(bounds.Width, bounds.Height))
            Dim avatarRect As New SKRect(bounds.MidX - size / 2.0F, bounds.MidY - size / 2.0F, bounds.MidX + size / 2.0F, bounds.MidY + size / 2.0F)
            _lastRenderDpi = dpi
            _lastAvatarRect = avatarRect
            DrawAvatarSurface(canvas, ctx, avatarRect, dpi, _selected, _hovered, _pressed)
            DrawInitials(canvas, ctx, avatarRect, dpi)
            If _showPresence Then DrawPresence(canvas, ctx, avatarRect, dpi)
            DrawFocusRing(canvas, ctx, avatarRect, dpi)
        End Sub

        Friend Sub RenderAvatarGlyph(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     bounds As SKRect,
                                     selected As Boolean,
                                     hovered As Boolean,
                                     pressed As Boolean)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            DrawAvatarSurface(canvas, ctx, bounds, dpi, selected, hovered, pressed)
            DrawInitials(canvas, ctx, bounds, dpi)
            If _showPresence Then DrawPresence(canvas, ctx, bounds, dpi)
        End Sub

        Private Sub DrawAvatarSurface(canvas As SKCanvas,
                                      ctx As MASThemeContext,
                                      bounds As SKRect,
                                      dpi As Single,
                                      selected As Boolean,
                                      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 baseColor As SKColor = If(Enabled, _accentColor, ResolveMutedColor(ctx))
            Dim topColor As SKColor = ColorMath.Mix(baseColor, SKColors.White, 0.28F).WithAlpha(If(Enabled, AvatarTokens.FillTopAlpha, AvatarTokens.DisabledAlpha))
            Dim bottomColor As SKColor = ColorMath.Mix(baseColor, SKColors.Black, 0.18F).WithAlpha(If(Enabled, AvatarTokens.FillBottomAlpha, AvatarTokens.DisabledAlpha))

            Using shader As SKShader = SKShader.CreateLinearGradient(New SKPoint(snapped.Left, snapped.Top), New SKPoint(snapped.Left, snapped.Bottom), New SKColor() {topColor, bottomColor}, Nothing, SKShaderTileMode.Clamp)
                Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Shader = shader}
                    canvas.DrawOval(snapped, paint)
                End Using
            End Using

            Dim overlayAlpha As Byte = 0
            If pressed Then
                overlayAlpha = AvatarTokens.PressedAlpha
            ElseIf hovered Then
                overlayAlpha = AvatarTokens.HoverAlpha
            ElseIf selected Then
                overlayAlpha = AvatarTokens.SelectedAlpha
            End If

            If overlayAlpha > 0 Then
                Dim overlayColor As SKColor = If(selected AndAlso Not hovered AndAlso Not pressed,
                                                 ColorMath.Mix(baseColor, SKColors.White, 0.2F),
                                                 ResolveAccentColor(ctx))
                Using overlay As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = overlayColor.WithAlpha(overlayAlpha)}
                    canvas.DrawOval(snapped, overlay)
                End Using
            End If

            Dim strokeColor As SKColor = If(selected,
                                            ColorMath.Mix(baseColor, ResolveRingColor(ctx), 0.35F),
                                            ResolveRingColor(ctx))
            Dim strokeAlpha As Byte = If(selected, AvatarTokens.SelectedIntegratedStrokeAlpha, AvatarTokens.RingAlpha)
            Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, AvatarTokens.RingStrokeDip * dpi), .Color = strokeColor.WithAlpha(strokeAlpha)}
                canvas.DrawOval(snapped, stroke)
            End Using
        End Sub

        Private Sub DrawInitials(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 bounds As SKRect,
                                 dpi As Single)
            TypographyTextPrimitives.DrawSingleLineText(
                canvas:=canvas,
                ctx:=ctx,
                text:=ResolveRenderInitials(),
                bounds:=bounds,
                dpi:=dpi,
                style:=If(bounds.Width >= AvatarTokens.LargeSizeDip * dpi, MASTypography.MASTextStyle.Body, MASTypography.MASTextStyle.Small),
                color:=ResolveTextColor(ctx).WithAlpha(If(Enabled, AvatarTokens.TextAlpha, AvatarTokens.DisabledAlpha)),
                align:=TypographyTextPrimitives.TextAlign.Center,
                drawShadow:=False)
        End Sub

        Private Sub DrawPresence(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 bounds As SKRect,
                                 dpi As Single)
            Dim size As Single = MASAvatarIdentityPolicy.ResolvePresenceDiameter(bounds, dpi)
            Dim center As SKPoint = MASAvatarIdentityPolicy.ResolvePresenceCenter(bounds, dpi, ResolveRightToLeft())
            Using borderPaint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolveSurfaceColor(ctx)}
                canvas.DrawCircle(center, size / 2.0F + AvatarTokens.PresenceBorderDip * dpi, borderPaint)
            End Using
            Using fillPaint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = ResolvePresenceColor(ctx)}
                canvas.DrawCircle(center, size / 2.0F, fillPaint)
            End Using
        End Sub

        Private Sub DrawFocusRing(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  bounds As SKRect,
                                  dpi As Single)
            If Not HasKeyboardFocus Then Return
            Dim rect As SKRect = bounds
            rect.Inflate(2.0F * dpi, 2.0F * dpi)
            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, AvatarTokens.FocusStrokeDip * dpi), .Color = ResolveAccentColor(ctx).WithAlpha(AvatarTokens.FocusAlpha)}
                canvas.DrawOval(rect, paint)
            End Using
        End Sub

#End Region

#Region "Input"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim isInsideVisualAvatar As Boolean = HitAvatarVisual(ptPx)
            Dim changed As Boolean = (_hovered <> isInsideVisualAvatar)
            _hovered = isInsideVisualAvatar

            If Not isInsideVisualAvatar AndAlso _pressed Then
                _pressed = False
                changed = True
            End If

            If changed Then 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
            If Not HitAvatarVisual(ptPx) Then Return False

            RequestFocus()
            _pressed = True
            _hovered = True
            InvalidateVisual()
            Return True
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean
            If button <> CInt(MouseButtons.Left) Then Return False

            Dim wasPressed As Boolean = _pressed
            Dim isInsideVisualAvatar As Boolean = HitAvatarVisual(ptPx)
            _pressed = False
            _hovered = isInsideVisualAvatar

            If wasPressed AndAlso isInsideVisualAvatar Then RaiseClickedSafe()
            If wasPressed OrElse isInsideVisualAvatar Then InvalidateVisual()
            Return wasPressed OrElse isInsideVisualAvatar
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If Not _hovered AndAlso Not _pressed Then Return
            _hovered = False
            _pressed = False
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            _hovered = False
            _pressed = False
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            _hovered = False
            _pressed = False
            InvalidateVisual()
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            Select Case keyCode
                Case Keys.Enter, Keys.Space
                    RaiseClickedSafe()
                    Return True
            End Select
            Return False
        End Function

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

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

#End Region

#Region "Helpers"

        Friend Function ResolveRenderInitials() As String
            Return MASAvatarIdentityPolicy.ResolveInitials(_displayName, _initials)
        End Function

        Private Function HitAvatarVisual(ptPx As SKPoint) As Boolean
            Return MASAvatarIdentityPolicy.ContainsAvatarVisualPoint(_lastAvatarRect, ptPx, _lastRenderDpi)
        End Function

        Private Sub RaiseClickedSafe()
            Try
                RaiseEvent Clicked(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException1, "MASAvatar.Clicked")
            End Try
        End Sub

        Private Sub RaiseSelectedChangedSafe()
            Try
                RaiseEvent SelectedChanged(Me, EventArgs.Empty)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2, "MASAvatar.SelectedChanged")
            End Try
        End Sub

        Private Shared Function NormalizeDisplayName(value As String) As String
            Return MASAvatarIdentityPolicy.NormalizeDisplayName(value)
        End Function

        Private Shared Function NormalizePresence(value As String) As String
            Return MASAvatarIdentityPolicy.NormalizePresence(value)
        End Function

        Private Shared Function ResolveInitials(displayName As String,
                                                explicitInitials As String) As String
            Return MASAvatarIdentityPolicy.ResolveInitials(displayName, explicitInitials)
        End Function

        Private Shared Function BuildInitialsFromName(displayName As String) As String
            Return MASAvatarIdentityPolicy.ResolveInitials(displayName, Nothing)
        End Function

        Private Shared Function ResolveRightToLeft() As Boolean
            Return Nexamas.UI.Localization.MASLocalization.IsCurrentRightToLeft()
        End Function

        Private Function ResolvePresenceColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolvePresenceColor(ctx, _presenceText)
        End Function

        Private Shared Function ResolveSurfaceColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolveSurfaceColor(ctx)
        End Function

        Private Shared Function ResolveTextColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolveTextColor(ctx)
        End Function

        Private Shared Function ResolveMutedColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolveMutedColor(ctx)
        End Function

        Private Shared Function ResolveAccentColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolveAccentColor(ctx)
        End Function

        Private Shared Function ResolveRingColor(ctx As MASThemeContext) As SKColor
            Return MASAvatarIdentityPolicy.ResolveRingColor(ctx)
        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
