Option Strict On
Option Explicit On

Imports System
Imports System.Reflection
Imports System.Windows.Forms
Imports SkiaSharp
Imports Nexamas.UI.General
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Values

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public configuration gateway for the MAS top bar. Consumer code may configure
    ''' title, logo, menu model, and window command events; render, input routing,
    ''' invalidation, native-window dragging, and FloatRuntime binding remain internal
    ''' host plumbing owned by MASApplicationWindow/MASSkiaRootHost.
    ''' </summary>
    Public NotInheritable Class MASTopBarComponent
        Implements IDisposable
        Implements IMASArchitecturalComponent
        Implements ITopBarHostActions
        Implements ITopBarInvalidator
        Implements ITopBarMotionFrameClockProvider

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASTopBarComponent))

        Private ReadOnly _primitives As MASVisualPrimitivesPainter
        Private ReadOnly _engine As TopBarEngine

        Private _windowAdapter As TopBarNativeWindowAdapter
        Private _floatRuntime As MASFloatRuntime
        Private _menuModel As MASDropDownMenuModel

        Private _contextProvider As Func(Of MASThemeContext)
        Private _motionFrameClockProvider As Func(Of MASMotionFrameClock)
        Private _lastCtx As MASThemeContext
        Private _lastBarRectPx As SKRect = SKRect.Empty

        Private _menuPopup As DropDownMenuPopupService

        Private _disposed As Boolean

        Public Sub New()
            _primitives = New MASVisualPrimitivesPainter()
            _engine = New TopBarEngine(_primitives, Me, Me, Me)

            AddHandler _engine.MenuButtonClicked, AddressOf OnMenuButtonClicked
        End Sub

        Friend Event RequestInvalidate As EventHandler
        Public Event MenuCommandInvoked As EventHandler(Of MASTopBarCommandEventArgs)
        Public Event WindowCommandInvoked As EventHandler(Of MASTopBarWindowCommandEventArgs)

        Public Property EnableNativeWindowDrag As Boolean = True
        Public Property EnableStandardWindowCommands As Boolean = True

        Friend Property FloatRuntime As MASFloatRuntime
            Get
                Return _floatRuntime
            End Get
            Set(value As MASFloatRuntime)
                If Object.ReferenceEquals(_floatRuntime, value) Then Return

                CloseMenuFloat()
                ReleaseMenuPopupService()

                _floatRuntime = value
            End Set
        End Property

        Friend ReadOnly Property AttachedForm As Form
            Get
                If _windowAdapter Is Nothing Then Return Nothing
                Return _windowAdapter.Form
            End Get
        End Property

        Public Property Title As String
            Get
                Return _engine.Title
            End Get
            Set(value As String)
                _engine.Title = value
            End Set
        End Property

        Public Property ShowLogo As Boolean
            Get
                Return _engine.ShowLogo
            End Get
            Set(value As Boolean)
                _engine.ShowLogo = value
            End Set
        End Property

        Public Property MenuModel As MASDropDownMenuModel
            Get
                Return _menuModel
            End Get
            Set(value As MASDropDownMenuModel)
                _menuModel = value

                CloseMenuFloat()
                Invalidate()
            End Set
        End Property

        Public Property SurfaceMaterial As String
            Get
                Return _engine.SurfaceMaterialKey
            End Get
            Set(value As String)
                _engine.SurfaceMaterialKey = value
            End Set
        End Property

