'========================================================
' File: SegmentedControlBox.vb
' - FIXED: primitives are owned internally (no public exposure)
' - FIXED: primitives disposed (no leak)
' - FIXED: invalidate after host rect update
'========================================================
Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports SkiaSharp
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Motion
Namespace Nexamas.UI.Components

    Friend NotInheritable Class SegmentedControlBox
        Implements IDisposable

        Private ReadOnly _invalidate As Action

        ' ✅ OWNED internally (Friend types not exposed)
        Private ReadOnly _primitives As MASVisualPrimitivesPainter
        Private ReadOnly _shadow As MASShadowPainter
        Private ReadOnly _sw As SegmentedControl

        Private _dpi As Single = 1.0F
        Private _hostRect As SKRect = SKRect.Empty
        Private _disposed As Boolean
        Private _selectionMotionRunner As MASMotionTimelineRunner

        Friend Event SelectedChanged(newIndex As Integer)
        Friend Property Enabled As Boolean = True

        Friend Sub New(Optional invalidate As Action = Nothing)
            _invalidate = invalidate

            _primitives = New MASVisualPrimitivesPainter()
            _shadow = New MASShadowPainter()
            _sw = New SegmentedControl(_primitives, _shadow)

            AddHandler _sw.SelectedChanged, AddressOf OnInnerSelectedChanged
        End Sub

        Friend Property SelectedIndex As Integer
            Get
                If _disposed Then Return 0
                Return _sw.SelectedIndex
            End Get
            Set(value As Integer)
                If _disposed Then Return
                Dim before As Integer = _sw.SelectedIndex
                _sw.SelectedIndex = value
                If _sw.SelectedIndex <> before Then
                    StartAnimIfNeeded()
                    SafeInvalidate()
                End If
            End Set
        End Property

        Friend ReadOnly Property ItemsCount As Integer
            Get
                If _disposed Then Return 0
                Dim it = _sw.Items
                Return If(it Is Nothing, 0, it.Count)
            End Get
        End Property

        Friend ReadOnly Property Items As IReadOnlyList(Of String)
            Get
                If _disposed Then Return Array.Empty(Of String)()
                Return _sw.Items
            End Get
        End Property

        Friend Sub SetItems(ParamArray labels() As String)
            If _disposed Then Return
            StopSelectionMotion()
            _sw.SetItems(labels)
            SafeInvalidate()
        End Sub

        Friend Sub SetDpi(dpi As Single)
            If _disposed Then Return
            Dim d As Single = PixelSnap.SafeDpi(dpi)
            If Math.Abs(_dpi - d) < 0.0001F Then Return
            _dpi = d
            _sw.SetDpi(_dpi)
            SafeInvalidate()
        End Sub

        Friend Function PreferredHeightPx(baseRowHeightPx As Single) As Single
            If _disposed Then Return 0.0F
            Return _sw.PreferredHeightPx(baseRowHeightPx)
        End Function

        Friend Sub SetHostRect(hostRect As SKRect)
            If _disposed Then Return
            If hostRect = _hostRect Then Return
            _hostRect = PixelSnap.SnapRectHalf(hostRect)
            _sw.SetHostRect(_hostRect)
            SafeInvalidate()
        End Sub

        Friend ReadOnly Property HostRect As SKRect
            Get
                Return _hostRect
            End Get
        End Property

        Friend Sub Draw(canvas As SKCanvas, ctx As MASThemeContext)
            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            _sw.Draw(canvas, ctx)
        End Sub

        Friend Function HitTest(x As Single, y As Single) As Boolean
            If _disposed Then Return False
            Return _sw.HitTest(x, y)
        End Function

        Friend Sub PointerMove(x As Single, y As Single)
            If _disposed OrElse Not Enabled Then Return
            If _sw.PointerMove(x, y) Then SafeInvalidate()
        End Sub

        Friend Sub PointerLeave()
            If _disposed Then Return
            If _sw.PointerLeave() Then SafeInvalidate()
        End Sub

        Friend Function PointerDown(x As Single, y As Single) As Boolean
            If _disposed OrElse Not Enabled Then Return False

            Dim changed As Boolean = _sw.PointerDown(x, y)
            If changed Then SafeInvalidate()

            Return changed
        End Function

        Friend Function PointerUp(x As Single, y As Single) As Boolean
            If _disposed OrElse Not Enabled Then Return False

            Dim changed As Boolean = _sw.PointerUp(x, y)
            If changed Then
                StartAnimIfNeeded()
                SafeInvalidate()
            End If

            Return changed
        End Function

        Private Sub OnInnerSelectedChanged(newIndex As Integer)
            If _disposed Then Return
            RaiseEvent SelectedChanged(newIndex)
            StartAnimIfNeeded()
            SafeInvalidate()
        End Sub

        Private Sub StartAnimIfNeeded()
            If _disposed Then Return

            If Not _sw.IsAnimating Then
                StopSelectionMotion()
                Return
            End If

            StopSelectionMotion()

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.SelectionMove, 0.0F, 1.0F)
            If Not plan.ShouldAnimate Then
                _sw.CompleteAnimation()
                SafeInvalidate()
                Return
            End If

            _selectionMotionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="SegmentedControlBox.SelectionMove",
                plan:=plan,
                requestFrame:=AddressOf SafeInvalidate,
                applyFrame:=AddressOf ApplySelectionMotionFrame,
                completed:=AddressOf CompleteSelectionMotionFrame)
            _selectionMotionRunner.Start()
        End Sub

        Private Sub ApplySelectionMotionFrame(frame As MASMotionFrame)
            If _disposed OrElse frame Is Nothing Then Return
            _sw.SetAnimationProgress(frame.Value)
            SafeInvalidate()
        End Sub

        Private Sub CompleteSelectionMotionFrame(frame As MASMotionFrame)
            If _disposed Then Return
            If frame IsNot Nothing Then _sw.CompleteAnimation()
            _selectionMotionRunner = Nothing
            SafeInvalidate()
        End Sub

        Private Sub StopSelectionMotion()
            If _selectionMotionRunner IsNot Nothing Then
                _selectionMotionRunner.Dispose()
                _selectionMotionRunner = Nothing
            End If
        End Sub

        Private Sub SafeInvalidate()
            If _disposed OrElse _invalidate Is Nothing Then Return
            Try
                _invalidate.Invoke()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException1)
            End Try
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True

            StopSelectionMotion()

            If _sw IsNot Nothing Then
                RemoveHandler _sw.SelectedChanged, AddressOf OnInnerSelectedChanged
                _sw.Dispose()
            End If

            If _shadow IsNot Nothing Then
                _shadow.Dispose()
            End If

            ' ✅ IMPORTANT: dispose primitives (owned here)
            If _primitives IsNot Nothing Then
                _primitives.Dispose()
            End If
        End Sub

    End Class

End Namespace
