'========================================================
' File: SegmentedControl.vb
' - Painter injected with MASVisualPrimitivesPainter + MASShadowPainter
' - No accidental extra shadow instances
'========================================================
Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports SkiaSharp
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Architecture
Namespace Nexamas.UI.Components

    Friend NotInheritable Class SegmentedControl
        Implements IDisposable
        Implements IMASArchitecturalComponent


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

        Friend Delegate Sub SelectedChangedHandler(newIndex As Integer)
        Friend Event SelectedChanged As SelectedChangedHandler

        Private ReadOnly _layout As New SegmentedControlLayout()
        Private ReadOnly _style As New SegmentedControlPainter.Style()
        Private ReadOnly _labels As New List(Of String)()

        Private ReadOnly _primitives As MASVisualPrimitivesPainter
        Private ReadOnly _shadow As MASShadowPainter
        Private ReadOnly _painter As SegmentedControlPainter

        Private _dpi As Single = 1.0F
        Private _hostRect As SKRect = SKRect.Empty
        Private _m As SegmentedControlLayout.Metrics = Nothing

        Private _selectedIndex As Integer = 0
        Private _hoverIndex As Integer = -1
        Private _down As Boolean
        Private _downIndex As Integer = -1
        Private _disposed As Boolean

        '========================
        ' Motion progress (indicator)
        '========================
        Private _animActive As Boolean
        Private _animFromIdx As Integer
        Private _animToIdx As Integer
        Private _animProgress As Single = 1.0F

        ' ✅ keep indicator shape stable while moving to avoid "jump"
        Private _shapeIndex As Integer = 0

        '----------------------------------------------------
        ' ✅ The correct constructor (inject both)
        '----------------------------------------------------
        Friend Sub New(primitives As MASVisualPrimitivesPainter, shadow As MASShadowPainter)
            If primitives Is Nothing Then Throw New ArgumentNullException(NameOf(primitives))
            If shadow Is Nothing Then Throw New ArgumentNullException(NameOf(shadow))

            _primitives = primitives
            _shadow = shadow

            _painter = New SegmentedControlPainter(_primitives, _shadow)

            SetItems("Built-in", "My Library")
        End Sub


        Friend Sub SetItems(ParamArray labels() As String)
            If _disposed Then Return

            Dim oldSelected As Integer = _selectedIndex
            _labels.Clear()

            If labels IsNot Nothing Then
                For Each raw As String In labels
                    Dim label As String = If(raw, String.Empty).Trim()
                    If label.Length > 0 Then _labels.Add(label)
                Next
            End If

            If _labels.Count < 2 Then
                _labels.Clear()
                _labels.Add("Built-in")
                _labels.Add("My Library")
            End If

            _selectedIndex = ClampIndex(_selectedIndex)
            _shapeIndex = _selectedIndex
            _animActive = False
            _animProgress = 1.0F
            Recompute()

            If oldSelected <> _selectedIndex Then
                RaiseEvent SelectedChanged(_selectedIndex)
            End If
        End Sub

        Friend ReadOnly Property Items As IReadOnlyList(Of String)
            Get
                Return _labels
            End Get
        End Property

        Friend Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
            Set(value As Integer)
                If _disposed Then Return
                Dim v As Integer = ClampIndex(value)
                If _selectedIndex = v Then Return

                Dim oldIdx As Integer = _selectedIndex
                _selectedIndex = v

                StartIndicatorAnim(oldIdx, _selectedIndex)

                If Not _animActive Then
                    _shapeIndex = _selectedIndex
                End If

                RaiseEvent SelectedChanged(_selectedIndex)
                Recompute()
            End Set
        End Property

        Private Function ClampIndex(value As Integer) As Integer
            If _labels.Count <= 0 Then Return 0
            If value < 0 Then Return 0
            If value > _labels.Count - 1 Then Return _labels.Count - 1
            Return value
        End Function

        Friend ReadOnly Property Style As SegmentedControlPainter.Style
            Get
                Return _style
            End Get
        End Property

        Friend ReadOnly Property Layout As SegmentedControlLayout
            Get
                Return _layout
            End Get
        End Property

        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
            If Not _hostRect.IsEmpty Then Recompute()
        End Sub

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

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

        Private Sub Recompute()
            If _hostRect.IsEmpty OrElse _labels.Count <= 0 Then
                _m = Nothing
                Return
            End If
            _m = _layout.Compute(_hostRect, _labels.Count, _selectedIndex, _dpi)
        End Sub

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

            If _m Is Nothing Then Recompute()
            If _m Is Nothing Then Return

            Dim indR As SKRect = GetAnimatedIndicatorRect()
            _painter.Draw(
    canvas:=canvas,
    ctx:=ctx,
    contract:=_contract,
    m:=_m,
    indicatorR:=indR,
    labels:=_labels,
    selectedIndex:=_selectedIndex,
    shapeIndex:=_shapeIndex,
    hoverIndex:=_hoverIndex,
    dpi:=_dpi,
    st:=_style)
        End Sub

        Friend Function HitTest(x As Single, y As Single) As Boolean
            If _disposed Then Return False
            If _m Is Nothing Then Recompute()
            If _m Is Nothing OrElse _m.PillR.IsEmpty Then Return False
            Dim rm As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(_dpi)
            Dim pillR As SKRect = MASSurfaceInteractionRing.SnapBaseRect(_m.PillR, rm)
            If pillR.IsEmpty Then Return False
            Return pillR.Contains(x, y)
        End Function

        Friend Function PointerMove(x As Single, y As Single) As Boolean
            If _disposed Then Return False
            If _m Is Nothing Then Recompute()
            Dim oldHover As Integer = _hoverIndex
            _hoverIndex = HitSegmentIndex(x, y)
            Return oldHover <> _hoverIndex
        End Function

        Friend Function PointerLeave() As Boolean
            If _disposed Then Return False
            Dim changed As Boolean = (_hoverIndex <> -1) OrElse _down
            _hoverIndex = -1
            _down = False
            _downIndex = -1
            Return changed
        End Function

        Friend Function PointerDown(x As Single, y As Single) As Boolean
            If _disposed Then Return False
            If _m Is Nothing Then Recompute()
            If _m Is Nothing Then Return False

            Dim hit As Integer = HitSegmentIndex(x, y)
            If hit < 0 Then
                Dim had As Boolean = _down OrElse (_downIndex <> -1)
                _down = False
                _downIndex = -1
                Return had
            End If

            Dim changed As Boolean = (Not _down) OrElse (_downIndex <> hit)
            _down = True
            _downIndex = hit
            Return changed
        End Function

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

            _down = False

            Dim upIdx As Integer = HitSegmentIndex(x, y)
            If _downIndex >= 0 AndAlso upIdx = _downIndex Then
                SelectedIndex = upIdx
            End If

            _downIndex = -1
            Return True
        End Function

        Private Function HitSegmentIndex(x As Single, y As Single) As Integer
            If _m Is Nothing OrElse _m.Segments Is Nothing OrElse _m.Segments.Length = 0 Then Return -1

            Dim rm As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(_dpi)

            Dim pillR As SKRect = MASSurfaceInteractionRing.SnapBaseRect(_m.PillR, rm)
            If pillR.IsEmpty OrElse Not pillR.Contains(x, y) Then Return -1

            For i As Integer = 0 To _m.Segments.Length - 1
                Dim seg As SKRect = MASSurfaceInteractionRing.SnapBaseRect(_m.Segments(i), rm)
                If seg.Contains(x, y) Then Return i
            Next

            Return -1
        End Function

        Friend Function SetAnimationProgress(progress As Single) As Boolean
            If _disposed OrElse Not _animActive Then Return False

            Dim safeProgress As Single = Clamp01(progress)
            If safeProgress >= 0.999F Then
                _animProgress = 1.0F
                _animActive = False
                _shapeIndex = _animToIdx
                Return False
            End If

            _animProgress = safeProgress
            Return True
        End Function

        Friend Sub CompleteAnimation()
            If _disposed Then Return
            If Not _animActive Then Return

            _animProgress = 1.0F
            _animActive = False
            _shapeIndex = _animToIdx
        End Sub

        Friend ReadOnly Property IsAnimating As Boolean
            Get
                Return _animActive
            End Get
        End Property

        Private Shared Function Clamp01(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 Sub StartIndicatorAnim(fromIdx As Integer, toIdx As Integer)
            If _m Is Nothing OrElse _m.Segments Is Nothing Then
                _animActive = False
                _shapeIndex = ClampIndex(toIdx)
                Return
            End If
            If fromIdx < 0 OrElse toIdx < 0 Then
                _animActive = False
                _shapeIndex = ClampIndex(toIdx)
                Return
            End If
            If fromIdx >= _m.Segments.Length OrElse toIdx >= _m.Segments.Length Then
                _animActive = False
                _shapeIndex = ClampIndex(toIdx)
                Return
            End If

            _animFromIdx = fromIdx
            _animToIdx = toIdx

            _shapeIndex = fromIdx
            _animProgress = 0.0F
            _animActive = True
        End Sub

        Private Function GetAnimatedIndicatorRect() As SKRect
            If _m Is Nothing OrElse _m.Segments Is Nothing OrElse _m.Segments.Length < 2 Then Return SKRect.Empty

            If Not _animActive Then Return _m.IndicatorR

            Dim e As Single = Clamp01(_animProgress)

            If e >= 1.0F Then
                _animActive = False
                _shapeIndex = _animToIdx
                Return _m.IndicatorR
            End If

            Dim a As SKRect = _m.Segments(_animFromIdx)
            Dim b As SKRect = _m.Segments(_animToIdx)

            Dim left As Single = a.Left + (b.Left - a.Left) * e
            Dim right As Single = a.Right + (b.Right - a.Right) * e

            Return New SKRect(left, _m.IndicatorR.Top, right, _m.IndicatorR.Bottom)
        End Function


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

            Try
                _painter.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try

            ' مهم:
            ' لا تعمل Dispose لـ _primitives ولا _shadow هنا
            ' لأنهم غالباً "مملوكين" للـowner (MASHeader مثلاً).
        End Sub

    End Class

End Namespace