#Region "SDK Fluent API"

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

        Friend Shared Function [Default]() As MASTopBarComponent
            Return Create().
                WithNativeWindowDrag().
                WithStandardWindowCommands()
        End Function

        Public Function WithTitle(text As String) As MASTopBarComponent
            Me.Title = If(text, String.Empty)
            Return Me
        End Function

        Public Function WithLogo(Optional visible As Boolean = True) As MASTopBarComponent
            Me.ShowLogo = visible
            Return Me
        End Function

        Public Function WithSurfaceMaterial(materialKey As String) As MASTopBarComponent
            Me.SurfaceMaterial = materialKey
            Return Me
        End Function

        Public Function WithoutLogo() As MASTopBarComponent
            Me.ShowLogo = False
            Return Me
        End Function

        Friend Function WithFloatRuntime(runtime As MASFloatRuntime) As MASTopBarComponent
            Me.FloatRuntime = runtime
            Return Me
        End Function

        Friend Sub SetThemeContextProvider(provider As Func(Of MASThemeContext))
            _contextProvider = provider
        End Sub

        Public Function WithMenu(model As MASDropDownMenuModel) As MASTopBarComponent
            Me.MenuModel = model
            Return Me
        End Function

        Public Function WithMenu(configure As Action(Of MASDropDownMenuBuilder)) As MASTopBarComponent
            If configure Is Nothing Then
                Me.MenuModel = Nothing
                Return Me
            End If

            Dim b As MASDropDownMenuBuilder = MASDropDownMenuBuilder.Default()
            configure.Invoke(b)

            Me.MenuModel = b.Build()
            Return Me
        End Function

        Public Function WithNativeWindowDrag(Optional enabled As Boolean = True) As MASTopBarComponent
            Me.EnableNativeWindowDrag = enabled
            Return Me
        End Function

        Public Function WithoutNativeWindowDrag() As MASTopBarComponent
            Me.EnableNativeWindowDrag = False
            Return Me
        End Function

        Public Function WithStandardWindowCommands(Optional enabled As Boolean = True) As MASTopBarComponent
            Me.EnableStandardWindowCommands = enabled
            Return Me
        End Function

        Public Function WithoutStandardWindowCommands() As MASTopBarComponent
            Me.EnableStandardWindowCommands = False
            Return Me
        End Function

        Friend Function AttachToWindow(form As Form) As MASTopBarComponent
            AttachWindow(form)
            Return Me
        End Function

        Friend Function DetachFromWindow() As MASTopBarComponent
            DetachWindow()
            Return Me
        End Function

        Friend Function SyncTitle() As MASTopBarComponent
            SyncTitleFromAttachedWindow()
            Return Me
        End Function

        Public Function WithMenuLottie(asm As Assembly,
                                       resourceName As String) As MASTopBarComponent
            ConfigureMenuLottie(asm, resourceName)
            Return Me
        End Function

        Friend Function OnRequestInvalidate(handler As EventHandler) As MASTopBarComponent
            If handler IsNot Nothing Then
                AddHandler RequestInvalidate, handler
            End If

            Return Me
        End Function

        Friend Function OnMenuCommandInvoked(handler As EventHandler(Of MASTopBarCommandEventArgs)) As MASTopBarComponent
            If handler IsNot Nothing Then
                AddHandler MenuCommandInvoked, handler
            End If

            Return Me
        End Function

        Friend Function OnMenuCommandInvoked(action As Action(Of MASTopBarComponent, String)) As MASTopBarComponent
            If action Is Nothing Then Return Me

            AddHandler MenuCommandInvoked,
                Sub(sender As Object, e As MASTopBarCommandEventArgs)
                    If e Is Nothing Then Return
                    action.Invoke(Me, e.CommandId)
                End Sub

            Return Me
        End Function

        Friend Function OnWindowCommandInvoked(handler As EventHandler(Of MASTopBarWindowCommandEventArgs)) As MASTopBarComponent
            If handler IsNot Nothing Then
                AddHandler WindowCommandInvoked, handler
            End If

            Return Me
        End Function

        Friend Function OnWindowCommandInvoked(action As Action(Of MASTopBarComponent, MASTopBarWindowCommand)) As MASTopBarComponent
            If action Is Nothing Then Return Me

            AddHandler WindowCommandInvoked,
                Sub(sender As Object, e As MASTopBarWindowCommandEventArgs)
                    If e Is Nothing Then
                        Return
                    End If

                    action.Invoke(Me, e.Command)
                End Sub

            Return Me
        End Function

