Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components.DropDownMenu

    ''' <summary>
    ''' Dedicated popup service for MASMenuBar dropdowns. It reuses the shared menu model and
    ''' FloatRuntime lifetime, but owns a separate float key, owner key, content class and
    ''' renderer so shell navigation styling cannot leak into context menus, split buttons,
    ''' data-grid menus or general menu buttons.
    ''' </summary>
    Friend NotInheritable Class MASMenuBarDropDownPopupService

        Private Const DefaultOwnerKey As String = "MAS.MenuBar.DropDown.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 MASMenuBarDropDownFloatContent
        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, "MASMenuBarDropDownPopupService.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

        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)

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

        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,
                "MASMenuBarDropDownPopupService.ShowModel")

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

            Dim ctx As MASThemeContext = ResolveContextOrNothing()
            If ctx Is Nothing Then Return

            Dim viewportBoundsPx As SKRect = ResolveViewportBoundsOrEmpty()
            If viewportBoundsPx.IsEmpty OrElse viewportBoundsPx.Width <= 1.0F OrElse viewportBoundsPx.Height <= 1.0F Then Return

            If HasActivePopupVisual Then Close()

            Dim content As New MASMenuBarDropDownFloatContent(
                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, CreateMenuBarPresentation(_floatKey)),
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .AnchorRectPx = anchorRectPx,
                .AvailableBoundsPx = viewportBoundsPx,
                .InitialBoundsPx = viewportBoundsPx,
                .OnClosed =
                    Sub(args As MASFloatClosedEventArgs)

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

                        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 masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException2, "MASMenuBarDropDownPopupService.Show.RuntimeShow")
                _content = Nothing
                _handle = Nothing
                _isOpen = False
                Throw
            End Try
        End Sub

        Friend Sub Close()
            Nexamas.UI.Performance.MASPerformanceSystem.RecordConsumerStateRefresh(
                Nexamas.UI.Performance.MASPerformanceConsumerKind.DropDownMenu,
                "MASMenuBarDropDownPopupService.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 masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException3, "MASMenuBarDropDownPopupService.Close.RuntimeClose")
            End Try
        End Sub

        Friend Sub SetViewportBounds()
            Dim viewportBoundsPx As SKRect = ResolveViewportBoundsOrEmpty()

            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 masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException4, "MASMenuBarDropDownPopupService.SetViewportBounds.ApplyRuntimeBounds")
            End Try
        End Sub

        Private Function ResolveContextOrNothing() As MASThemeContext
            Try
                Return _getContext.Invoke()
            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException5, "MASMenuBarDropDownPopupService.ResolveThemeContext")
                Return Nothing
            End Try
        End Function

        Private Function ResolveViewportBoundsOrEmpty() As SKRect
            Try
                Return _getViewportBoundsPx.Invoke()
            Catch masCaughtException6 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException6, "MASMenuBarDropDownPopupService.ResolveViewportBounds")
                Return SKRect.Empty
            End Try
        End Function

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

        Private Shared Function CreateMenuBarPresentation(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
            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.MenuBarDropDownMenu
            Return key
        End Function

    End Class

End Namespace
