Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.Components.DropDownMenu

    Friend NotInheritable Class DropDownMenuPopupService

        Private Const DefaultOwnerKey As String = "MAS.DropDownMenu.PopupService"

        Private ReadOnly _runtime As MASFloatRuntime
        Private ReadOnly _getContext As Func(Of MASThemeContext)
        Private ReadOnly _getViewportBoundsPx As Func(Of SKRect)
        Private ReadOnly _ownerKey As String
        Private ReadOnly _floatKey As String

        Private _handle As MASFloatHandle
        Private _content As DropDownMenuFloatContent
        Private _isOpen As Boolean

        Friend Sub New(runtime As MASFloatRuntime,
                       getContext As Func(Of MASThemeContext),
                       getViewportBoundsPx As Func(Of SKRect),
                       Optional ownerKey As String = Nothing,
                       Optional floatKey As String = Nothing)

            If runtime Is Nothing Then Throw New ArgumentNullException(NameOf(runtime))
            If getContext Is Nothing Then Throw New ArgumentNullException(NameOf(getContext))
            If getViewportBoundsPx Is Nothing Then Throw New ArgumentNullException(NameOf(getViewportBoundsPx))

            _runtime = runtime
            _getContext = getContext
            _getViewportBoundsPx = getViewportBoundsPx

            _ownerKey = NormalizeOwnerKey(ownerKey)
            _floatKey = NormalizeFloatKey(floatKey)
        End Sub

        Friend Event Closed As EventHandler

        Friend ReadOnly Property IsOpen As Boolean
            Get
                If Not _isOpen Then Return False
                If _handle Is Nothing Then Return False

                Try
                    Return _runtime.IsOpen(_handle)
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException1, "DropDownMenuPopupService.IsOpen.RuntimeState")
                    Return False
                End Try
            End Get
        End Property

        Friend ReadOnly Property HasActivePopupVisual As Boolean
            Get
                Return _isOpen AndAlso _handle IsNot Nothing
            End Get
        End Property

        ' CONTRACT:
        ' anchorRectPx must be in physical pixel coordinates relative to the SK surface.
        ' Callers must convert logical/control bounds to pixel bounds before calling Show.
        ' Do not pass BoundsLogical directly.
        Friend Sub Show(anchorRectPx As SKRect,
                        configure As Action(Of MASDropDownMenuBuilder),
                        Optional fallbackCommand As Action(Of String) = Nothing,
                        Optional presentation As MASFloatPresentation = Nothing)

            If configure Is Nothing Then Throw New ArgumentNullException(NameOf(configure))

            Dim builder As New MASDropDownMenuBuilder()
            configure.Invoke(builder)

            Dim model As MASDropDownMenuModel = builder.Build()

            Show(
                anchorRectPx:=anchorRectPx,
                model:=model,
                executeCommand:=builder.CreateCommandExecutor(fallbackCommand),
                presentation:=presentation
            )
        End Sub

        ' CONTRACT:
        ' anchorRectPx must be in physical pixel coordinates relative to the SK surface.
        ' Callers must convert logical/control bounds to pixel bounds before calling Show.
        ' Do not pass BoundsLogical directly.
        Friend Sub Show(anchorRectPx As SKRect,
                        model As MASDropDownMenuModel,
                        executeCommand As Action(Of String),
                        Optional presentation As MASFloatPresentation = Nothing)

            If model Is Nothing Then Throw New ArgumentNullException(NameOf(model))
            If executeCommand Is Nothing Then Throw New ArgumentNullException(NameOf(executeCommand))
            If model.IsEmpty Then Return

            Nexamas.UI.Performance.MASPerformanceSystem.RecordConsumerStateRefresh(
                Nexamas.UI.Performance.MASPerformanceConsumerKind.DropDownMenu,
                "DropDownMenuPopupService.ShowModel")
            If anchorRectPx.IsEmpty OrElse anchorRectPx.Width <= 1.0F OrElse anchorRectPx.Height <= 1.0F Then Return

            Dim ctx As MASThemeContext = Nothing

            Try
                ctx = _getContext.Invoke()
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException2, "DropDownMenuPopupService.Show.ResolveThemeContext")
                Return
            End Try

            If ctx Is Nothing Then Return

            Dim viewportBoundsPx As SKRect

            Try
                viewportBoundsPx = _getViewportBoundsPx.Invoke()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException3, "DropDownMenuPopupService.Show.ResolveViewportBounds")
                Return
            End Try

            If viewportBoundsPx.IsEmpty OrElse viewportBoundsPx.Width <= 1.0F OrElse viewportBoundsPx.Height <= 1.0F Then Return

            If HasActivePopupVisual Then
                Close()
            End If

            Dim content As New DropDownMenuFloatContent(
                ctx:=ctx,
                model:=model,
                anchorRectPx:=anchorRectPx,
                executeCommand:=executeCommand,
                contextProvider:=_getContext
            )

            Dim shownHandle As MASFloatHandle = Nothing

            Dim request As New MASFloatRequest() With {
                .FloatKey = _floatKey,
                .OwnerKey = _ownerKey,
                .Content = content,
                .RequestedSlot = MASFloatSlot.Popup,
                .RequestedScope = MASFloatScope.Owner,
                .RequestedLayer = MASFloatLayerLevel.Interactive,
                .RequestedPresentation = If(presentation, CreateMenuPresentation(_floatKey)),
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .AnchorRectPx = anchorRectPx,
                .AvailableBoundsPx = viewportBoundsPx,
                .InitialBoundsPx = viewportBoundsPx,
                .OnClosed =
                    Sub(args As MASFloatClosedEventArgs)

                        If Object.ReferenceEquals(_content, content) Then
                            _content = Nothing
                        End If

                        Dim ownsClosingHandle As Boolean = False

                        If args IsNot Nothing Then
                            ownsClosingHandle = IsSameHandle(args.Handle, shownHandle)
                        Else
                            ownsClosingHandle = (_handle Is Nothing OrElse IsSameHandle(_handle, shownHandle))
                        End If

                        If ownsClosingHandle Then
                            If _handle Is Nothing OrElse IsSameHandle(_handle, shownHandle) Then
                                _handle = Nothing
                                _isOpen = False
                            End If

                            RaiseClosedSafe()
                        End If

                    End Sub
            }

            Try
                _runtime.SetAvailableBoundsPx(viewportBoundsPx)

                _content = content
                _isOpen = True
                shownHandle = _runtime.Show(request)
                _handle = shownHandle

                If _handle Is Nothing Then
                    _content = Nothing
                    _isOpen = False
                End If

            Catch masCaughtException8 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException8, "DropDownMenuPopupService.Show.RuntimeShow")
                _content = Nothing
                _handle = Nothing
                _isOpen = False
                Throw
            End Try
        End Sub

        Private Sub RaiseClosedSafe()
            Try
                RaiseEvent Closed(Me, EventArgs.Empty)
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                    masCaughtException4,
                    "DropDownMenuPopupService.Closed")
            End Try
        End Sub

        Friend Sub Close()
            Nexamas.UI.Performance.MASPerformanceSystem.RecordConsumerStateRefresh(
                Nexamas.UI.Performance.MASPerformanceConsumerKind.DropDownMenu,
                "DropDownMenuPopupService.Close")

            Dim oldHandle As MASFloatHandle = _handle

            _content = Nothing
            _handle = Nothing
            _isOpen = False

            If oldHandle Is Nothing Then Return

            Try
                _runtime.Close(oldHandle, MASFloatCloseReason.Programmatic)
            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException5, "DropDownMenuPopupService.Close.RuntimeClose")
            End Try
        End Sub

        Friend Sub SetViewportBounds()
            Dim viewportBoundsPx As SKRect

            Try
                viewportBoundsPx = _getViewportBoundsPx.Invoke()
            Catch masCaughtException6 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException6, "DropDownMenuPopupService.SetViewportBounds.ResolveViewport")

                If HasActivePopupVisual Then Close()
                Return
            End Try

            If viewportBoundsPx.IsEmpty OrElse viewportBoundsPx.Width <= 1.0F OrElse viewportBoundsPx.Height <= 1.0F Then
                If HasActivePopupVisual Then Close()
                Return
            End If

            Try
                _runtime.SetAvailableBoundsPx(viewportBoundsPx)
            Catch masCaughtException7 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException7, "DropDownMenuPopupService.SetViewportBounds.ApplyRuntimeBounds")
            End Try
        End Sub

        Private Shared Function CreateMenuPresentation(floatKey As String) As MASFloatPresentation
            Return MASFloatPresentationProfiles.ForKey(floatKey)
        End Function

        Private Shared Function IsSameHandle(left As MASFloatHandle,
                                             right As MASFloatHandle) As Boolean

            If left Is Nothing OrElse right Is Nothing Then Return False
            Return left.Id = right.Id AndAlso left.Generation = right.Generation
        End Function

        Private Shared Function NormalizeOwnerKey(ownerKey As String) As String
            Dim key As String = If(ownerKey, String.Empty).Trim()

            If key.Length = 0 Then
                Return DefaultOwnerKey
            End If

            Return key
        End Function

        Private Shared Function NormalizeFloatKey(floatKey As String) As String
            Dim key As String = If(floatKey, String.Empty).Trim()

            If key.Length = 0 Then
                Return MASFloatKeys.DropDownMenu
            End If

            Return key
        End Function

    End Class

End Namespace