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 toggle-switch control. External projects configure semantic
    ''' state through Text, Checked, CheckedChanged, and fluent helpers; track,
    ''' thumb, input-hit geometry, and rendering remain Nexamas UI-owned internals.
    ''' </summary>
    Public NotInheritable Class MASToggleSwitch
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private ReadOnly _painter As New ToggleSwitchPainter()

        Private _text As String = String.Empty
        Private _checked As Boolean
        Private _isHover As Boolean
        Private _isPressed As Boolean
        Private _hasFocus As Boolean
        Private _selectionRunner As MASMotionTimelineRunner
        Private _selectionProgress As Single
        Private _lastRenderBoundsPx As SKRect = SKRect.Empty

#End Region

#Region "Events / Construction"

        Public Event CheckedChanged As EventHandler

        Public Sub New()
            MyBase.New()
            _selectionProgress = 0.0F
        End Sub

        Public Sub New(text As String)
            Me.New()
            Me.Text = text
        End Sub

#End Region

#Region "Properties"

        Public Property [Text] As String
            Get
                Return _text
            End Get
            Set(value As String)
                Dim s As String = MASToggleSwitchInteractionPolicy.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)
                SetCheckedInternal(value, animate:=False)
            End Set
        End Property

#End Region

#Region "SDK Fluent API"

        Public Shared Function Create(Optional text As String = Nothing,
                                      Optional isChecked As Boolean = False) As MASToggleSwitch
            Dim toggleSwitch As New MASToggleSwitch()

            If text IsNot Nothing Then
                toggleSwitch.Text = text
            End If

            toggleSwitch.Checked = isChecked
            Return toggleSwitch
        End Function

        Friend Shared Function [Default]() As MASToggleSwitch
            Return Create()
        End Function

        Friend Function SetBounds(bounds As SKRect) As MASToggleSwitch
            Return WithBounds(bounds)
        End Function

        Friend Function WithBounds(bounds As SKRect) As MASToggleSwitch
            Return WithBounds(bounds.Left, bounds.Top, bounds.Width, bounds.Height)
        End Function

        Friend Function WithBounds(x As Single,
                                   y As Single,
                                   width As Single,
                                   height As Single) As MASToggleSwitch

            Dim safeX As Single = NormalizeCoordinate(x)
            Dim safeY As Single = NormalizeCoordinate(y)

             SetLocalLayoutBoundsInternal(New SKRect(
                safeX,
                safeY,
                safeX + NormalizeExtent(width),
                safeY + NormalizeExtent(height)
            ))

            Return Me
        End Function

        Public Function WithText(text As String) As MASToggleSwitch
            Me.Text = MASToggleSwitchInteractionPolicy.NormalizeText(text)
            Return Me
        End Function

        Public Function WithChecked(Optional value As Boolean = True) As MASToggleSwitch
            Checked = value
            Return Me
        End Function

        Public Function CheckedState(Optional value As Boolean = True) As MASToggleSwitch
            Return WithChecked(value)
        End Function

        Public Function Unchecked() As MASToggleSwitch
            Checked = False
            Return Me
        End Function

        Public Function ToggleState() As MASToggleSwitch
            Toggle()
            Return Me
        End Function

        Public Sub Toggle()
            If Not MASToggleSwitchInteractionPolicy.CanInteract(Enabled, Visible) Then Return
            SetCheckedInternal(MASToggleSwitchInteractionPolicy.ResolveNextChecked(Checked), animate:=True)
        End Sub

        Public Sub SetChecked(value As Boolean)
            If Not MASToggleSwitchInteractionPolicy.CanInteract(Enabled, Visible) Then Return
            Checked = value
        End Sub

        Friend Function OnCheckedChanged(handler As EventHandler) As MASToggleSwitch
            If handler IsNot Nothing Then
                AddHandler CheckedChanged, handler
            End If

            Return Me
        End Function

        Friend Function OnCheckedChanged(action As Action(Of MASToggleSwitch, Boolean)) As MASToggleSwitch
            If action Is Nothing Then Return Me

            AddHandler CheckedChanged,
                Sub(sender As Object, e As EventArgs)
                    action.Invoke(Me, Checked)
                End Sub

            Return Me
        End Function

#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,
                ToggleSwitchTokens.Width,
                ToggleSwitchTokens.LabelGap,
                ToggleSwitchTokens.TextPadRight)

            Dim textSize As SKSize = MASLayoutTextMeasureGateway.Measure(
                safeContext.IntegrationContext,
                _text,
                MASTypography.MASTextStyle.Body,
                maxTextWidth,
                allowWrap:=False).Size

            Dim desired As SKSize = MASIntrinsicControlSizeMetrics.ResolveChoiceControlDesiredSize(
                ToggleSwitchTokens.Width,
                ToggleSwitchTokens.Height,
                ToggleSwitchTokens.MinHeight,
                ToggleSwitchTokens.LabelGap,
                ToggleSwitchTokens.TextPadRight,
                ToggleSwitchTokens.VerticalTextSafetyPad,
                textSize,
                hasText,
                safeContext.AvailableSize.Width)

            Dim minimum As SKSize = MASIntrinsicControlSizeMetrics.ResolveChoiceControlMinimumSize(
                ToggleSwitchTokens.Width,
                ToggleSwitchTokens.Height,
                ToggleSwitchTokens.MinHeight,
                ToggleSwitchTokens.LabelGap,
                ToggleSwitchTokens.TextPadRight,
                ToggleSwitchTokens.MinLabelWidth,
                ToggleSwitchTokens.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 / Keyboard"

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

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext, keyCode As Keys) As Boolean
            If Not MASToggleSwitchInteractionPolicy.CanInteract(Enabled, Visible) Then Return False

            If MASToggleSwitchInteractionPolicy.ShouldCancelFromKey(keyCode) Then
                ResetTransientState(clearFocus:=False)
                Return True
            End If

            If MASToggleSwitchInteractionPolicy.ShouldToggleFromKey(keyCode) Then
                Toggle()
                Return True
            End If

            Dim nextChecked As Boolean = Checked
            If MASToggleSwitchInteractionPolicy.TryResolveKeyboardState(keyCode, Checked, nextChecked) Then
                SetCheckedInternal(nextChecked, animate:=True)
                ResetTransientState(clearFocus:=False)
                Return True
            End If

            Return False
        End Function

        Protected Overrides Sub OnGotKeyboardFocus()
            If _hasFocus Then Return
            _hasFocus = True
            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnLostKeyboardFocus()
            ResetTransientState(clearFocus:=True)
        End Sub

        Protected Overrides Sub OnHostLostFocus(ctx As MASThemeContext)
            ResetTransientState(clearFocus:=False)
        End Sub

#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,
    hasFocus:=_hasFocus,
    isEnabled:=Enabled,
    isChecked:=_checked,
    contract:=_contract,
    selectionProgress:=_selectionProgress)
        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 MASToggleSwitchInteractionPolicy.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()
                Return True
            End If

            If wasPressed Then
                InvalidateVisual()
                Return True
            End If

            Return False
        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)
            ResetTransientState(clearFocus:=False)
        End Sub

        Protected Overrides Sub OnMouseCancel(ctx As MASThemeContext)
            ResetTransientState(clearFocus:=False)
        End Sub

#End Region

#Region "Lifecycle"

        Protected Overrides Sub OnEnabledChanged()
            If MASToggleSwitchInteractionPolicy.ShouldResetTransientState(Enabled, Visible) Then
                ResetTransientState(clearFocus:=True)
            End If

            InvalidateVisual()
        End Sub

        Protected Overrides Sub OnDetached()
            ResetTransientState(clearFocus:=True)
            StopSelectionMotion(syncToCheckedState:=True)
            _lastRenderBoundsPx = SKRect.Empty
            MyBase.OnDetached()
        End Sub

        Protected Overrides Sub Dispose(disposing As Boolean)
            If disposing Then
                StopSelectionMotion(syncToCheckedState:=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 Function PreciseHitTest(ctx As MASThemeContext, ptPx As SKPoint) As Boolean
            If Not MASToggleSwitchInteractionPolicy.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 ToggleSwitchLayoutHelper.LayoutInfo =
                ToggleSwitchLayoutHelper.BuildLayout(
                    ctx:=ctx,
                    boundsPx:=boundsPx,
                    text:=_text,
                    isPressed:=False,
                    isChecked:=_checked)

            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 Sub ResetTransientState(clearFocus As Boolean)
            Dim needInvalidate As Boolean = (_isHover OrElse _isPressed OrElse (clearFocus AndAlso _hasFocus))
            _isHover = False
            _isPressed = False
            If clearFocus Then _hasFocus = False

            If needInvalidate Then
                InvalidateVisual()
            End If
        End Sub


        Private Sub SetCheckedInternal(value As Boolean, animate As Boolean)
            If _checked = value Then Return

            Dim fromProgress As Single = ResolveSelectionProgressForState(_selectionProgress, _checked)
            _checked = value

            If animate Then
                StartSelectionMotion(fromProgress, If(_checked, 1.0F, 0.0F))
            Else
                StopSelectionMotion(syncToCheckedState:=True)
            End If

            RaiseEvent CheckedChanged(Me, EventArgs.Empty)
            InvalidateVisual()
        End Sub

        Private Sub StartSelectionMotion(fromProgress As Single, toProgress As Single)
            fromProgress = ClampMotionProgress(fromProgress)
            toProgress = ClampMotionProgress(toProgress)

            StopSelectionMotion(syncToCheckedState:=False)
            _selectionProgress = fromProgress

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(
                MASMotionTokenId.SelectionMove,
                fromProgress,
                toProgress)

            _selectionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASToggleSwitch.SelectionMove",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplySelectionMotionFrame,
                completed:=AddressOf CompleteSelectionMotionFrame)
            _selectionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplySelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _selectionProgress = ClampMotionProgress(frame.Value)
            InvalidateVisual()
        End Sub

        Private Sub CompleteSelectionMotionFrame(frame As MASMotionFrame)
            _selectionProgress = If(_checked, 1.0F, 0.0F)

            If _selectionRunner IsNot Nothing Then
                _selectionRunner.Dispose()
                _selectionRunner = Nothing
            End If

            InvalidateVisual()
        End Sub

        Private Sub StopSelectionMotion(syncToCheckedState As Boolean)
            If _selectionRunner IsNot Nothing Then
                _selectionRunner.Dispose()
                _selectionRunner = Nothing
            End If

            If syncToCheckedState Then
                _selectionProgress = If(_checked, 1.0F, 0.0F)
            End If
        End Sub

        Private Shared Function ResolveSelectionProgressForState(currentProgress As Single, currentChecked As Boolean) As Single
            If Single.IsNaN(currentProgress) OrElse Single.IsInfinity(currentProgress) Then
                Return If(currentChecked, 1.0F, 0.0F)
            End If

            Return ClampMotionProgress(currentProgress)
        End Function

        Private Shared Function ClampMotionProgress(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 0.0F
            If value <= 0.0F Then Return 0.0F
            If value >= 1.0F Then Return 1.0F
            Return value
        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 MASToggleSwitch
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function






#End Region
    End Class

End Namespace