#End Region

        Friend Sub AttachWindow(form As Form)
            If _disposed Then Return
            If form Is Nothing Then Throw New ArgumentNullException(NameOf(form))

            DetachWindow()

            _windowAdapter = New TopBarNativeWindowAdapter(form)
            _engine.HostActions = Me

            If String.IsNullOrWhiteSpace(Me.Title) Then
                Me.Title = form.Text
            End If
        End Sub

        Friend Sub DetachWindow()
            If _windowAdapter Is Nothing Then Return

            Try
                _windowAdapter.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try

            _windowAdapter = Nothing
        End Sub

        Friend Sub SyncTitleFromAttachedWindow()
            If _disposed Then Return
            If _windowAdapter Is Nothing Then Return

            Try
                Me.Title = _windowAdapter.Form.Text
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2)
            End Try
        End Sub

        Friend Sub Render(canvas As SKCanvas,
                          ctx As MASThemeContext,
                          barRectPx As SKRect,
                          timeSeconds As Single)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return

            _lastCtx = ctx
            _lastBarRectPx = barRectPx

            _engine.Render(canvas, ctx, barRectPx, timeSeconds)
        End Sub

        Friend Sub PointerMove(ptPx As SKPoint)
            If _disposed Then Return

            _engine.PointerMove(ptPx)
        End Sub

        Friend Function PointerDown(ptPx As SKPoint,
                                    Optional screenPt As SKPoint? = Nothing) As Boolean

            If _disposed Then Return False

            Return _engine.PointerDown(ptPx, screenPt)
        End Function

        Friend Sub PointerUp(ptPx As SKPoint,
                             Optional screenPt As SKPoint? = Nothing)

            If _disposed Then Return

            _engine.PointerUp(ptPx, screenPt)
        End Sub

        Friend Sub PointerLeave()
            If _disposed Then Return

            _engine.PointerLeave()
        End Sub

        Friend Sub PointerMoveScreen(screenPt As SKPoint)
            If _disposed Then Return

            _engine.PointerMoveScreen(screenPt)
        End Sub

        Friend Function WantsCaptureAt(ptPx As SKPoint) As Boolean
            If _disposed Then Return False

            Return _engine.WantsCaptureAt(ptPx)
        End Function

        Friend Sub ConfigureMenuLottie(asm As Assembly,
                                       resourceName As String)

            If _disposed Then Return

            _engine.ConfigureMenuLottie(asm, resourceName)
        End Sub

        Friend Sub SetMotionFrameClockProvider(provider As Func(Of MASMotionFrameClock))
            If _disposed Then Return
            _motionFrameClockProvider = provider
        End Sub

        Friend Function TryGetMotionFrameClock() As MASMotionFrameClock Implements ITopBarMotionFrameClockProvider.TryGetMotionFrameClock
            If _disposed OrElse _motionFrameClockProvider Is Nothing Then Return Nothing

            Try
                Return _motionFrameClockProvider.Invoke()
            Catch masCaughtExceptionTopBarClock As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(masCaughtExceptionTopBarClock, "MASTopBarComponent.MotionFrameClock")
                Return Nothing
            End Try
        End Function

        Private Sub OnMenuButtonClicked(sender As Object,
                                        e As TopBarMenuButtonClickEventArgs)

            If _disposed Then Return
            If e Is Nothing Then Return

            If _floatRuntime Is Nothing Then Return
            If _lastCtx Is Nothing Then Return
            If _menuModel Is Nothing OrElse _menuModel.Root Is Nothing Then Return

            Dim popup As DropDownMenuPopupService = EnsureMenuPopupService()
            If popup Is Nothing Then Return

            If popup.HasActivePopupVisual Then
                CloseMenuFloat()
                Return
            End If

            Dim anchorRectPx As SKRect = ResolveTopBarMenuAnchorRectPx(
                buttonRectPx:=e.ButtonRectPx,
                barRectPx:=e.BarRectPx)

            Try
                popup.Show(
                    anchorRectPx:=anchorRectPx,
                    model:=_menuModel,
                    executeCommand:=AddressOf ExecuteMenuCommand,
                    presentation:=CreateTopBarMenuPresentation()
                )

                _engine.SetMenuButtonOpenVisual(popup.HasActivePopupVisual)
                Invalidate()

            Catch masCaughtException18 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException18)
                _engine.SetMenuButtonOpenVisual(False)
                Invalidate()
            End Try
        End Sub

        Private Function EnsureMenuPopupService() As DropDownMenuPopupService
            If _disposed Then Return Nothing
            If _floatRuntime Is Nothing Then Return Nothing

            If _menuPopup Is Nothing Then
                _menuPopup = New DropDownMenuPopupService(
                    runtime:=_floatRuntime,
                    getContext:=AddressOf ResolveMenuContext,
                    getViewportBoundsPx:=AddressOf ResolveFloatViewportBoundsPx,
                    ownerKey:=MASTopBarChromePolicy.MenuOwnerKey,
                    floatKey:=MASFloatKeys.DropDownMenu
                )

                AddHandler _menuPopup.Closed, AddressOf OnMenuPopupClosed
            End If

            Return _menuPopup
        End Function

        Private Function ResolveMenuContext() As MASThemeContext
            If _contextProvider IsNot Nothing Then
                Try
                    Dim current As MASThemeContext = _contextProvider.Invoke()

                    If current IsNot Nothing Then
                        Return current
                    End If

                Catch masCaughtException3 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException3)
                End Try
            End If

            Return _lastCtx
        End Function

        Private Function ResolveFloatViewportBoundsPx() As SKRect
            Dim ctx As MASThemeContext = ResolveMenuContext()
            If ctx Is Nothing Then Return SKRect.Empty

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)

            Return BuildSafeFloatInputBounds(_lastBarRectPx, dpi)
        End Function

        Private Shared Function CreateTopBarMenuPresentation() As MASFloatPresentation
            Return MASFloatPresentationProfiles.AnchoredDropDownMenu()
        End Function

        Private Shared Function ResolveTopBarMenuAnchorRectPx(buttonRectPx As SKRect,
                                                             barRectPx As SKRect) As SKRect
            Dim anchorRectPx As SKRect = buttonRectPx

            If anchorRectPx.IsEmpty Then
                anchorRectPx = barRectPx
            End If

            If anchorRectPx.IsEmpty OrElse barRectPx.IsEmpty Then Return anchorRectPx

            Dim bottomPx As Single = Math.Max(anchorRectPx.Bottom, barRectPx.Bottom)
            If bottomPx <= anchorRectPx.Bottom + 0.01F Then Return anchorRectPx

            Dim heightPx As Single = Math.Max(1.0F, anchorRectPx.Height)

            Return New SKRect(
                anchorRectPx.Left,
                bottomPx - heightPx,
                anchorRectPx.Right,
                bottomPx)
        End Function

        Private Shared Function BuildSafeFloatInputBounds(barRectPx As SKRect,
                                                          dpi As Single) As SKRect
            Return MASTopBarChromePolicy.BuildSafeFloatInputBounds(barRectPx, dpi)
        End Function

        Private Sub ReleaseMenuPopupService()
            If _menuPopup Is Nothing Then Return

            Try
                RemoveHandler _menuPopup.Closed, AddressOf OnMenuPopupClosed
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException4)
            End Try

            _menuPopup = Nothing
        End Sub

        Private Sub CloseMenuFloat()
            Try
                If _menuPopup IsNot Nothing AndAlso _menuPopup.HasActivePopupVisual Then
                    _menuPopup.Close()
                    Return
                End If

            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException5)
            End Try

            OnMenuPopupClosed(Me, EventArgs.Empty)
        End Sub

        Private Sub OnMenuPopupClosed(sender As Object,
                                      e As EventArgs)

            If _disposed Then Return

            _engine.SetMenuButtonOpenVisual(False)
            Invalidate()
        End Sub

        Friend Sub Invalidate() Implements ITopBarInvalidator.Invalidate
            RaiseRequestInvalidateSafe()
        End Sub

        Friend Sub BeginDrag(screenPt As SKPoint) Implements ITopBarHostActions.BeginDrag
            If _disposed Then Return
            If Not EnableNativeWindowDrag Then Return
            If _windowAdapter Is Nothing Then Return

            Try
                _windowAdapter.BeginDrag(screenPt)
            Catch masCaughtException6 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException6)
            End Try
        End Sub

        Friend Sub DragTo(screenPt As SKPoint) Implements ITopBarHostActions.DragTo
            If _disposed Then Return
            If Not EnableNativeWindowDrag Then Return
            If _windowAdapter Is Nothing Then Return

            Try
                _windowAdapter.DragTo(screenPt)
            Catch masCaughtException7 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException7)
            End Try
        End Sub

        Friend Sub EndDrag() Implements ITopBarHostActions.EndDrag
            If _disposed Then Return
            If Not EnableNativeWindowDrag Then Return
            If _windowAdapter Is Nothing Then Return

            Try
                _windowAdapter.EndDrag()
            Catch masCaughtException8 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException8)
            End Try
        End Sub

        Private Sub ExecuteMenuCommand(commandId As String)
            Dim key As String = If(commandId, String.Empty).Trim()
            If key.Length = 0 Then Return

            _engine.SetMenuButtonOpenVisual(False)

            If TryExecuteStandardWindowCommand(key) Then Return

            RaiseMenuCommandInvokedSafe(key)
        End Sub

        Private Sub RaiseRequestInvalidateSafe()
            Try
                RaiseEvent RequestInvalidate(Me, EventArgs.Empty)
            Catch masCaughtException9 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                    masCaughtException9,
                    "MASTopBarComponent.RequestInvalidate")
            End Try
        End Sub

        Private Sub RaiseMenuCommandInvokedSafe(commandKey As String)
            Try
                RaiseEvent MenuCommandInvoked(Me, New MASTopBarCommandEventArgs(commandKey))
            Catch masCaughtException10 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                    masCaughtException10,
                    "MASTopBarComponent.MenuCommandInvoked")
            End Try
        End Sub

        Private Sub RaiseWindowCommandInvokedSafe(command As MASTopBarWindowCommand)
            Try
                RaiseEvent WindowCommandInvoked(Me, New MASTopBarWindowCommandEventArgs(command))
            Catch masCaughtException11 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                    masCaughtException11,
                    "MASTopBarComponent.WindowCommandInvoked")
            End Try
        End Sub

        Private Function TryExecuteStandardWindowCommand(commandId As String) As Boolean
            If Not EnableStandardWindowCommands Then Return False

            Dim resolved As Nullable(Of MASTopBarWindowCommand) = MASTopBarChromePolicy.ResolveStandardWindowCommand(commandId)
            If Not resolved.HasValue Then Return False

            RaiseWindowCommandInvokedSafe(resolved.Value)

            If _windowAdapter IsNot Nothing Then
                Select Case resolved.Value
                    Case MASTopBarWindowCommand.Minimize
                        _windowAdapter.Minimize()
                    Case MASTopBarWindowCommand.ToggleMaximizeRestore
                        _windowAdapter.ToggleMaximizeRestore()
                    Case MASTopBarWindowCommand.CloseWindow
                        _windowAdapter.CloseWindow()
                End Select
            End If

            Return True
        End Function

        Friend Function CreateTopBarReadinessManifest() As MASTopBarReadinessManifest
            Return MASTopBarReadinessManifest.CreateCurrent()
        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True

            Try
                RemoveHandler _engine.MenuButtonClicked, AddressOf OnMenuButtonClicked
            Catch masCaughtException12 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException12)
            End Try

            Try
                CloseMenuFloat()
            Catch masCaughtException13 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException13)
            End Try

            Try
                ReleaseMenuPopupService()
            Catch masCaughtException14 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException14)
            End Try

            Try
                DetachWindow()
            Catch masCaughtException15 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException15)
            End Try

            Try
                _engine.Dispose()
            Catch masCaughtException16 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException16)
            End Try

            Try
                _primitives.Dispose()
            Catch masCaughtException17 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException17)
            End Try

            _floatRuntime = Nothing
            _menuModel = Nothing
            _menuPopup = Nothing
            _contextProvider = Nothing
            _lastCtx = Nothing
            _lastBarRectPx = SKRect.Empty
        End Sub

    End Class

End Namespace