Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Components
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.WinForms
Imports SkiaSharp

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' Shell facade for one MASApplicationWindow.
    ''' It delegates technical application-chrome binding to MASSkiaRootHost while
    ''' keeping TopBar/MainMenuBar placement owned by MASApplication layout policy.
    ''' </summary>
    Public NotInheritable Class MASApplicationWindowShell

        Private ReadOnly _rootHost As MASSkiaRootHost

        Private ReadOnly Property RootHost As MASSkiaRootHost
            Get
                _rootHost.ThrowIfDisposedCore()
                Return _rootHost
            End Get
        End Property
        Private _standardMainMenuBar As MASMenuBar

        Friend Sub New(rootHost As MASSkiaRootHost)
            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            _rootHost = rootHost
        End Sub

        Public ReadOnly Property TopBar As MASTopBarComponent
            Get
                Return RootHost.AttachedTopBar
            End Get
        End Property

        ''' <summary>
        ''' Returns the standard application MainMenuBar attached through this shell,
        ''' or Nothing when the window uses only a TopBar/custom chrome.
        ''' </summary>
        Public ReadOnly Property MainMenuBar As MASMenuBar
            Get
                Return RootHost.AttachedMainMenuBar
            End Get
        End Property

        Public ReadOnly Property HasStandardMenuBar As Boolean
            Get
                Return _standardMainMenuBar IsNot Nothing AndAlso
                       Object.ReferenceEquals(RootHost.AttachedMainMenuBar, _standardMainMenuBar)
            End Get
        End Property

        Public Sub AttachTopBar(topBar As MASTopBarComponent,
                                getTopBarLogicalRect As Func(Of SKRect),
                                Optional attachWindow As Boolean = True)

            RootHost.AttachTopBar(topBar, getTopBarLogicalRect, attachWindow)
        End Sub

        Public Sub DetachTopBar()
            RootHost.DetachTopBar()
        End Sub

        ''' <summary>
        ''' Returns the official MAS application TopBar height for the active size profile.
        ''' Application code should use this value when reserving content below a standard TopBar
        ''' instead of carrying a local title-bar height.
        ''' </summary>
        Public ReadOnly Property StandardTopBarHeightLogical As Single
            Get
                Return ResolveStandardTopBarHeightLogical()
            End Get
        End Property

        ''' <summary>
        ''' Returns the official MAS application MainMenuBar height for the active size profile.
        ''' The height belongs to MASSize/MASApplication chrome policy, not to page-local constants.
        ''' </summary>
        Public ReadOnly Property StandardMenuBarHeightLogical As Single
            Get
                Return ResolveStandardMenuBarHeightLogical()
            End Get
        End Property

        ''' <summary>
        ''' Returns the content start for windows that only reserve the standard MAS TopBar.
        ''' Use StandardContentTopLogical for normal application pages because it also accounts
        ''' for an attached standard MainMenuBar.
        ''' </summary>
        Public ReadOnly Property StandardTopBarContentTopLogical As Single
            Get
                Return MASApplicationLayoutPolicy.ResolveStandardTopBarContentTop(Me)
            End Get
        End Property

        ''' <summary>
        ''' Returns the official content start below the standard MAS application chrome.
        ''' It includes TopBar, an attached standard MainMenuBar when present, and the
        ''' MASApplication-owned breathing gap below application chrome.
        ''' </summary>
        Public ReadOnly Property StandardContentTopLogical As Single
            Get
                Return MASApplicationLayoutPolicy.ResolveStandardApplicationChromeContentTop(Me)
            End Get
        End Property

        ''' <summary>
        ''' Returns the standard TopBar rectangle for the attached root host and active size
        ''' profile. Consumers that draw their own top-bar pass should use this instead of
        ''' constructing a rectangle from local height constants.
        ''' </summary>
        Public Function CreateStandardTopBarLogicalRect() As SKRect
            Return ResolveStandardTopBarLogicalRect()
        End Function

        ''' <summary>
        ''' Returns the standard MainMenuBar rectangle directly below the standard TopBar.
        ''' This is the host-owned full-width application chrome slot, not a page-content row.
        ''' </summary>
        Public Function CreateStandardMenuBarLogicalRect() As SKRect
            Return ResolveStandardMenuBarLogicalRect()
        End Function

        ''' <summary>
        ''' Creates and attaches a standard MAS TopBar using the canonical MASSize/MASLayout
        ''' application-chrome height. This is the preferred SDK gateway for normal windows;
        ''' advanced callers may still use AttachTopBar when they own a custom shell rectangle.
        ''' </summary>
        Public Function AttachStandardTopBar(Optional title As String = Nothing,
                                             Optional configureMenu As Action(Of MASDropDownMenuBuilder) = Nothing,
                                             Optional commandHandler As Action(Of String) = Nothing,
                                             Optional showLogo As Boolean = False,
                                             Optional attachWindow As Boolean = True) As MASTopBarComponent

            Dim resolvedTitle As String = If(title, String.Empty)
            If resolvedTitle.Length = 0 AndAlso RootHost.OwnerForm IsNot Nothing Then
                resolvedTitle = If(RootHost.OwnerForm.Text, String.Empty)
            End If

            Dim topBar As MASTopBarComponent = MASTopBarComponent.Default().WithTitle(resolvedTitle)
            If showLogo Then
                topBar.WithLogo()
            Else
                topBar.WithoutLogo()
            End If

            If configureMenu IsNot Nothing Then topBar.WithMenu(configureMenu)
            If commandHandler IsNot Nothing Then topBar.OnMenuCommandInvoked(Sub(sender As MASTopBarComponent, commandId As String) commandHandler.Invoke(commandId))

            AttachTopBar(topBar, AddressOf ResolveStandardTopBarLogicalRect, attachWindow)
            Return topBar
        End Function

        ''' <summary>
        ''' Internal implementation for the public Controls.AddMenuBar gateway. The shell
        ''' places the menu directly below the standard TopBar and updates page/application-surface
        ''' content reservations through MASApplicationLayoutPolicy.
        ''' </summary>
        Friend Function AttachStandardMenuBar(Optional configureMenuBar As Action(Of MASMenuBar) = Nothing) As MASMenuBar
            Dim menuBar As MASMenuBar = MASMenuBar.Create().WithSize(MASSize.FillWidth)
            BindStandardMenuBar(menuBar)

            If configureMenuBar IsNot Nothing Then
                configureMenuBar.Invoke(menuBar)
            End If

            _standardMainMenuBar = menuBar
            RootHost.AttachMainMenuBar(_standardMainMenuBar, AddressOf ResolveStandardMenuBarLogicalRect)
            Return _standardMainMenuBar
        End Function

        Friend Sub DetachStandardMenuBar()
            RootHost.DetachMainMenuBar()
            _standardMainMenuBar = Nothing
        End Sub

        Private Function BindStandardMenuBar(menuBar As MASMenuBar) As MASMenuBar
            If menuBar Is Nothing Then Throw New ArgumentNullException(NameOf(menuBar))

            menuBar.BindDropDownPresenterInternal(
                Sub(anchorRectPx As SKRect,
                    buildMenu As Action(Of MASDropDownMenuBuilder),
                    commandHandler As Action(Of String),
                    closedHandler As Action)
                    ShowMenuBarDropDownMenu(anchorRectPx, buildMenu, commandHandler, onClosed:=closedHandler)
                End Sub)

            Return menuBar
        End Function

        Private Function ResolveStandardTopBarLogicalRect() As SKRect
            Return New SKRect(
                0.0F,
                0.0F,
                ResolveHostSurfaceWidthLogical(),
                ResolveStandardTopBarHeightLogical())
        End Function

        Private Function ResolveStandardMenuBarLogicalRect() As SKRect
            Dim top As Single = ResolveStandardTopBarHeightLogical()
            Return New SKRect(
                0.0F,
                top,
                ResolveHostSurfaceWidthLogical(),
                top + ResolveStandardMenuBarHeightLogical())
        End Function

        Private Function ResolveHostSurfaceWidthLogical() As Single
            Dim dpi As Single = RootHost.GetDpi()
            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then dpi = 1.0F

            Dim widthDip As Single = 0.0F
            If RootHost.SurfaceSizePx.Width > 0 Then widthDip = CSng(RootHost.SurfaceSizePx.Width) / dpi
            If widthDip <= 0.0F AndAlso RootHost.OwnerForm IsNot Nothing Then widthDip = CSng(Math.Max(1, RootHost.OwnerForm.ClientSize.Width))

            Return Math.Max(1.0F, widthDip)
        End Function

        Private Shared Function ResolveStandardTopBarHeightLogical() As Single
            Return MASComponentSizeContractTokens.ResolveApplicationTopBarHeight(MASRuntimeSizeProfileService.Current)
        End Function

        Private Shared Function ResolveStandardMenuBarHeightLogical() As Single
            Return MASComponentSizeContractTokens.ResolveApplicationMenuBarHeight(MASRuntimeSizeProfileService.Current)
        End Function

        ''' <summary>
        ''' Creates a WinForms MAS menu button and binds it to this application window shell.
        ''' This is the beginner-facing menu trigger entry point; callers should not pass RootHost manually.
        ''' </summary>
        Public Function CreateMenuButton(Optional text As String = "Menu",
                                         Optional buildMenu As Action(Of MASDropDownMenuBuilder) = Nothing,
                                         Optional commandHandler As Action(Of String) = Nothing) As MASMenuButton

            Dim button As MASMenuButton = MASMenuButton.Create().WithText(text)
            BindMenuButton(button)

            If buildMenu IsNot Nothing Then
                button.WithMenu(buildMenu)
            End If

            If commandHandler IsNot Nothing Then
                button.OnCommand(commandHandler)
            End If

            Return button
        End Function

        ''' <summary>
        ''' Binds an existing MASMenuButton to this application window shell.
        ''' This keeps menu buttons under the Single Entry Law: window.Shell owns RootHost wiring.
        ''' </summary>
        Public Function BindMenuButton(button As MASMenuButton) As MASMenuButton
            If button Is Nothing Then Throw New ArgumentNullException(NameOf(button))
            button.BindToShellRootHost(_rootHost)
            Return button
        End Function

        ''' <summary>
        ''' Shows a dropdown menu through the application shell without exposing DropDownMenuPopupService.
        ''' The anchor rectangle must be in MAS surface physical pixels.
        ''' Internal pixel-space menu hook. Public consumers use ShowDropDownMenuForControl or CreateMenuButton/BindMenuButton instead.
        ''' </summary>
        Friend Sub ShowDropDownMenu(anchorRectPx As SKRect,
                                    configure As Action(Of MASDropDownMenuBuilder),
                                    Optional commandHandler As Action(Of String) = Nothing,
                                    Optional viewportProvider As Func(Of SKRect) = Nothing,
                                    Optional onClosed As Action = Nothing)

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

            Dim popup As DropDownMenuPopupService = RootHost.CreateDropDownMenuPopupServiceCore(viewportProvider)
            Dim closeNotificationSent As Boolean = False
            Dim notifyClosed As Action =
                Sub()
                    If onClosed Is Nothing Then Return
                    If closeNotificationSent Then Return

                    closeNotificationSent = True

                    Try
                        onClosed.Invoke()
                    Catch masCaughtExceptionClosed As Exception
                        Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtExceptionClosed, "MASApplicationWindowShell.DropDownClosed")
                    End Try
                End Sub

            If onClosed IsNot Nothing Then
                AddHandler popup.Closed,
                    Sub(sender As Object, e As EventArgs)
                        notifyClosed.Invoke()
                    End Sub
            End If

            popup.Show(
                anchorRectPx:=anchorRectPx,
                configure:=configure,
                fallbackCommand:=commandHandler
            )

            ' A newly shown menu-bar dropdown can still be in the FloatRuntime Opening
            ' lifecycle state while its visual handle already exists. Using IsOpen here
            ' treats Opening as closed, immediately disarms MASMenuBar hover-switch sessions,
            ' and leaves the first popup visible but no longer owned by the menu bar gesture.
            ' HasActivePopupVisual is the correct post-show failure check: it is False only
            ' when no handle/visual was created, and True for Opening or Open visuals.
            If onClosed IsNot Nothing AndAlso Not popup.HasActivePopupVisual Then
                notifyClosed.Invoke()
            End If

            RootHost.RequestInvalidate()
        End Sub

        ''' <summary>
        ''' Shows a MASMenuBar-owned dropdown through the dedicated shell-navigation popup route.
        ''' This protects the general DropDownMenu/ContextMenu renderer from MenuBar palette changes.
        ''' </summary>
        Friend Sub ShowMenuBarDropDownMenu(anchorRectPx As SKRect,
                                           configure As Action(Of MASDropDownMenuBuilder),
                                           Optional commandHandler As Action(Of String) = Nothing,
                                           Optional viewportProvider As Func(Of SKRect) = Nothing,
                                           Optional onClosed As Action = Nothing)

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

            Dim popup As MASMenuBarDropDownPopupService = RootHost.CreateMenuBarDropDownPopupServiceCore(viewportProvider)
            Dim closeNotificationSent As Boolean = False
            Dim notifyClosed As Action =
                Sub()
                    If onClosed Is Nothing Then Return
                    If closeNotificationSent Then Return

                    closeNotificationSent = True

                    Try
                        onClosed.Invoke()
                    Catch masCaughtExceptionClosed As Exception
                        Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtExceptionClosed, "MASApplicationWindowShell.MenuBarDropDownClosed")
                    End Try
                End Sub

            If onClosed IsNot Nothing Then
                AddHandler popup.Closed,
                    Sub(sender As Object, e As EventArgs)
                        notifyClosed.Invoke()
                    End Sub
            End If

            popup.Show(
                anchorRectPx:=anchorRectPx,
                configure:=configure,
                fallbackCommand:=commandHandler)

            ' A newly shown menu-bar dropdown can still be in the FloatRuntime Opening
            ' lifecycle state while its visual handle already exists. Using IsOpen here
            ' treats Opening as closed, immediately disarms MASMenuBar hover-switch sessions,
            ' and leaves the first popup visible but no longer owned by the menu bar gesture.
            ' HasActivePopupVisual is the correct post-show failure check: it is False only
            ' when no handle/visual was created, and True for Opening or Open visuals.
            If onClosed IsNot Nothing AndAlso Not popup.HasActivePopupVisual Then
                notifyClosed.Invoke()
            End If

            RootHost.RequestInvalidate()
        End Sub

        ''' <summary>
        ''' Internal logical-space menu hook for MAS-owned shell surfaces.
        ''' The shell performs the official logical-to-pixel conversion before delegating to the pixel-space hook.
        ''' </summary>
        Friend Sub ShowDropDownMenuLogical(anchorRectLogical As SKRect,
                                           configure As Action(Of MASDropDownMenuBuilder),
                                           Optional commandHandler As Action(Of String) = Nothing,
                                           Optional viewportProvider As Func(Of SKRect) = Nothing,
                                           Optional onClosed As Action = Nothing)

            ShowDropDownMenu(
                anchorRectPx:=RootHost.LogicalToPx(anchorRectLogical),
                configure:=configure,
                commandHandler:=commandHandler,
                viewportProvider:=viewportProvider,
                onClosed:=onClosed)
        End Sub

        ''' <summary>
        ''' Shows a dropdown menu anchored to an existing MAS control's layout-owned float anchor.
        ''' This is the safest public menu path for direct controls added through MASApplicationWindow.Controls.
        ''' </summary>
        Public Sub ShowDropDownMenuForControl(control As MASControlBase,
                                              configure As Action(Of MASDropDownMenuBuilder),
                                              Optional commandHandler As Action(Of String) = Nothing,
                                              Optional viewportProvider As Func(Of SKRect) = Nothing)

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

            ShowDropDownMenuLogical(
                anchorRectLogical:=MASLayoutSlotBoundsResolver.ResolveFloatAnchorLogical(control),
                configure:=configure,
                commandHandler:=commandHandler,
                viewportProvider:=viewportProvider)
        End Sub

        ''' <summary>
        ''' Internal shell service creation hook. SDK code must use CreateMenuButton, BindMenuButton, or ShowDropDownMenuForControl.
        ''' </summary>
        Friend Function CreateDropDownMenuPopupServiceCore(Optional viewportProvider As Func(Of SKRect) = Nothing) As DropDownMenuPopupService
            Return RootHost.CreateDropDownMenuPopupServiceCore(viewportProvider)
        End Function

    End Class

End Namespace
