Option Strict On
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports DrawingColor = System.Drawing.Color
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Windows.Forms
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.WinForms

    ''' <summary>
    ''' Compatibility-only WinForms bridge trigger for MAS dropdown menus. Public consumers should
    ''' normally obtain or bind instances through MASApplicationWindow.Shell so
    ''' the RootHost and popup service stay owned by the MAS application facade.
    ''' </summary>
    ''' <remarks>
    ''' Phase-11 API-04: MASMenuButton is intentionally isolated in Nexamas.UI.WinForms
    ''' as an advanced compatibility surface. It is not a beginner/core SDK control path,
    ''' does not expose RootHost publicly, and should not be expanded as a parallel UI layer.
    ''' </remarks>
    <ToolboxItem(True)>
    <DesignerCategory("Code")>
    <EditorBrowsable(EditorBrowsableState.Advanced)>
    Public Class MASMenuButton
        Inherits Button
        Implements IMASMenuTrigger

        Private _rootHost As MASSkiaRootHost
        Private _menuService As DropDownMenuPopupService
        Private _menuBuilder As Action(Of MASDropDownMenuBuilder)
        Private _commandHandler As Action(Of String)
        Private _isHovering As Boolean
        Private _isPressing As Boolean

        Public Sub New()
            MyBase.New()

            Dim defaultSize As SKSize = MASIntrinsicControlSizeMetrics.ResolveMenuButtonDefaultSize()
            Dim minimumSizeContract As SKSize = MASIntrinsicControlSizeMetrics.ResolveMenuButtonMinimumSize()
            Dim contentInset As MASLayoutInset = MASIntrinsicControlSizeMetrics.CreateMenuButtonContentInset()

            Text = "Menu"
            AutoSize = False
            Width = ToWinFormsPixels(defaultSize.Width)
            Height = ToWinFormsPixels(defaultSize.Height)
            MinimumSize = New Size(ToWinFormsPixels(minimumSizeContract.Width), ToWinFormsPixels(minimumSizeContract.Height))
            Padding = New Padding(
                ToWinFormsPixels(contentInset.Left),
                ToWinFormsPixels(contentInset.Top),
                ToWinFormsPixels(contentInset.Right),
                ToWinFormsPixels(contentInset.Bottom))
            TextAlign = ContentAlignment.MiddleCenter
            UseVisualStyleBackColor = False
            FlatStyle = FlatStyle.Flat
            FlatAppearance.BorderSize = 0
            BackColor = DrawingColor.Transparent
            Cursor = Cursors.Hand

            SetStyle(ControlStyles.UserPaint Or
                     ControlStyles.AllPaintingInWmPaint Or
                     ControlStyles.OptimizedDoubleBuffer Or
                     ControlStyles.ResizeRedraw Or
                     ControlStyles.SupportsTransparentBackColor,
                     True)
        End Sub

        <Browsable(False)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        <Obsolete("RootHost is a raw wiring property kept for compatibility. Prefer MASApplicationWindow.Shell.CreateMenuButton or MASApplicationWindow.Shell.BindMenuButton.", False)>
        Friend Property RootHost As MASSkiaRootHost
            Get
                Return _rootHost
            End Get
            Set(value As MASSkiaRootHost)
                _rootHost = value
                RebuildMenuService()
            End Set
        End Property

        <Browsable(False)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        Public Property MenuBuilder As Action(Of MASDropDownMenuBuilder) Implements IMASMenuTrigger.MenuBuilder
            Get
                Return _menuBuilder
            End Get
            Set(value As Action(Of MASDropDownMenuBuilder))
                If Object.ReferenceEquals(_menuBuilder, value) Then Return
                CloseMenu()
                _menuBuilder = value
                Invalidate()
            End Set
        End Property

        <Browsable(False)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        Public Property CommandHandler As Action(Of String) Implements IMASMenuTrigger.CommandHandler
            Get
                Return _commandHandler
            End Get
            Set(value As Action(Of String))
                If Object.ReferenceEquals(_commandHandler, value) Then Return
                _commandHandler = value
            End Set
        End Property
