Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public MAS checkbox control. External projects configure semantic state
    ''' through Text, Checked, CheckedChanged, and the fluent creation helpers;
    ''' painting, layout measuring, hit testing, and theme resolution remain
    ''' Nexamas UI-owned internals.
    ''' </summary>
    Public NotInheritable Class MASCheckBox
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
    MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASCheckBox))

        Private ReadOnly _painter As New CheckBoxPainter()

        Private _text As String = String.Empty
        Private _checked As Boolean

        Private _isHover As Boolean
        Private _isPressed As Boolean
        Private _hasFocusVisual As Boolean
        Private _pressPulseRunner As MASMotionTimelineRunner
        Private _motionPressActive As Boolean
        Private _lastRenderBoundsPx As SKRect = SKRect.Empty

#End Region

#Region "Ctor"

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(text As String)
            Me.New()
            Me.Text = text
        End Sub

#End Region

#Region "Events"

        Public Event CheckedChanged As EventHandler

#End Region

#Region "Factories"

        ''' <summary>
        ''' Creates a new MAS checkbox using the standard SDK entry pattern.
        ''' </summary>
        Public Shared Function Create(Optional text As String = Nothing,
                                      Optional isChecked As Boolean = False) As MASCheckBox

            Dim checkBox As New MASCheckBox()

            If text IsNot Nothing Then
                checkBox.Text = text
            End If

            checkBox.Checked = isChecked
            Return checkBox
        End Function

#End Region

#Region "Properties"

        Public Property Text As String
            Get
                Return _text
            End Get
            Set(value As String)
                Dim s As String = MASCheckBoxInteractionPolicy.NormalizeText(value)
                If _text = s Then Return

                _text = s
                RaiseTextChanged()
                InvalidateVisual()
            End Set
        End Property

        Public Property Checked As Boolean
            Get
                Return _checked
            End Get
            Set(value As Boolean)
                Dim normalized As Boolean = MASCheckBoxInteractionPolicy.NormalizeChecked(value)
                If _checked = normalized Then Return

                _checked = normalized
                RaiseEvent CheckedChanged(Me, EventArgs.Empty)
                InvalidateVisual()
            End Set
        End Property

#End Region

#Region "API"

        Friend Sub SetBounds(bounds As SKRect)
            SetBounds(bounds.Left, bounds.Top, bounds.Width, bounds.Height)
        End Sub

        Friend Sub SetBounds(x As Single,
                             y As Single,
                             width As Single,
                             height As Single)

            Dim safeX As Single = NormalizeCoordinate(x)
            Dim safeY As Single = NormalizeCoordinate(y)

             SetLocalLayoutBoundsInternal(New SKRect(
                safeX,
                safeY,
                safeX + NormalizeExtent(width),
                safeY + NormalizeExtent(height)
            ))
        End Sub

        Friend Function WithBounds(bounds As SKRect) As MASCheckBox
            SetBounds(bounds)
            Return Me
        End Function

        Friend Function WithBounds(x As Single,
                                   y As Single,
                                   width As Single,
                                   height As Single) As MASCheckBox

            SetBounds(x, y, width, height)
            Return Me
        End Function

        Public Function WithText(value As String) As MASCheckBox
            Text = value
            Return Me
        End Function

        Public Function WithChecked(Optional value As Boolean = True) As MASCheckBox
            Checked = value
            Return Me
        End Function

        Public Sub Toggle()
            If Not MASCheckBoxInteractionPolicy.CanInteract(Enabled, Visible) Then Return
            Checked = MASCheckBoxInteractionPolicy.ResolveNextChecked(_checked)
        End Sub

        Public Sub SetChecked(value As Boolean)
            Checked = value
        End Sub

        Public Sub SetFocusVisual(value As Boolean)
            SetKeyboardFocusInternal(value)
        End Sub

#End Region

#Region "Composition"

        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 hasText As Boolean = Not String.IsNullOrWhiteSpace(_text)
            Dim maxTextWidth As Single = MASIntrinsicControlSizeMetrics.ResolveSelectionLabelAvailableWidth(
                safeContext.AvailableSize.Width,
                CheckBoxTokens.BoxSize,
                CheckBoxTokens.LabelGap,
                CheckBoxTokens.TextPadRight)

            Dim textSize As SKSize = MASLayoutTextMeasureGateway.Measure(
                safeContext.IntegrationContext,
                _text,
                MASTypography.MASTextStyle.Body,
                maxTextWidth,
                allowWrap:=False).Size

            Dim desired As SKSize = MASIntrinsicControlSizeMetrics.ResolveChoiceControlDesiredSize(
                CheckBoxTokens.BoxSize,
                CheckBoxTokens.BoxSize,
                CheckBoxTokens.MinHeight,
                CheckBoxTokens.LabelGap,
                CheckBoxTokens.TextPadRight,
                CheckBoxTokens.VerticalTextSafetyPad,
                textSize,
                hasText,
                safeContext.AvailableSize.Width)

            Dim minimum As SKSize = MASIntrinsicControlSizeMetrics.ResolveChoiceControlMinimumSize(
                CheckBoxTokens.BoxSize,
                CheckBoxTokens.BoxSize,
                CheckBoxTokens.MinHeight,
                CheckBoxTokens.LabelGap,
                CheckBoxTokens.TextPadRight,
                CheckBoxTokens.MinLabelWidth,
                CheckBoxTokens.VerticalTextSafetyPad,
                textSize,
                hasText)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=MASLayoutInset.Empty,
                visualOverflowInset:=MASIntrinsicControlSizeMetrics.DefaultVisualOverflowInset,
                hitOverflowInset:=MASIntrinsicControlSizeMetrics.CreateTouchHitOverflowInset(desired, safeContext.Density),
                isFallback:=False)
        End Function

#End Region

#Region "Focus Contract"

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return MASCheckBoxInteractionPolicy.WantsKeyboardFocus(Enabled, Visible)
        End Function

        Protected Overrides Sub OnGotKeyboardFocus()
            If _hasFocusVisual Then Return
            _hasFocusVisual = True
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnLostKeyboardFocus()
            Dim needInvalidate As Boolean = (_hasFocusVisual OrElse _isPressed OrElse _motionPressActive)
            _hasFocusVisual = False
            _isPressed = False
            StopPressMotion(clearPressed:=True)

            If needInvalidate Then
                InvalidateVisual()
            End If
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            ResetTransientState(clearFocus:=False)
        End Sub

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext, keyCode As Keys) As Boolean
            If Not MASCheckBoxInteractionPolicy.CanInteract(Enabled, Visible) Then Return False

            If MASCheckBoxInteractionPolicy.ShouldToggleFromKey(keyCode) Then
                Toggle()
                PulsePressReleaseMotion()
                Return True
            End If

            If MASCheckBoxInteractionPolicy.ShouldCancelFromKey(keyCode) Then
                If _isPressed OrElse _motionPressActive Then
                    _isPressed = False
                    StopPressMotion(clearPressed:=True)
                    InvalidateVisual()
                    Return True
                End If
            End If

            Return False
        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

            _painter.Draw(
    canvas:=canvas,
    ctx:=ctx,
    boundsPx:=pixelBounds,
    text:=_text,
    dpi:=If(ctx IsNot Nothing, ctx.Dpi, 1.0F),
    isHover:=_isHover,
    isPressed:=(_isPressed OrElse _motionPressActive),
    hasFocus:=_hasFocusVisual,
    isEnabled:=Enabled,
    isChecked:=_checked,
    contract:=_contract)
        End Sub

#End Region

#Region "Mouse"

        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 MASCheckBoxInteractionPolicy.CanInteract(Enabled, Visible) Then Return False
            If Not PreciseHitTest(ctx, ptPx) Then Return False

            RequestFocus()

            _isPressed = 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 = _isPressed
            _isPressed = False

            If wasPressed AndAlso PreciseHitTest(ctx, ptPx) Then
                Toggle()
                PulsePressReleaseMotion()
                Return True
            End If

            If wasPressed Then
                InvalidateVisual()
            End If

            Return wasPressed
        End Function

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim overNow As Boolean = PreciseHitTest(ctx, ptPx)
            If _isHover = overNow Then Return

            _isHover = overNow
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _isHover OrElse _isPressed OrElse _motionPressActive Then
                _isHover = False
                _isPressed = False
                StopPressMotion(clearPressed:=True)
                InvalidateVisual()
            End If
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            If _isPressed OrElse _motionPressActive Then
                _isPressed = False
                StopPressMotion(clearPressed:=True)
                InvalidateVisual()
            End If
        End Sub

#End Region

#Region "Lifecycle"

        Protected Overrides Sub OnEnabledChanged()
            If Not Enabled Then
                ResetTransientState(clearFocus:=True)
            End If

            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnDetached()
            ResetTransientState(clearFocus:=True)
            _lastRenderBoundsPx = SKRect.Empty
            MyBase.OnDetached()
        End Sub

        Protected Overrides Sub Dispose(disposing As Boolean)
            If disposing Then
                StopPressMotion(clearPressed:=True)

                Try
                    _painter.Dispose()
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
                End Try
            End If

            MyBase.Dispose(disposing)
        End Sub

#End Region

#Region "Private Helpers"

        Private Sub ResetTransientState(clearFocus As Boolean)
            Dim needInvalidate As Boolean = (_isHover OrElse _isPressed OrElse _motionPressActive OrElse (clearFocus AndAlso _hasFocusVisual))

            _isHover = False
            _isPressed = False
            StopPressMotion(clearPressed:=True)

            If clearFocus Then
                _hasFocusVisual = False
            End If

            If needInvalidate Then
                InvalidateVisual()
            End If
        End Sub

        Private Sub PulsePressReleaseMotion()
            If Not MASCheckBoxInteractionPolicy.CanInteract(Enabled, Visible) Then Return

            _motionPressActive = True
            StopPressMotion(clearPressed:=False)
            _motionPressActive = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.PressRelease, 0.0F, 1.0F)

            _pressPulseRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASCheckBox.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 Function PreciseHitTest(ctx As MASThemeContext, ptPx As SKPoint) As Boolean
            If Not MASCheckBoxInteractionPolicy.CanInteract(Enabled, Visible) Then Return False

            Dim boundsPx As SKRect = _lastRenderBoundsPx
            If boundsPx.Width <= 0.0F OrElse boundsPx.Height <= 0.0F Then
                boundsPx = GetPixelBounds(ctx)
            End If

            If boundsPx.Width <= 0.0F OrElse boundsPx.Height <= 0.0F Then Return False

            Dim layout As CheckBoxLayoutHelper.LayoutInfo =
                CheckBoxLayoutHelper.BuildLayout(
                    ctx:=ctx,
                    boundsPx:=boundsPx,
                    text:=_text)

            If layout.InteractiveRect.Width <= 0.0F OrElse layout.InteractiveRect.Height <= 0.0F Then
                Return False
            End If

            Return layout.InteractiveRect.Contains(ptPx)
        End Function


        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return New MASSizeContext(New SKSize(Single.PositiveInfinity, Single.PositiveInfinity))
            Return context.ToSizeContext()
        End Function

        Private Shared Function NormalizeExtent(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) OrElse value < 0.0F Then Return 0.0F
            Return value
        End Function

        Private Shared Function NormalizeCoordinate(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 0.0F
            Return value
        End Function

#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>
        Public Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As MASCheckBox
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function






#End Region
    End Class

End Namespace
