Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports SkiaSharp
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' MAS-owned compact icon button used by internal toolbars, preview chrome,
    ''' file navigation, toast close affordances, and other platform surfaces.
    ''' It is intentionally Friend; consumer applications should use MASButton
    ''' or higher-level service/facade APIs instead of creating glyph buttons.
    ''' </summary>
    Friend NotInheritable Class GlyphButton
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
    MASArchitectureRuntime.ResolveContractOrThrow(GetType(GlyphButton))

#Region "Fields"

        Private ReadOnly _renderer As New GlyphButtonRenderer()

        Private _intent As GlyphButtonIntent = GlyphButtonIntent.Neutral
        Private _iconKind As MASIconKind = MASIconKind.None
        Private _iconStyle As MASIconStyle = MASIconStyle.Outline
        Private _isHovered As Boolean
        Private _isPressed As Boolean
        Private _isKeyboardFocused As Boolean
        Private _isEnabledEx As Boolean = True
        Private _pressPulseRunner As MASMotionTimelineRunner
        Private _motionPressActive As Boolean

        Private _lastRenderBoundsPx As SKRect = SKRect.Empty

#End Region

#Region "Events"

        Friend Event Click As EventHandler

#End Region

#Region "Properties"

        Friend Property Intent As GlyphButtonIntent
            Get
                Return _intent
            End Get
            Set(value As GlyphButtonIntent)
                Dim normalized As GlyphButtonIntent = GlyphButtonInteractionPolicy.NormalizeIntent(value)
                If _intent = normalized Then Return
                _intent = normalized
                InvalidateVisual()
            End Set
        End Property

        Friend Property IconKind As MASIconKind
            Get
                Return _iconKind
            End Get
            Set(value As MASIconKind)
                Dim normalized As MASIconKind = GlyphButtonInteractionPolicy.NormalizeIconKind(value)
                If _iconKind = normalized Then Return
                _iconKind = normalized
                InvalidateVisual()
            End Set
        End Property

        Friend Property IconStyle As MASIconStyle
            Get
                Return _iconStyle
            End Get
            Set(value As MASIconStyle)
                Dim normalized As MASIconStyle = GlyphButtonInteractionPolicy.NormalizeIconStyle(value)
                If _iconStyle = normalized Then Return
                _iconStyle = normalized
                InvalidateVisual()
            End Set
        End Property

        Friend Property EnabledEx As Boolean
            Get
                Return _isEnabledEx
            End Get
            Set(value As Boolean)
                If _isEnabledEx = value Then Return
                _isEnabledEx = value

                If Not _isEnabledEx Then
                    ClearInteractionState()
                    StopPressMotion(clearPressed:=True)
                End If

                InvalidateVisual()
            End Set
        End Property

        Friend ReadOnly Property IsHoveredEx As Boolean
            Get
                Return _isHovered
            End Get
        End Property

        Friend ReadOnly Property IsPressedEx As Boolean
            Get
                Return _isPressed
            End Get
        End Property

#End Region

#Region "Focus"

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return GlyphButtonInteractionPolicy.CanReceiveKeyboardFocus(_isEnabledEx)
        End Function

        Protected Overrides Sub OnGotKeyboardFocus()
            MyBase.OnGotKeyboardFocus()
            If _isKeyboardFocused Then Return
            _isKeyboardFocused = True
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnLostKeyboardFocus()
            MyBase.OnLostKeyboardFocus()
            If Not _isKeyboardFocused AndAlso Not _isPressed Then Return
            _isKeyboardFocused = False
            _isPressed = False
            InvalidateVisual()
        End Sub

#End Region


#Region "Size Contract"

        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 SKSize = MASIntrinsicControlSizeMetrics.ResolveGlyphButtonDesiredSize(_intent = GlyphButtonIntent.Toolbar)
            Dim minimum As SKSize = MASIntrinsicControlSizeMetrics.ResolveGlyphButtonMinimumSize()

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=False,
                canGrowHeight:=False,
                contentInset:=MASIntrinsicControlSizeMetrics.CreateGlyphButtonContentInset(),
                visualOverflowInset:=MASIntrinsicControlSizeMetrics.CreateGlyphButtonVisualOverflowInset(),
                hitOverflowInset:=MASIntrinsicControlSizeMetrics.CreateTouchHitOverflowInset(desired, safeContext.Density),
                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 "Render"

        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

            _lastRenderBoundsPx = pixelBounds

            Dim state As GlyphButtonVisualState = ResolveVisualState()

            Dim opacity As Single = If(_motionPressActive, 0.92F, 1.0F)

            _renderer.Draw(
    canvas:=canvas,
    ctx:=ctx,
    boundsPx:=pixelBounds,
    state:=state,
    opacity:=opacity,
    intent:=_intent,
    iconKind:=_iconKind,
    iconStyle:=_iconStyle
)
        End Sub

#End Region

#Region "HitTest"

        Protected Overrides Function HitTest(ctx As MASThemeContext, ptPx As SKPoint) As Boolean
            If Not _isEnabledEx Then Return False

            Dim r As SKRect = GetPixelHitBounds(ctx)
            If r.Width <= 1.0F OrElse r.Height <= 1.0F Then
                r = _lastRenderBoundsPx
            End If

            If r.Width <= 1.0F OrElse r.Height <= 1.0F Then Return False
            Return r.Contains(ptPx.X, ptPx.Y)
        End Function

#End Region