#Region "SDK Fluent API"

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

        Friend Shared Function [Default]() As MASMenuButton
            Return Create()
        End Function

        <Obsolete("WithRootHost is a raw wiring shortcut kept for compatibility. Prefer MASApplicationWindow.Shell.CreateMenuButton or MASApplicationWindow.Shell.BindMenuButton.", False)>
        Friend Function WithRootHost(rootHost As MASSkiaRootHost) As MASMenuButton
            _rootHost = rootHost
            RebuildMenuService()
            Return Me
        End Function

        Friend Function BindToShellRootHost(rootHost As MASSkiaRootHost) As MASMenuButton
            _rootHost = rootHost
            RebuildMenuService()
            Return Me
        End Function

        Public Function WithText(text As String) As MASMenuButton
            Me.Text = MASMenuButtonChromePolicy.NormalizeText(text)
            Return Me
        End Function

        ''' <summary>
        ''' Applies the same MASSize intent vocabulary used by Skia controls to this WinForms toolbox trigger.
        ''' The trigger keeps explicit pixel WithSize(width, height) for advanced interop, but named sizes
        ''' are always resolved from the MASSize default-size contract instead of a local preset table.
        ''' </summary>
        Public Function WithSize(sizeIntent As MASSize) As MASMenuButton
            Dim defaultSize As SKSize = MASIntrinsicControlSizeMetrics.ResolveMenuButtonDefaultSize()
            Dim minimumSizeContract As SKSize = MASIntrinsicControlSizeMetrics.ResolveMenuButtonMinimumSize()
            Dim resolved As MASSizeResult = MASSizeResolver.FromMeasured(
                desiredSize:=defaultSize,
                minSize:=minimumSizeContract,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=sizeIntent,
                context:=New MASSizeContext(New SKSize(Single.PositiveInfinity, Single.PositiveInfinity)),
                canGrowWidth:=False,
                canGrowHeight:=False,
                contentInset:=MASIntrinsicControlSizeMetrics.CreateMenuButtonContentInset(),
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)

            Return WithSize(ToWinFormsPixels(resolved.DesiredSize.Width), ToWinFormsPixels(resolved.DesiredSize.Height))
        End Function

        Friend Function WithDefaultSize() As MASMenuButton
            Return WithSize(MASSize.[Default])
        End Function

        Friend Function WithCompactSize() As MASMenuButton
            Return WithSize(MASSize.Compact)
        End Function

        Friend Function WithLargeSize() As MASMenuButton
            Return WithSize(MASSize.Large)
        End Function

        Friend Function WithFillWidthSize() As MASMenuButton
            Return WithSize(MASSize.FillWidth)
        End Function

        Friend Function WithHugContentSize() As MASMenuButton
            Return WithSize(MASSize.HugContent)
        End Function

        <EditorBrowsable(EditorBrowsableState.Advanced)>
        Friend Function WithSize(width As Integer,
                                 height As Integer) As MASMenuButton
            Me.Width = SafeWidth(width)
            Me.Height = SafeHeight(height)
            Return Me
        End Function

        Friend Function WithBounds(x As Integer,
                                   y As Integer,
                                   width As Integer,
                                   height As Integer) As MASMenuButton
            Me.SetBounds(
                x,
                y,
                SafeWidth(width),
                SafeHeight(height)
            )

            Return Me
        End Function

        Public Function WithMenu(buildMenu As Action(Of MASDropDownMenuBuilder)) As MASMenuButton
            Me.MenuBuilder = buildMenu
            Return Me
        End Function

        Friend Function OnCommand(commandHandler As Action(Of String)) As MASMenuButton
            Me.CommandHandler = commandHandler
            Return Me
        End Function

        Public Function WithAction(text As String,
                                   commandId As String,
                                   Optional handler As Action = Nothing,
                                   Optional enabled As Boolean = True,
                                   Optional shortcutText As String = "",
                                   Optional intent As MASDropDownMenuItemIntent = MASDropDownMenuItemIntent.Default) As MASMenuButton

            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)

                    menu.AddAction(
                        text:=text,
                        commandId:=commandId,
                        handler:=handler,
                        enabled:=enabled,
                        shortcutText:=shortcutText,
                        intent:=intent
                    )
                End Sub

            Return Me
        End Function

        Public Function WithSeparator() As MASMenuButton
            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)
                    menu.AddSeparator()
                End Sub

            Return Me
        End Function

        Public Function WithSubMenu(text As String,
                                    configure As Action(Of MASDropDownMenuBuilder),
                                    Optional enabled As Boolean = True) As MASMenuButton

            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)

                    menu.AddSubMenu(
                        text:=text,
                        configure:=configure,
                        enabled:=enabled
                    )
                End Sub

            Return Me
        End Function

#End Region

#Region "Premium WinForms trigger chrome"

        Protected Overrides Sub OnMouseEnter(e As EventArgs)
            MyBase.OnMouseEnter(e)
            _isHovering = True
            Invalidate()
        End Sub

        Protected Overrides Sub OnMouseLeave(e As EventArgs)
            MyBase.OnMouseLeave(e)
            _isHovering = False
            _isPressing = False
            Invalidate()
        End Sub

        Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
            MyBase.OnMouseDown(e)
            If e IsNot Nothing AndAlso e.Button = MouseButtons.Left AndAlso CanShowMenu() Then
                _isPressing = True
                Invalidate()
            End If
        End Sub

        Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
            MyBase.OnMouseUp(e)
            If _isPressing Then
                _isPressing = False
                Invalidate()
            End If
        End Sub

        Protected Overrides Sub OnEnabledChanged(e As EventArgs)
            MyBase.OnEnabledChanged(e)
            _isHovering = False
            _isPressing = False
            If Not Enabled Then CloseMenu()
            Invalidate()
        End Sub

        Protected Overrides Sub OnGotFocus(e As EventArgs)
            MyBase.OnGotFocus(e)
            Invalidate()
        End Sub

        Protected Overrides Sub OnLostFocus(e As EventArgs)
            MyBase.OnLostFocus(e)
            _isPressing = False
            Invalidate()
        End Sub

        Protected Overrides Sub OnVisibleChanged(e As EventArgs)
            MyBase.OnVisibleChanged(e)
            If Not Visible Then CloseMenu()
        End Sub

        Protected Overrides Sub OnParentChanged(e As EventArgs)
            MyBase.OnParentChanged(e)
            If Parent Is Nothing Then CloseMenu()
        End Sub

        Protected Overrides Sub OnHandleDestroyed(e As EventArgs)
            CloseMenu()
            MyBase.OnHandleDestroyed(e)
        End Sub

        Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
            If e IsNot Nothing Then
                If MASMenuButtonChromePolicy.ShouldCloseFromKey(e.KeyCode) Then
                    CloseMenu()
                    _isPressing = False
                    e.Handled = True
                    e.SuppressKeyPress = True
                    Invalidate()
                    Return
                End If

                If MASMenuButtonChromePolicy.ShouldOpenFromKey(e.KeyCode, e.Alt) AndAlso CanShowMenu() Then
                    _isPressing = True
                    ShowMenu()
                    _isPressing = False
                    e.Handled = True
                    e.SuppressKeyPress = True
                    Invalidate()
                    Return
                End If
            End If

            MyBase.OnKeyDown(e)
        End Sub

        Protected Overrides Sub OnPaint(e As PaintEventArgs)
            If e Is Nothing OrElse e.Graphics Is Nothing Then Return

            Dim g As Graphics = e.Graphics
            g.SmoothingMode = SmoothingMode.AntiAlias
            g.PixelOffsetMode = PixelOffsetMode.HighQuality
            g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit

            Dim bounds As Rectangle = ClientRectangle
            If bounds.Width <= 1 OrElse bounds.Height <= 1 Then Return

            Dim surfaceBounds As New RectangleF(
                0.5F,
                0.5F,
                CSng(Math.Max(1, bounds.Width - 1)),
                CSng(Math.Max(1, bounds.Height - 1)))

            Dim state As MASMenuButtonChromeState = MASMenuButtonChromePolicy.ResolveState(Enabled, _isHovering, _isPressing, Focused)
            Dim radius As Single = MASMenuButtonChromePolicy.ResolveCornerRadius(surfaceBounds)
            Dim colors As MASMenuButtonChrome = MASMenuButtonChromePolicy.ResolveChrome(state)

            Using path As GraphicsPath = CreateRoundedRectanglePath(surfaceBounds, radius)
                Using fill As New LinearGradientBrush(surfaceBounds, colors.FillTop, colors.FillBottom, LinearGradientMode.Vertical)
                    g.FillPath(fill, path)
                End Using

                Using wash As New SolidBrush(colors.Wash)
                    g.FillPath(wash, path)
                End Using

                Using borderPen As New Pen(colors.Border, 1.0F)
                    g.DrawPath(borderPen, path)
                End Using

                If Focused AndAlso ShowFocusCues Then
                    Dim focusRect As RectangleF = surfaceBounds
                    focusRect.Inflate(-2.5F, -2.5F)
                    Using focusPath As GraphicsPath = CreateRoundedRectanglePath(focusRect, Math.Max(1.0F, radius - 2.0F))
                        Using focusPen As New Pen(colors.FocusRing, 1.2F)
                            g.DrawPath(focusPen, focusPath)
                        End Using
                    End Using
                End If
            End Using

            Dim glyphWidth As Integer = MASMenuButtonChromePolicy.ResolveGlyphWidth(bounds)
            Dim textRect As Rectangle = MASMenuButtonChromePolicy.ResolveTextRect(bounds, Padding, glyphWidth, Me.RightToLeft)
            Dim flags As TextFormatFlags = MASMenuButtonChromePolicy.ResolveTextFlags(Me.RightToLeft)

            TextRenderer.DrawText(g, MASMenuButtonChromePolicy.NormalizeText(Text), Font, textRect, colors.Text, flags)
            DrawChevron(g, bounds, colors.Glyph)
        End Sub

        Private Shared Function CreateRoundedRectanglePath(bounds As RectangleF,
                                                                  radius As Single) As GraphicsPath
            Dim path As New GraphicsPath()

            If bounds.Width <= 0.0F OrElse bounds.Height <= 0.0F Then
                Return path
            End If

            Dim safeRadius As Single = radius
            If Single.IsNaN(safeRadius) OrElse Single.IsInfinity(safeRadius) OrElse safeRadius < 0.0F Then
                safeRadius = 0.0F
            End If

            safeRadius = Math.Min(safeRadius, Math.Min(bounds.Width, bounds.Height) * 0.5F)

            If safeRadius <= 0.5F Then
                path.AddRectangle(bounds)
                path.CloseFigure()
                Return path
            End If

            Dim diameter As Single = safeRadius * 2.0F
            Dim right As Single = bounds.Right - diameter
            Dim bottom As Single = bounds.Bottom - diameter

            path.AddArc(bounds.Left, bounds.Top, diameter, diameter, 180.0F, 90.0F)
            path.AddArc(right, bounds.Top, diameter, diameter, 270.0F, 90.0F)
            path.AddArc(right, bottom, diameter, diameter, 0.0F, 90.0F)
            path.AddArc(bounds.Left, bottom, diameter, diameter, 90.0F, 90.0F)
            path.CloseFigure()

            Return path
        End Function

        Private Sub DrawChevron(g As Graphics,
                                bounds As Rectangle,
                                color As DrawingColor)

            If g Is Nothing Then Return
            If bounds.Width <= 1 OrElse bounds.Height <= 1 Then Return

            Dim cx As Single = MASMenuButtonChromePolicy.ResolveChevronCenterX(bounds, Padding, Me.RightToLeft)
            Dim cy As Single = bounds.Top + (bounds.Height * 0.5F) + 1.0F
            Dim size As Single = MASMenuButtonChromePolicy.ResolveChevronSize(bounds)

            Using pen As New Pen(color, 1.45F)
                pen.StartCap = LineCap.Round
                pen.EndCap = LineCap.Round
                pen.LineJoin = LineJoin.Round
                g.DrawLines(
                    pen,
                    New PointF() {
                        New PointF(cx - size, cy - (size * 0.35F)),
                        New PointF(cx, cy + (size * 0.45F)),
                        New PointF(cx + size, cy - (size * 0.35F))
                    })
            End Using
        End Sub

#End Region

        Protected Overrides Sub OnClick(e As EventArgs)
            MyBase.OnClick(e)
            ShowMenu()
        End Sub

        Public Sub ShowMenu() Implements IMASMenuTrigger.ShowMenu
            If Not CanShowMenu() Then Return

            EnsureMenuService()

            If _menuService Is Nothing Then Return

            If _menuService.IsOpen Then
                CloseMenu()
                Return
            End If

            _menuService.Show(
                anchorRectPx:=GetMenuAnchorRectPx(),
                configure:=_menuBuilder,
                fallbackCommand:=_commandHandler
            )

            _rootHost.RequestInvalidate()
        End Sub

        Public Sub CloseMenu() Implements IMASMenuTrigger.CloseMenu
            If _menuService Is Nothing Then Return

            _menuService.Close()

            If _rootHost IsNot Nothing Then
                _rootHost.RequestInvalidate()
            End If
        End Sub

        ' CONTRACT:
        ' Returns the menu anchor in SK surface physical pixels.
        ' MASSkiaRootHost.ScreenToClientPx already returns the hosted SK surface
        ' client pixel coordinate space. Do not multiply these values by DPI again.
        ' Do not replace this with BoundsLogical or raw form-relative bounds.
        Public Function GetMenuAnchorRectPx() As SKRect Implements IMASMenuTrigger.GetMenuAnchorRectPx
            If _rootHost Is Nothing Then
                Return SKRect.Empty
            End If

            Dim screenLeftTop As Point = PointToScreen(New Point(0, 0))
            Dim skClientLeftTop As Point = _rootHost.ScreenToClientPx(screenLeftTop)

            Return New SKRect(
                CSng(skClientLeftTop.X),
                CSng(skClientLeftTop.Y),
                CSng(skClientLeftTop.X + Width),
                CSng(skClientLeftTop.Y + Height)
            )
        End Function

        Private Sub EnsureMenuService()
            If _menuService IsNot Nothing Then Return
            RebuildMenuService()
        End Sub

        Private Sub RebuildMenuService()
            If _menuService IsNot Nothing Then
                _menuService.Close()
            End If

            _menuService = Nothing

            If _rootHost Is Nothing Then Return

            _menuService = _rootHost.CreateDropDownMenuPopupServiceCore()
        End Sub

        Private Function CanShowMenu() As Boolean
            Return MASMenuButtonChromePolicy.CanShowMenu(
                isDesignMode:=DesignMode,
                isEnabled:=Enabled,
                hasRootHost:=_rootHost IsNot Nothing,
                hasMenuBuilder:=_menuBuilder IsNot Nothing)
        End Function

        Private Shared Function SafeWidth(value As Integer) As Integer
            Return MASIntrinsicControlSizeMetrics.ResolveMenuButtonWidth(value)
        End Function

        Private Shared Function SafeHeight(value As Integer) As Integer
            Return MASIntrinsicControlSizeMetrics.ResolveMenuButtonHeight(value)
        End Function

        Private Shared Function ToWinFormsPixels(value As Single) As Integer
            Return Math.Max(0, CInt(Math.Ceiling(CDbl(value))))
        End Function

    End Class

End Namespace
