Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.ListPopup
Imports Nexamas.UI.Components.ListRendering
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASComboBox

#Region "Dropdown Core"

        Private Sub ToggleDropdown(ctx As MASThemeContext)
            Nexamas.UI.Performance.MASPerformanceSystem.RecordConsumerStateRefresh(
                Nexamas.UI.Performance.MASPerformanceConsumerKind.ComboBox,
                "MASComboBox.ToggleDropdown")
            If ctx Is Nothing Then Return
            If ResolveFloatRuntime() Is Nothing Then Return
            If Not Me.Enabled OrElse Not Me.Visible Then Return

            If _items.Count <= 0 Then
                CloseDropdownSafe()
                Return
            End If

            Dim preAnchorBoundsPx As SKRect = ResolveDropdownAnchorBoundsPx(_lastPixelBounds)
            If preAnchorBoundsPx.Width <= 1.0F OrElse preAnchorBoundsPx.Height <= 1.0F Then
                Return
            End If

            If _isDropdownOpen Then
                CloseDropdownSafe()
                InvalidateVisual()
                Return
            End If

            _ddService.CloseAny()

            _isDropdownOpen = True
            InvalidateVisual()

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim layoutPlan As MASComboBoxLayoutPlan = MASComboBoxLayoutPlan.Create(ctx, preAnchorBoundsPx, SizeIntent)
            If layoutPlan IsNot Nothing AndAlso layoutPlan.CollapsePopup Then
                _isDropdownOpen = False
                ResetInputAll()
                InvalidateVisual()
                Return
            End If

            Dim content As IMASFloatContent =
                New MASDropDownListFloatContent(
                    _items,
                    _selectedIndex,
                    Sub(i As Integer, t As String)
                        Me.SelectedIndex = i
                        CloseDropdownSafe()
                    End Sub,
                    Sub()
                        _hover = True
                        InvalidateVisual()
                    End Sub,
                    Sub()
                        _isDropdownOpen = False
                        _pressed = False
                        _hover = False

                        StopPressPulse(clearPressed:=False)

                        InvalidateVisual()
                    End Sub,
                    Function(contract As MASResolvedComponentContract,
                             w As Integer,
                             h As Integer,
                             d As Single,
                             total As Integer) As ListPopupLayout.Metrics

                        Dim contentDpi As Single = d
                        If layoutPlan IsNot Nothing Then contentDpi = PixelSnap.SafeDpi(d * layoutPlan.MetricScale)

                        Return ListPopupLayout.Build(
                            contract:=contract,
                            surfaceW:=w,
                            surfaceH:=h,
                            dpi:=contentDpi,
                            totalItems:=total)
                    End Function,
                    ctx,
                    contextProvider:=
                        Function()
                            If Me.Host Is Nothing Then Return Nothing
                            Return Me.Host.TryGetContext()
                        End Function)

            Dim anchorBoundsPx As SKRect = preAnchorBoundsPx
            Dim dropdownSizePx As SKSize = CalculateDropdownSizePx(ctx, dpi, anchorBoundsPx)

            Dim boundsPx As SKRect =
                DropDownPositionPolicy.Calculate(
                    anchorBoundsPx,
                    dropdownSizePx,
                    dpi,
                    If(layoutPlan Is Nothing, ComboBoxTokens.DropGapDip * dpi, layoutPlan.DropGapPx),
                    ResolveSurfaceSizeProvider())

            Try
                _ddService.ShowOwned(Me, content, boundsPx)

                If Not IsCurrentDropdownHandleOpen() Then
                    _ddService.CloseAny()
                    _isDropdownOpen = False
                    ResetInputAll()
                    InvalidateVisual()
                End If
            Catch masComboShowException As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masComboShowException, "MASComboBox.ToggleDropdown.ShowOwned")
                _isDropdownOpen = False
                ResetInputAll()
                InvalidateVisual()
            End Try
        End Sub

        Private Sub CloseDropdownSafe()
            Nexamas.UI.Performance.MASPerformanceSystem.RecordConsumerStateRefresh(
                Nexamas.UI.Performance.MASPerformanceConsumerKind.ComboBox,
                "MASComboBox.CloseDropdownSafe")

            Dim wasOpen As Boolean = _isDropdownOpen
            Dim hadOwnedState As Boolean =
                (_ddService IsNot Nothing AndAlso
                 (_ddService.CurrentHandle IsNot Nothing OrElse _ddService.CurrentContent IsNot Nothing))

            _isDropdownOpen = False
            ResetInputAll()

            If wasOpen OrElse hadOwnedState Then
                _ddService.CloseIfOwnedBy(Me)
            End If

            InvalidateVisual()
        End Sub

        Private Function ShowDropDownFloat(ownerKey As String,
                                           content As IMASFloatContent,
                                           boundsPx As SKRect) As MASFloatHandle

            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()

            If runtime Is Nothing Then Return Nothing
            If content Is Nothing Then Return Nothing

            boundsPx = NormalizeDropdownFloatBoundsPx(boundsPx)
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return Nothing

            If String.IsNullOrWhiteSpace(ownerKey) Then
                ownerKey = "MAS.ComboBox.DropDownList"
            End If

            Dim request As New MASFloatRequest() With {
                .FloatKey = MASFloatKeys.DropDownList,
                .OwnerKey = ownerKey,
                .Content = content,
                .RequestedSlot = MASFloatSlot.Popup,
                .RequestedScope = MASFloatScope.Owner,
                .RequestedLayer = MASFloatLayerLevel.Interactive,
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .AnchorRectPx = ResolveDropdownAnchorBoundsPx(boundsPx),
                .InitialBoundsPx = boundsPx,
                .AvailableBoundsPx = ResolveAvailableBoundsPx(boundsPx),
                .RequestedPresentation = MASFloatPresentationProfiles.DropDownList()
            }

            Return runtime.Show(request)
        End Function


        Private Function ResolveDropdownAnchorBoundsPx(fallbackBoundsPx As SKRect) As SKRect
            Dim dpi As Single = 1.0F
            If Host IsNot Nothing Then
                Try
                    dpi = PixelSnap.SafeDpi(Host.Dpi)
                Catch masCaughtExceptionAnchorDpi As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtExceptionAnchorDpi, "MASComboBox.ResolveDropdownAnchorDpi")
                    dpi = 1.0F
                End Try
            End If

            Dim slotAnchorPx As SKRect = GetLayoutFloatAnchorBoundsPxInternal(dpi)
            If slotAnchorPx.Width > 1.0F AndAlso slotAnchorPx.Height > 1.0F Then
                Return slotAnchorPx
            End If

            If _lastPixelBounds.Width > 1.0F AndAlso _lastPixelBounds.Height > 1.0F Then
                Return _lastPixelBounds
            End If

            Return fallbackBoundsPx
        End Function

        ''' <summary>
        ''' Performs the final internal geometry guard before the ComboBox dropdown enters
        ''' FloatRuntime. The popup must always stay a compact explicit-bounds float and
        ''' must never fall back to the full host surface because of invalid or inverted
        ''' layout data.
        ''' </summary>
        Private Function NormalizeDropdownFloatBoundsPx(boundsPx As SKRect) As SKRect
            If boundsPx.IsEmpty Then
                Return SKRect.Empty
            End If

            Dim left As Single = Math.Min(boundsPx.Left, boundsPx.Right)
            Dim top As Single = Math.Min(boundsPx.Top, boundsPx.Bottom)
            Dim right As Single = Math.Max(boundsPx.Left, boundsPx.Right)
            Dim bottom As Single = Math.Max(boundsPx.Top, boundsPx.Bottom)

            If Single.IsNaN(left) OrElse Single.IsInfinity(left) Then
                Return SKRect.Empty
            End If

            If Single.IsNaN(top) OrElse Single.IsInfinity(top) Then
                Return SKRect.Empty
            End If

            If Single.IsNaN(right) OrElse Single.IsInfinity(right) Then
                Return SKRect.Empty
            End If

            If Single.IsNaN(bottom) OrElse Single.IsInfinity(bottom) Then
                Return SKRect.Empty
            End If

            Dim normalized As New SKRect(left, top, right, bottom)
            If normalized.Width <= 1.0F OrElse normalized.Height <= 1.0F Then
                Return SKRect.Empty
            End If

            Dim available As SKRect = ResolveAvailableBoundsPx(normalized)
            If available.Width <= 1.0F OrElse available.Height <= 1.0F Then
                Return PixelSnap.SnapRectHalf(normalized)
            End If

            Dim width As Single = Math.Min(normalized.Width, available.Width)
            Dim height As Single = Math.Min(normalized.Height, available.Height)
            Dim x As Single = normalized.Left
            Dim y As Single = normalized.Top

            If x < available.Left Then
                x = available.Left
            End If

            If y < available.Top Then
                y = available.Top
            End If

            If x + width > available.Right Then
                x = available.Right - width
            End If

            If y + height > available.Bottom Then
                y = available.Bottom - height
            End If

            Return PixelSnap.SnapRectHalf(New SKRect(x, y, x + width, y + height))
        End Function

        Private Function ResolveAvailableBoundsPx(fallbackBoundsPx As SKRect) As SKRect
            Dim s As SKSizeI = ResolveSurfaceSizePx()

            If s.Width > 1 AndAlso s.Height > 1 Then
                Return New SKRect(
                    0.0F,
                    0.0F,
                    CSng(s.Width),
                    CSng(s.Height))
            End If

            Return New SKRect(
                0.0F,
                0.0F,
                Math.Max(fallbackBoundsPx.Right + 4.0F, fallbackBoundsPx.Width),
                Math.Max(fallbackBoundsPx.Bottom + 4.0F, fallbackBoundsPx.Height))
        End Function

#End Region

    End Class

End Namespace