#Region "Mouse"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            MyBase.OnMouseMove(ctx, ptPx)

            If Not _isEnabledEx Then Return

            Dim hoveredNow As Boolean = HitTest(ctx, ptPx)
            If hoveredNow = _isHovered Then Return

            _isHovered = hoveredNow
            If Not _isHovered Then
                _isPressed = False
            End If

            InvalidateVisual()
        End Sub

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext, ptPx As SKPoint, button As Integer) As Boolean
            If Not _isEnabledEx Then Return False
            If Not IsPrimaryButton(button) Then Return False
            If Not HitTest(ctx, ptPx) Then Return False

            RequestFocus()

            _isPressed = True
            _isHovered = True
            InvalidateVisual()
            Return True
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext, ptPx As SKPoint, button As Integer) As Boolean
            If Not IsPrimaryButton(button) Then Return False

            Dim wasPressed As Boolean = _isPressed
            Dim inside As Boolean = _isEnabledEx AndAlso HitTest(ctx, ptPx)

            _isPressed = False
            _isHovered = inside

            InvalidateVisual()

            If _isEnabledEx AndAlso wasPressed AndAlso inside Then
                PulsePressReleaseMotion()
                Activate()
                Return True
            End If

            Return wasPressed
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            MyBase.OnMouseLeave(ctx)

            If Not _isHovered AndAlso Not _isPressed Then Return

            ClearInteractionState()
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            MyBase.OnMouseCancel(ctx)

            If Not _isHovered AndAlso Not _isPressed Then Return

            ClearInteractionState()
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            MyBase.OnHostLostFocus(ctx)

            If Not _isHovered AndAlso Not _isPressed AndAlso Not _motionPressActive Then Return

            ClearInteractionState()
            StopPressMotion(clearPressed:=True)
            InvalidateVisual()
        End Sub

#End Region

#Region "Keyboard"

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext, keyCode As Keys) As Boolean
            If Not GlyphButtonInteractionPolicy.CanActivate(_isEnabledEx) Then Return False

            If GlyphButtonInteractionPolicy.IsKeyboardActivationKey(keyCode) Then
                _isPressed = False
                _isHovered = False
                PulsePressReleaseMotion()
                Activate()
                Return True
            End If

            If GlyphButtonInteractionPolicy.IsKeyboardCancelKey(keyCode) Then
                If Not _isPressed AndAlso Not _isHovered Then Return False
                _isPressed = False
                _isHovered = False
                StopPressMotion(clearPressed:=True)
                InvalidateVisual()
                Return True
            End If

            Return False
        End Function

#End Region

#Region "Helpers"

        Private Function ResolveVisualState() As GlyphButtonVisualState
            Return GlyphButtonInteractionPolicy.ResolveVisualState(
                isEnabled:=_isEnabledEx,
                isPressed:=(_isPressed OrElse _motionPressActive),
                isHovered:=_isHovered,
                isKeyboardFocused:=_isKeyboardFocused)
        End Function

        Private Sub Activate()
            If Not GlyphButtonInteractionPolicy.CanActivate(_isEnabledEx) Then Return
            RaiseEvent Click(Me, EventArgs.Empty)
        End Sub

        Private Sub ClearInteractionState()
            _isHovered = False
            _isPressed = False
        End Sub

        Private Sub PulsePressReleaseMotion()
            If Not GlyphButtonInteractionPolicy.CanActivate(_isEnabledEx) Then Return

            StopPressMotion(clearPressed:=False)
            _motionPressActive = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.PressRelease, 0.0F, 1.0F)
            _pressPulseRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="GlyphButton.PressReleasePulse",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyPressMotionFrame,
                completed:=AddressOf CompletePressMotionFrame)
            _pressPulseRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyPressMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            InvalidateVisual()
        End Sub

        Private Sub CompletePressMotionFrame(frame As MASMotionFrame)
            StopPressMotion(clearPressed:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopPressMotion(clearPressed As Boolean)
            If _pressPulseRunner IsNot Nothing Then
                _pressPulseRunner.Dispose()
                _pressPulseRunner = Nothing
            End If

            If clearPressed Then
                _motionPressActive = False
            End If
        End Sub

        Private Shared Function IsPrimaryButton(button As Integer) As Boolean
            Return button = 0 OrElse button = CInt(MouseButtons.Left)
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If disposing Then
                StopPressMotion(clearPressed:=True)

                Try
                    _renderer.Dispose()
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
                End Try
            End If

            MyBase.Dispose(disposing)
        End Sub

#End Region


#Region "Unified MASSize Fluent API"

        ''' <summary>
        ''' Strongly typed entry into the shared MASSize intent API.
        ''' This method keeps fluent chains on the concrete element while delegating all sizing decisions to MASControlBase/MASSize.
        ''' </summary>
        Friend Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As GlyphButton
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

        Friend Shadows Function WithDefaultSize() As GlyphButton
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.[Default])
            Return Me
        End Function

        Friend Shadows Function WithCompactSize() As GlyphButton
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Compact)
            Return Me
        End Function

        Friend Shadows Function WithLargeSize() As GlyphButton
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Large)
            Return Me
        End Function

        Friend Shadows Function WithFillWidthSize() As GlyphButton
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.FillWidth)
            Return Me
        End Function

        Friend Shadows Function WithHugContentSize() As GlyphButton
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.HugContent)
            Return Me
        End Function

#End Region
    End Class

End Namespace
