Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.General
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components.DropDownMenu

    Friend NotInheritable Class DropDownMenuInteractionResolver

        Private Structure MenuInteractionColors
            Friend Fill As SKColor
            Friend Ring As SKColor
        End Structure

        Friend Function ResolveRowState(m As DropDownMenuState,
                                        level As Integer,
                                        rowIndex As Integer) As MASInteractionState

            If m Is Nothing Then Return MASInteractionState.None

            If Not m.Enabled Then
                Return MASInteractionState.Disabled
            End If

            Dim hoverIdx As Integer = ResolveHoverIndex(m, level)
            Dim openIdx As Integer = ResolveOpenIndex(m, level)

            If rowIndex = hoverIdx Then Return MASInteractionState.Hovered
            If rowIndex = openIdx Then Return MASInteractionState.Selected

            Return MASInteractionState.None
        End Function

        Friend Function ResolveHoverIndex(m As DropDownMenuState,
                                          level As Integer) As Integer

            If m Is Nothing Then Return -1
            If m.HoverPath Is Nothing Then Return -1
            If level < 0 OrElse level >= m.HoverPath.Count Then Return -1

            Return m.HoverPath(level)
        End Function

        Friend Function ResolveOpenIndex(m As DropDownMenuState,
                                         level As Integer) As Integer

            If m Is Nothing Then Return -1
            If m.OpenPath Is Nothing Then Return -1
            If level < 0 OrElse level >= m.OpenPath.Count Then Return -1

            Return m.OpenPath(level)
        End Function

        Friend Function ShouldDrawRowOverlay(state As MASInteractionState) As Boolean
            Return state <> MASInteractionState.None
        End Function

        Friend Function ResolveTextColor(ctx As MASThemeContext,
                                         rowState As MASInteractionState,
                                         menuEnabled As Boolean,
                                         menuTheme As IMASMenuTheme) As SKColor

            If ctx Is Nothing OrElse ctx.Theme Is Nothing OrElse menuTheme Is Nothing Then
                Return SKColors.White
            End If

            Dim baseColor As SKColor =
                If(menuEnabled, menuTheme.ItemText.Text, menuTheme.ItemText.TextDisabled)

            If rowState = MASInteractionState.Disabled OrElse Not menuEnabled Then
                Return ColorMath.Mix(baseColor, MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Disabled, 0.42F)
            End If

            Dim colors As MenuInteractionColors = ResolveMenuInteractionColors(rowState, ctx.Theme)
            Dim ringStrength As Single = ResolveRingStrength(rowState)

            If colors.Ring.Alpha = 0 OrElse ringStrength <= 0.0F Then Return baseColor

            Select Case rowState
                Case MASInteractionState.Hovered
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.16F * ringStrength)

                Case MASInteractionState.Selected
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.24F * ringStrength)

                Case MASInteractionState.Pressed
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.18F * ringStrength)

                Case MASInteractionState.Focused
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.18F * ringStrength)

                Case Else
                    Return baseColor
            End Select
        End Function

        Friend Function ResolveChevronColor(ctx As MASThemeContext,
                                            rowState As MASInteractionState,
                                            menuEnabled As Boolean,
                                            menuTheme As IMASMenuTheme) As SKColor

            If ctx Is Nothing OrElse ctx.Theme Is Nothing OrElse menuTheme Is Nothing Then
                Return SKColors.White
            End If

            Dim baseColor As SKColor =
                If(menuEnabled, menuTheme.ItemText.Icon, menuTheme.ItemText.IconMuted)

            If rowState = MASInteractionState.Disabled OrElse Not menuEnabled Then
                Return ColorMath.Mix(baseColor, MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Disabled, 0.46F)
            End If

            Dim colors As MenuInteractionColors = ResolveMenuInteractionColors(rowState, ctx.Theme)
            Dim ringStrength As Single = ResolveRingStrength(rowState)

            If colors.Ring.Alpha = 0 OrElse ringStrength <= 0.0F Then Return baseColor

            Select Case rowState
                Case MASInteractionState.Hovered
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.18F * ringStrength)

                Case MASInteractionState.Selected
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.28F * ringStrength)

                Case MASInteractionState.Pressed
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.2F * ringStrength)

                Case MASInteractionState.Focused
                    Return ColorMath.Mix(baseColor, colors.Ring, 0.2F * ringStrength)

                Case Else
                    Return baseColor
            End Select
        End Function

        Friend Sub DrawRowOverlay(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  dpi As Single,
                                  r As SKRect,
                                  panelCornerPx As Single,
                                  isFirst As Boolean,
                                  isLast As Boolean,
                                  state As MASInteractionState,
                                  isDisabled As Boolean,
                                  itemSurface As MASMenuItemSurfaceTheme)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If r.Width <= 2.0F OrElse r.Height <= 2.0F Then Return

            dpi = PixelSnap.SafeDpi(dpi)

            Dim appTheme As IMASAppTheme = ctx.Theme
            If appTheme Is Nothing Then Return

            Dim effectiveState As MASInteractionState =
                If(isDisabled, MASInteractionState.Disabled, state)

            Dim colors As MenuInteractionColors =
                ResolveMenuInteractionColors(effectiveState, appTheme)

            If colors.Fill.Alpha = 0 AndAlso colors.Ring.Alpha = 0 Then Return

            Dim fillStrength As Single = ResolveFillStrength(effectiveState)
            Dim ringStrength As Single = ResolveRingStrength(effectiveState)

            Dim mm As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(dpi)

            Dim baseR As SKRect = MASSurfaceInteractionRing.SnapBaseRect(r, mm)
            If baseR.Width <= 1.0F OrElse baseR.Height <= 1.0F Then Return

            Dim tl As Single = If(isFirst, PixelSnap.SafeRadius(panelCornerPx), 0.0F)
            Dim tr As Single = If(isFirst, PixelSnap.SafeRadius(panelCornerPx), 0.0F)
            Dim br As Single = If(isLast, PixelSnap.SafeRadius(panelCornerPx), 0.0F)
            Dim bl As Single = If(isLast, PixelSnap.SafeRadius(panelCornerPx), 0.0F)

            Dim bodyTop As SKColor = SKColors.Transparent
            Dim bodyBottom As SKColor = SKColors.Transparent

            If fillStrength > 0.0F Then
                ResolveMenuRowBodyFill(
                    itemSurface:=itemSurface,
                    appTheme:=appTheme,
                    state:=effectiveState,
                    fillColor:=colors.Fill,
                    fillStrength:=fillStrength,
                    fillTop:=bodyTop,
                    fillBottom:=bodyBottom
                )
            End If

            If bodyTop.Alpha > 0 OrElse bodyBottom.Alpha > 0 Then
                Using fillShader As SKShader = SKShader.CreateLinearGradient(
                    New SKPoint(baseR.Left, baseR.Top),
                    New SKPoint(baseR.Left, baseR.Bottom),
                    New SKColor() {
                        bodyTop,
                        ColorMath.Mix(bodyTop, bodyBottom, 0.52F),
                        bodyBottom
                    },
                    New Single() {0.0F, 0.52F, 1.0F},
                    SKShaderTileMode.Clamp
                )
                    Using fill As New SKPaint With {
                        .IsAntialias = True,
                        .IsDither = True,
                        .Style = SKPaintStyle.Fill,
                        .Shader = fillShader,
                        .Color = SKColors.White,
                        .BlendMode = SKBlendMode.SrcOver
                    }
                        Dim rrBody As New SKRoundRect()

                        rrBody.SetRectRadii(
                            rect:=baseR,
                            radii:=New SKPoint() {
                                New SKPoint(tl, tl),
                                New SKPoint(tr, tr),
                                New SKPoint(br, br),
                                New SKPoint(bl, bl)
                            }
                        )

                        canvas.DrawRoundRect(rrBody, fill)
                    End Using
                End Using
            End If

            DrawMenuRowRim(
                canvas:=canvas,
                dpi:=dpi,
                baseR:=baseR,
                tl:=tl,
                tr:=tr,
                br:=br,
                bl:=bl,
                itemSurface:=itemSurface,
                state:=effectiveState,
                ringColor:=colors.Ring,
                ringStrength:=ringStrength
            )
        End Sub

        Private Shared Sub ResolveMenuRowBodyFill(itemSurface As MASMenuItemSurfaceTheme,
                                                  appTheme As IMASAppTheme,
                                                  state As MASInteractionState,
                                                  fillColor As SKColor,
                                                  fillStrength As Single,
                                                  ByRef fillTop As SKColor,
                                                  ByRef fillBottom As SKColor)

            fillTop = SKColors.Transparent
            fillBottom = SKColors.Transparent

            If appTheme Is Nothing Then Return
            If fillColor.Alpha = 0 Then Return

            Dim surfaceTop As SKColor = MASAppThemeContracts.Foundation(appTheme).Surface.SurfaceTop
            Dim surfaceMid As SKColor = MASAppThemeContracts.Foundation(appTheme).Surface.SurfaceMid
            Dim surfaceBot As SKColor = MASAppThemeContracts.Foundation(appTheme).Surface.SurfaceBot

            Select Case state
                Case MASInteractionState.Hovered
                    fillTop = ColorMath.Mix(fillColor, surfaceTop, Clamp01(0.18F - (fillStrength * 0.08F)))
                    fillBottom = ColorMath.Mix(fillColor, surfaceMid, Clamp01(0.14F - (fillStrength * 0.06F)))

                Case MASInteractionState.Selected
                    fillTop = ColorMath.Mix(fillColor, surfaceTop, Clamp01(0.12F - (fillStrength * 0.06F)))
                    fillBottom = ColorMath.Mix(fillColor, surfaceMid, Clamp01(0.1F - (fillStrength * 0.05F)))

                Case MASInteractionState.Pressed
                    fillTop = ColorMath.Mix(fillColor, surfaceMid, Clamp01(0.2F - (fillStrength * 0.05F)))
                    fillBottom = ColorMath.Mix(fillColor, surfaceBot, Clamp01(0.18F - (fillStrength * 0.05F)))

                Case MASInteractionState.Focused
                    fillTop = ColorMath.Mix(fillColor, surfaceTop, Clamp01(0.16F - (fillStrength * 0.07F)))
                    fillBottom = ColorMath.Mix(fillColor, surfaceMid, Clamp01(0.13F - (fillStrength * 0.05F)))

                Case MASInteractionState.Disabled
                    fillTop = ColorMath.Mix(fillColor, surfaceTop, Clamp01(0.28F - (fillStrength * 0.06F)))
                    fillBottom = ColorMath.Mix(fillColor, surfaceMid, Clamp01(0.24F - (fillStrength * 0.05F)))
            End Select
        End Sub

        Private Shared Sub DrawMenuRowRim(canvas As SKCanvas,
                                          dpi As Single,
                                          baseR As SKRect,
                                          tl As Single,
                                          tr As Single,
                                          br As Single,
                                          bl As Single,
                                          itemSurface As MASMenuItemSurfaceTheme,
                                          state As MASInteractionState,
                                          ringColor As SKColor,
                                          ringStrength As Single)

            If canvas Is Nothing Then Return
            If baseR.Width <= 1.0F OrElse baseR.Height <= 1.0F Then Return
            If ringStrength <= 0.0F Then Return
            If ringColor.Alpha = 0 Then Return

            Dim rim As SKColor =
                ColorMath.Mix(itemSurface.Border, ringColor, Clamp01(0.56F * ringStrength))

            Dim alpha As Byte

            Select Case state
                Case MASInteractionState.Hovered
                    alpha = ClampByte(CInt(Math.Round(28.0F * ringStrength)))

                Case MASInteractionState.Selected
                    alpha = ClampByte(CInt(Math.Round(38.0F * ringStrength)))

                Case MASInteractionState.Pressed
                    alpha = ClampByte(CInt(Math.Round(18.0F * ringStrength)))

                Case MASInteractionState.Focused
                    alpha = ClampByte(CInt(Math.Round(30.0F * ringStrength)))

                Case MASInteractionState.Disabled
                    alpha = ClampByte(CInt(Math.Round(14.0F * ringStrength)))

                Case Else
                    Return
            End Select

            Dim strokeW As Single = Math.Max(1.0F, 0.95F * dpi)

            Using p As New SKPaint With {
                .IsAntialias = True,
                .IsDither = False,
                .Style = SKPaintStyle.Stroke,
                .StrokeJoin = SKStrokeJoin.Round,
                .StrokeCap = SKStrokeCap.Round,
                .StrokeWidth = strokeW,
                .Color = rim.WithAlpha(alpha)
            }
                Dim rr As New SKRoundRect()

                rr.SetRectRadii(
                    rect:=PixelSnap.InsetAll(baseR, strokeW * 0.5F),
                    radii:=New SKPoint() {
                        New SKPoint(tl, tl),
                        New SKPoint(tr, tr),
                        New SKPoint(br, br),
                        New SKPoint(bl, bl)
                    }
                )

                canvas.DrawRoundRect(rr, p)
            End Using
        End Sub

        Private Shared Function ResolveMenuInteractionColors(st As MASInteractionState,
                                                             theme As IMASAppTheme) As MenuInteractionColors

            Dim c As New MenuInteractionColors With {
                .Fill = SKColors.Transparent,
                .Ring = SKColors.Transparent
            }

            If theme Is Nothing OrElse MASAppThemeContracts.Foundation(theme).Interaction Is Nothing Then Return c

            Select Case st
                Case MASInteractionState.Hovered
                    If MASAppThemeContracts.Foundation(theme).Interaction.Hover.Fill.Enabled Then c.Fill = MASAppThemeContracts.Foundation(theme).Interaction.Hover.Fill.Color
                    If MASAppThemeContracts.Foundation(theme).Interaction.Hover.Ring.Enabled Then c.Ring = MASAppThemeContracts.Foundation(theme).Interaction.Hover.Ring.Color

                Case MASInteractionState.Pressed
                    If MASAppThemeContracts.Foundation(theme).Interaction.Pressed.Fill.Enabled Then c.Fill = MASAppThemeContracts.Foundation(theme).Interaction.Pressed.Fill.Color
                    If MASAppThemeContracts.Foundation(theme).Interaction.Pressed.Ring.Enabled Then c.Ring = MASAppThemeContracts.Foundation(theme).Interaction.Pressed.Ring.Color

                Case MASInteractionState.Selected
                    If MASAppThemeContracts.Foundation(theme).Interaction.Selected.Fill.Enabled Then c.Fill = MASAppThemeContracts.Foundation(theme).Interaction.Selected.Fill.Color
                    If MASAppThemeContracts.Foundation(theme).Interaction.Selected.Ring.Enabled Then c.Ring = MASAppThemeContracts.Foundation(theme).Interaction.Selected.Ring.Color

                Case MASInteractionState.Focused
                    If MASAppThemeContracts.Foundation(theme).Interaction.Focused.Fill.Enabled Then c.Fill = MASAppThemeContracts.Foundation(theme).Interaction.Focused.Fill.Color
                    If MASAppThemeContracts.Foundation(theme).Interaction.Focused.Ring.Enabled Then c.Ring = MASAppThemeContracts.Foundation(theme).Interaction.Focused.Ring.Color

                Case MASInteractionState.Disabled
                    If MASAppThemeContracts.Foundation(theme).Interaction.DisabledFill.Enabled Then c.Fill = MASAppThemeContracts.Foundation(theme).Interaction.DisabledFill.Color
                    c.Ring = MASAppThemeContracts.Foundation(theme).Semantics.Disabled
            End Select

            Return c
        End Function

        Private Shared Function ResolveFillStrength(st As MASInteractionState) As Single
            Return MASInteractionStateVisualResolver.ResolveFillStrength(CommonEnums.ControlKind.Menu, st)
        End Function

        Private Shared Function ResolveRingStrength(st As MASInteractionState) As Single
            Return MASInteractionStateVisualResolver.ResolveRingStrength(CommonEnums.ControlKind.Menu, st)
        End Function

        Private Shared Function Clamp01(v As Single) As Single
            If Single.IsNaN(v) OrElse Single.IsInfinity(v) Then Return 0.0F
            If v < 0.0F Then Return 0.0F
            If v > 1.0F Then Return 1.0F
            Return v
        End Function

        Private Shared Function ClampByte(v As Integer) As Byte
            If v < 0 Then Return 0
            If v > 255 Then Return 255
            Return CByte(v)
        End Function

    End Class

End Namespace