Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports Nexamas.UI.Components
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Public standalone segmented switch control. Consumers own the segment model through
    ''' <see cref="MASSegmentEntry"/> while Nexamas UI keeps rendering, animation,
    ''' hit-testing, and material semantics internal.
    ''' </summary>
    Public NotInheritable Class MASSegmentedControl
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

        Private ReadOnly _segments As New List(Of MASSegmentEntry)()
        Private ReadOnly _segmentsView As IReadOnlyList(Of MASSegmentEntry)
        Private ReadOnly _box As SegmentedControlBox

        Private _lastRaisedIndex As Integer = 0
        Private _suppressInnerEvents As Boolean
        Private _disposed As Boolean

        Public Event SelectionChanged As EventHandler(Of MASSegmentedControlSelectionChangedEventArgs)

        Public Sub New()
            Me.Name = "MASSegmentedControl"
            _segmentsView = _segments.AsReadOnly()
            _box = New SegmentedControlBox(AddressOf InvalidateVisual)
            AddHandler _box.SelectedChanged, AddressOf InnerSelectedChanged

            SetSegmentsCore(New MASSegmentEntry() {
                New MASSegmentEntry("built-in", "Built-in"),
                New MASSegmentEntry("library", "My Library")
            }, raiseChanged:=False)
        End Sub

        Public Shared Function Create() As MASSegmentedControl
            Return New MASSegmentedControl()
        End Function

        Public ReadOnly Property Segments As IReadOnlyList(Of MASSegmentEntry)
            Get
                Return _segmentsView
            End Get
        End Property

        Public ReadOnly Property SelectedIndex As Integer
            Get
                Return _box.SelectedIndex
            End Get
        End Property

        Public ReadOnly Property SelectedId As String
            Get
                Dim seg As MASSegmentEntry = SelectedSegment
                If seg Is Nothing Then Return String.Empty
                Return seg.Id
            End Get
        End Property

        Public ReadOnly Property SelectedSegment As MASSegmentEntry
            Get
                Dim idx As Integer = _box.SelectedIndex
                If idx < 0 OrElse idx >= _segments.Count Then Return Nothing
                Return _segments(idx)
            End Get
        End Property

        Friend Function SetBounds(bounds As SKRect) As MASSegmentedControl
            Me.SetLocalLayoutBoundsInternal(bounds)
            Return Me
        End Function

        Friend Function SetBounds(x As Single,
                                  y As Single,
                                  width As Single,
                                  height As Single) As MASSegmentedControl

            Me.SetLocalLayoutBoundsInternal(New SKRect(x, y, x + width, y + height))
            Return Me
        End Function

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

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

            Return SetBounds(x, y, width, height)
        End Function

        Public Function WithSegments(items As IEnumerable(Of MASSegmentEntry)) As MASSegmentedControl
            SetSegmentsCore(items, raiseChanged:=True)
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithSegments(ParamArray labels() As String) As MASSegmentedControl
            Dim list As New List(Of MASSegmentEntry)()
            Dim usedIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

            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 Continue For
                    list.Add(New MASSegmentEntry(MakeUniqueId(NormalizeId(label), usedIds), label))
                Next
            End If

            Return WithSegments(list)
        End Function

        Public Function WithSelectedIndex(index As Integer) As MASSegmentedControl
            If index < 0 OrElse index >= _segments.Count Then
                Throw New ArgumentOutOfRangeException(NameOf(index), "Selected index is outside the segmented-control range.")
            End If

            _box.SelectedIndex = index
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithSelectedId(id As String) As MASSegmentedControl
            Dim normalized As String = If(id, String.Empty).Trim()
            If normalized.Length = 0 Then Throw New ArgumentException("Segment id cannot be empty.", NameOf(id))

            For i As Integer = 0 To _segments.Count - 1
                If String.Equals(_segments(i).Id, normalized, StringComparison.OrdinalIgnoreCase) Then
                    _box.SelectedIndex = i
                    InvalidateVisual()
                    Return Me
                End If
            Next

            Throw New ArgumentException("Segment id was not found.", NameOf(id))
        End Function

        Friend Function OnSelectionChanged(handler As EventHandler(Of MASSegmentedControlSelectionChangedEventArgs)) As MASSegmentedControl
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            AddHandler Me.SelectionChanged, handler
            Return Me
        End Function

        Friend Function OnSelectedIdChanged(handler As Action(Of String)) As MASSegmentedControl
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            AddHandler Me.SelectionChanged,
                Sub(sender As Object, e As MASSegmentedControlSelectionChangedEventArgs)
                    Dim selected As MASSegmentEntry = e.SelectedSegment
                    handler(If(selected Is Nothing, String.Empty, selected.Id))
                End Sub
            Return Me
        End Function

        Public Function Show() As MASSegmentedControl
            Me.Visible = True
            Return Me
        End Function

        Public Function Hide() As MASSegmentedControl
            Me.Visible = False
            Return Me
        End Function

        Public Function Enable() As MASSegmentedControl
            Me.Enabled = True
            _box.Enabled = True
            Return Me
        End Function

        Public Function Disable() As MASSegmentedControl
            Me.Enabled = False
            _box.Enabled = False
            Return Me
        End Function

        Public Function WithVisible(value As Boolean) As MASSegmentedControl
            Me.Visible = value
            Return Me
        End Function

        Public Function WithEnabled(value As Boolean) As MASSegmentedControl
            Me.Enabled = value
            _box.Enabled = value
            Return Me
        End Function

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)

            If _disposed OrElse canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.IsEmpty OrElse pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return

            _box.Enabled = Me.Enabled
            _box.SetDpi(ctx.Dpi)

            Dim hostRect As SKRect = MASLayoutSlotBoundsResolver.ResolvePixel(Me, MASLayoutSlotBoundsRole.Content, ctx.Dpi)
            If hostRect.IsEmpty OrElse hostRect.Width <= 1.0F OrElse hostRect.Height <= 1.0F Then
                hostRect = pixelBounds
            End If

            _box.SetHostRect(hostRect)
            _box.Draw(canvas, ctx)
        End Sub

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            If _disposed OrElse Not Me.Enabled Then Return
            _box.PointerMove(ptPx.X, ptPx.Y)
        End Sub

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext,
                                                 ptPx As SKPoint,
                                                 button As Integer) As Boolean

            If _disposed OrElse Not Me.Enabled OrElse NormalizeButton(button) <> 0 Then Return False
            Return _box.PointerDown(ptPx.X, ptPx.Y)
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean

            If _disposed OrElse Not Me.Enabled OrElse NormalizeButton(button) <> 0 Then Return False
            Return _box.PointerUp(ptPx.X, ptPx.Y)
        End Function

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _disposed Then Return
            _box.PointerLeave()
        End Sub

        Private Sub SetSegmentsCore(items As IEnumerable(Of MASSegmentEntry), raiseChanged As Boolean)
            If items Is Nothing Then Throw New ArgumentNullException(NameOf(items))

            Dim nextItems As New List(Of MASSegmentEntry)()
            For Each item As MASSegmentEntry In items
                If item IsNot Nothing Then nextItems.Add(item)
            Next

            If nextItems.Count < 2 Then
                Throw New ArgumentException("MASSegmentedControl requires at least two segments.", NameOf(items))
            End If

            ValidateUniqueSegmentIds(nextItems)

            Dim oldIndex As Integer = _box.SelectedIndex
            Dim oldSelectedId As String = SelectedId

            _segments.Clear()
            _segments.AddRange(nextItems)

            Dim labels(_segments.Count - 1) As String
            For i As Integer = 0 To _segments.Count - 1
                labels(i) = _segments(i).Label
            Next

            _suppressInnerEvents = True
            Try
                _box.SetItems(labels)
                _box.SelectedIndex = ClampIndex(oldIndex)
            Finally
                _suppressInnerEvents = False
            End Try

            Dim newIndex As Integer = _box.SelectedIndex
            Dim newSelectedId As String = SelectedId
            _lastRaisedIndex = newIndex

            If raiseChanged AndAlso (oldIndex <> newIndex OrElse Not String.Equals(oldSelectedId, newSelectedId, StringComparison.OrdinalIgnoreCase)) Then
                RaiseSelectionChanged(oldIndex, newIndex)
            End If
        End Sub

        Private Sub InnerSelectedChanged(newIndex As Integer)
            If _disposed OrElse _suppressInnerEvents Then Return
            Dim oldIndex As Integer = _lastRaisedIndex
            _lastRaisedIndex = ClampIndex(newIndex)
            RaiseSelectionChanged(oldIndex, _lastRaisedIndex)
            InvalidateVisual()
        End Sub

        Private Sub RaiseSelectionChanged(oldIndex As Integer, newIndex As Integer)
            Dim selected As MASSegmentEntry = Nothing
            If newIndex >= 0 AndAlso newIndex < _segments.Count Then selected = _segments(newIndex)
            RaiseEvent SelectionChanged(Me, New MASSegmentedControlSelectionChangedEventArgs(oldIndex, newIndex, selected))
        End Sub

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


#Region "Size/Layout 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 profileNeutralHeight As Single = MASIntrinsicControlSizeMetrics.ResolveSegmentedControlProfileNeutralHeight(safeContext.SizeProfile)
            Dim desired As SKSize = ResolveIntrinsicDesiredSize(safeContext, profileNeutralHeight)
            Dim minimum As SKSize = MASIntrinsicControlSizeMetrics.ResolveSegmentedControlMinimumSize(desired)
            Dim contentInset As MASLayoutInset = MASIntrinsicControlSizeMetrics.CreateSegmentedControlContentInset()

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=contentInset,
                visualOverflowInset:=MASIntrinsicControlSizeMetrics.DefaultVisualOverflowInset,
                hitOverflowInset:=MASIntrinsicControlSizeMetrics.CreateVerticalTouchHitOverflowInset(desired, safeContext.Density),
                isFallback:=False)
        End Function

        Private Function ResolveIntrinsicDesiredSize(context As MASSizeContext, profileNeutralHeight As Single) As SKSize
            Dim totalLabelWidth As Single = 0.0F
            Dim maxLabelHeight As Single = 0.0F

            For Each segment As MASSegmentEntry In _segments
                If segment Is Nothing Then Continue For
                Dim measured As MASTextMeasureResult = MASLayoutTextMeasureGateway.Measure(
                    context.IntegrationContext,
                    segment.Label,
                    MASTypography.MASTextStyle.Button,
                    Single.PositiveInfinity,
                    allowWrap:=False)
                totalLabelWidth += Math.Max(0.0F, measured.Size.Width)
                maxLabelHeight = Math.Max(maxLabelHeight, Math.Max(0.0F, measured.Size.Height))
            Next

            Return MASIntrinsicControlSizeMetrics.ResolveSegmentedControlDesiredSize(
                totalLabelWidth:=totalLabelWidth,
                maxLabelHeight:=maxLabelHeight,
                segmentCount:=_segments.Count,
                profileNeutralHeight:=profileNeutralHeight)
        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

        Private Shared Function NormalizeId(label As String) As String
            Dim s As String = If(label, String.Empty).Trim().ToLowerInvariant()
            If s.Length = 0 Then Return "segment"

            Dim chars As Char() = s.ToCharArray()
            For i As Integer = 0 To chars.Length - 1
                Dim ch As Char = chars(i)
                If Char.IsLetterOrDigit(ch) Then
                    Continue For
                End If
                chars(i) = "-"c
            Next

            Dim normalized As String = New String(chars).Trim("-"c)
            If normalized.Length = 0 Then Return "segment"
            Return normalized
        End Function

        Private Shared Function MakeUniqueId(baseId As String, usedIds As HashSet(Of String)) As String
            If usedIds Is Nothing Then Throw New ArgumentNullException(NameOf(usedIds))

            Dim safeBase As String = If(baseId, String.Empty).Trim()
            If safeBase.Length = 0 Then safeBase = "segment"

            Dim candidate As String = safeBase
            Dim suffix As Integer = 2

            While usedIds.Contains(candidate)
                candidate = safeBase & "-" & suffix.ToString(System.Globalization.CultureInfo.InvariantCulture)
                suffix += 1
            End While

            usedIds.Add(candidate)
            Return candidate
        End Function

        Private Shared Sub ValidateUniqueSegmentIds(items As IEnumerable(Of MASSegmentEntry))
            Dim ids As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

            For Each item As MASSegmentEntry In items
                If item Is Nothing Then Continue For
                Dim safeId As String = If(item.Id, String.Empty).Trim()
                If safeId.Length = 0 Then Throw New ArgumentException("Segment id cannot be empty.", NameOf(items))
                If Not ids.Add(safeId) Then
                    Throw New ArgumentException("Duplicate segment id: " & safeId, NameOf(items))
                End If
            Next
        End Sub

        Private Shared Function NormalizeButton(button As Integer) As Integer
            Dim b As Integer = button
            If b < 0 Then Return -1
            If b = 0 OrElse b = 1 Then Return 0
            If b = CInt(MouseButtons.Left) Then Return 0
            If (b And CInt(MouseButtons.Right)) <> 0 Then Return -1
            If (b And CInt(MouseButtons.Left)) <> 0 Then Return 0
            Return -1
        End Function

        Protected Overrides Sub Dispose(disposing As Boolean)
            If Not _disposed Then
                _disposed = True

                If disposing Then
                    Try
                        RemoveHandler _box.SelectedChanged, AddressOf InnerSelectedChanged
                    Catch masCaughtException1 As Exception
                        Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
                    End Try

                    Try
                        _box.Dispose()
                    Catch masCaughtException2 As Exception
                        Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
                    End Try
                End If
            End If

            MyBase.Dispose(disposing)
        End Sub


#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 MASSegmentedControl
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function






#End Region
    End Class

End Namespace
