Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.PropertyGrid
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp
Imports Nexamas.UI.General

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Friend-owned renderer for MASPropertyGrid rows, categories, and value cells.
    ''' </summary>
    Friend NotInheritable Class MASPropertyGridRenderer

        Friend Sub Draw(canvas As SKCanvas,
                        ctx As MASThemeContext,
                        boundsPx As SKRect,
                        title As String,
                        rows As IReadOnlyList(Of MASPropertyGridRenderRow),
                        selectedIndex As Integer,
                        hoverIndex As Integer,
                        contract As MASResolvedComponentContract,
                        Optional metricDpi As Single = 0.0F,
                        Optional selectionMotionActive As Boolean = False,
                        Optional selectionMotionFromIndex As Integer = -1,
                        Optional selectionMotionToIndex As Integer = -1,
                        Optional selectionMotionProgress As Single = 1.0F)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            Dim scope As MASContractRenderScope =
                MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASPropertyGrid)(
                    NameOf(MASPropertyGridRenderer),
                    contract,
                    MASRenderLayer.Surface,
                    MASRenderLayer.Text,
                    MASRenderLayer.Hover,
                    MASRenderLayer.Pressed,
                    MASRenderLayer.Focus)

            scope.RequireAnyLayer(
                MASRenderLayer.Surface,
                MASRenderLayer.Text,
                MASRenderLayer.Hover,
                MASRenderLayer.Pressed,
                MASRenderLayer.Focus)

            Dim dpi As Single = If(metricDpi > 0.0F, PixelSnap.SafeDpi(metricDpi), PixelSnap.SafeDpi(ctx.Dpi))
            Dim surfaceRect As SKRect = PixelSnap.SnapRectHalf(boundsPx)
            DrawSurface(canvas, ctx, surfaceRect, dpi)
            DrawTitle(canvas, ctx, surfaceRect, title, dpi)

            Dim gridRect As SKRect = MASPropertyGridTokens.ResolveGridRect(surfaceRect, dpi)
            If gridRect.Width <= 1.0F OrElse gridRect.Height <= 1.0F Then Return

            canvas.Save()
            canvas.ClipRect(gridRect)

            If rows Is Nothing OrElse rows.Count = 0 Then
                DrawEmptyState(canvas, ctx, gridRect, dpi)
            Else
                DrawRows(canvas, ctx, gridRect, rows, selectedIndex, hoverIndex, dpi, selectionMotionActive, selectionMotionFromIndex, selectionMotionToIndex, selectionMotionProgress)
            End If

            canvas.Restore()
        End Sub

        Private Shared Sub DrawSurface(canvas As SKCanvas, ctx As MASThemeContext, rect As SKRect, dpi As Single)
            Dim radius As Single = Math.Max(1.0F, MASPropertyGridTokens.ToPx(MASPropertyGridTokens.SurfaceRadiusDip, dpi))

            Dim surfaceMaterialDrawn As Boolean = MASSurfaceMaterialComponentGateway.TryDrawCardViewportSurface(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=rect,
                materialKey:=MASSurfaceMaterialIds.Auto,
                dpi:=dpi,
                cornerRadiusPx:=radius,
                surfaceStrengthLevel:=3,
                borderStrengthLevel:=2,
                frameStrength:=MASSurfaceFrameStrength.Level3)

            If surfaceMaterialDrawn Then Return

            Dim frame As IMASFrameSurfaceTheme = ResolveFrameTheme(ctx)
            Dim fillTop As SKColor = If(frame IsNot Nothing, frame.SurfaceTop, New SKColor(248, 250, 252, 255))
            Dim fillBottom As SKColor = If(frame IsNot Nothing, frame.SurfaceBot, New SKColor(235, 240, 248, 255))
            Dim border As SKColor = If(frame IsNot Nothing, frame.BorderHairline, New SKColor(190, 200, 214, 160))

            Using shader As SKShader = SKShader.CreateLinearGradient(
                New SKPoint(rect.Left, rect.Top),
                New SKPoint(rect.Left, rect.Bottom),
                New SKColor() {fillTop, fillBottom},
                Nothing,
                SKShaderTileMode.Clamp)

                Using fillPaint As New SKPaint() With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Shader = shader},
                    borderPaint As New SKPaint() With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .Color = border, .StrokeWidth = Math.Max(1.0F, dpi)}

                    canvas.DrawRoundRect(rect, radius, radius, fillPaint)
                    canvas.DrawRoundRect(rect, radius, radius, borderPaint)
                End Using
            End Using
        End Sub

        Private Shared Sub DrawTitle(canvas As SKCanvas, ctx As MASThemeContext, surfaceRect As SKRect, title As String, dpi As Single)
            If ctx.Typography Is Nothing Then Return
            Dim safeTitle As String = If(title, String.Empty).Trim()
            If safeTitle.Length = 0 Then safeTitle = "Properties"

            Dim paint As SKPaint = ctx.Typography.GetPaint(MASTypography.MASTextStyle.Title, ResolvePrimaryText(ctx))
            If paint Is Nothing Then Return

            Dim x As Single = surfaceRect.Left + MASPropertyGridTokens.ToPx(MASPropertyGridTokens.ContentInsetDip, dpi)
            Dim baseline As Single = surfaceRect.Top + MASPropertyGridTokens.ToPx(25.0F, dpi)
            MASShapedText.Draw(canvas, safeTitle, x, baseline, paint)
        End Sub

        Private Shared Sub DrawEmptyState(canvas As SKCanvas, ctx As MASThemeContext, gridRect As SKRect, dpi As Single)
            If ctx.Typography Is Nothing Then Return
            Dim paint As SKPaint = ctx.Typography.GetPaint(MASTypography.MASTextStyle.Body, ResolveMutedText(ctx))
            If paint Is Nothing Then Return
            Dim text As String = "No properties"
            Dim width As Single = MASShapedText.MeasureWidth(text, paint)
            MASShapedText.Draw(canvas, text, gridRect.Left + Math.Max(0.0F, (gridRect.Width - width) / 2.0F), gridRect.Top + (gridRect.Height / 2.0F), paint)
        End Sub

        Private Shared Sub DrawRows(canvas As SKCanvas,
                                    ctx As MASThemeContext,
                                    gridRect As SKRect,
                                    rows As IReadOnlyList(Of MASPropertyGridRenderRow),
                                    selectedIndex As Integer,
                                    hoverIndex As Integer,
                                    dpi As Single,
                                    selectionMotionActive As Boolean,
                                    selectionMotionFromIndex As Integer,
                                    selectionMotionToIndex As Integer,
                                    selectionMotionProgress As Single)
            Dim frame As IMASFrameSurfaceTheme = ResolveFrameTheme(ctx)
            Dim separator As SKColor = If(frame IsNot Nothing, frame.SeparatorHairline, New SKColor(180, 190, 204, 130))
            Dim selectedFill As SKColor = ResolveAccent(ctx).WithAlpha(38)
            Dim hoverFill As SKColor = ResolveAccent(ctx).WithAlpha(22)
            Dim categoryFill As SKColor = If(frame IsNot Nothing, frame.SurfaceBot.WithAlpha(210), New SKColor(224, 232, 242, 210))
            Dim textColor As SKColor = ResolvePrimaryText(ctx)
            Dim mutedText As SKColor = ResolveMutedText(ctx)

            Using linePaint As New SKPaint() With {.IsAntialias = False, .Style = SKPaintStyle.Stroke, .Color = separator, .StrokeWidth = Math.Max(1.0F, dpi)},
                fillPaint As New SKPaint() With {.IsAntialias = False, .Style = SKPaintStyle.Fill}

                Dim namePaint As SKPaint = If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Body, textColor), Nothing)
                Dim valuePaint As SKPaint = If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Body, mutedText), Nothing)
                Dim categoryPaint As SKPaint = If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Small, textColor), Nothing)
                If namePaint Is Nothing OrElse valuePaint Is Nothing OrElse categoryPaint Is Nothing Then Return

                For Each row As MASPropertyGridRenderRow In rows
                    If row Is Nothing Then Continue For
                    If row.RowRectPx.Bottom < gridRect.Top OrElse row.RowRectPx.Top > gridRect.Bottom Then Continue For

                    If row.IsCategory Then
                        fillPaint.Color = categoryFill
                        canvas.DrawRect(PixelSnap.SnapRectHalf(row.RowRectPx), fillPaint)
                        DrawCategoryRow(canvas, ctx, row, categoryPaint, mutedText, dpi)
                    Else
                        If row.EntryIndex = selectedIndex AndAlso Not IsSelectionMotionEndpoint(row.EntryIndex, selectionMotionActive, selectionMotionFromIndex, selectionMotionToIndex) Then
                            fillPaint.Color = selectedFill
                            canvas.DrawRect(PixelSnap.SnapRectHalf(row.RowRectPx), fillPaint)
                        ElseIf row.EntryIndex = hoverIndex Then
                            fillPaint.Color = hoverFill
                            canvas.DrawRect(PixelSnap.SnapRectHalf(row.RowRectPx), fillPaint)
                        End If

                        DrawClippedText(canvas, row.Name, row.NameRectPx, namePaint, dpi)
                        DrawValueCell(canvas, ctx, row, row.ValueRectPx, valuePaint, mutedText, ResolveAccent(ctx), dpi)
                        canvas.DrawLine(row.ValueRectPx.Left, row.RowRectPx.Top, row.ValueRectPx.Left, row.RowRectPx.Bottom, linePaint)
                    End If

                    canvas.DrawLine(row.RowRectPx.Left, row.RowRectPx.Bottom, row.RowRectPx.Right, row.RowRectPx.Bottom, linePaint)
                Next

                DrawSelectionMotionOverlay(canvas, rows, selectionMotionActive, selectionMotionFromIndex, selectionMotionToIndex, selectionMotionProgress, selectedFill, dpi)
            End Using
        End Sub

        Private Shared Function IsSelectionMotionEndpoint(entryIndex As Integer,
                                                         motionActive As Boolean,
                                                         fromIndex As Integer,
                                                         toIndex As Integer) As Boolean
            If Not motionActive Then Return False
            Return entryIndex = fromIndex OrElse entryIndex = toIndex
        End Function

        Private Shared Function ResolveMotionRowRect(rows As IReadOnlyList(Of MASPropertyGridRenderRow),
                                                     entryIndex As Integer) As SKRect
            If rows Is Nothing OrElse entryIndex < 0 Then Return SKRect.Empty
            For Each row As MASPropertyGridRenderRow In rows
                If row IsNot Nothing AndAlso Not row.IsCategory AndAlso row.EntryIndex = entryIndex Then Return row.RowRectPx
            Next
            Return SKRect.Empty
        End Function

        Private Shared Sub DrawSelectionMotionOverlay(canvas As SKCanvas,
                                                      rows As IReadOnlyList(Of MASPropertyGridRenderRow),
                                                      motionActive As Boolean,
                                                      fromIndex As Integer,
                                                      toIndex As Integer,
                                                      progressValue As Single,
                                                      selectedFill As SKColor,
                                                      dpi As Single)
            If canvas Is Nothing OrElse Not motionActive Then Return
            Dim fromRect As SKRect = ResolveMotionRowRect(rows, fromIndex)
            Dim toRect As SKRect = ResolveMotionRowRect(rows, toIndex)
            If fromRect.IsEmpty OrElse toRect.IsEmpty Then Return

            Dim progress As Single = Math.Max(0.0F, Math.Min(1.0F, progressValue))
            Dim rect As New SKRect(fromRect.Left + (toRect.Left - fromRect.Left) * progress,
                                   fromRect.Top + (toRect.Top - fromRect.Top) * progress,
                                   fromRect.Right + (toRect.Right - fromRect.Right) * progress,
                                   fromRect.Bottom + (toRect.Bottom - fromRect.Bottom) * progress)
            Using fill As New SKPaint With {.IsAntialias = False, .Style = SKPaintStyle.Fill, .Color = selectedFill}
                canvas.DrawRect(PixelSnap.SnapRectHalf(rect), fill)
            End Using
        End Sub

        Private Shared Sub DrawCategoryRow(canvas As SKCanvas,
                                          ctx As MASThemeContext,
                                          row As MASPropertyGridRenderRow,
                                          paint As SKPaint,
                                          mutedText As SKColor,
                                          dpi As Single)
            Dim captionRect As SKRect = row.NameRectPx
            DrawClippedText(canvas, row.Name, captionRect, paint, dpi)
            Dim countRect As New SKRect(row.RowRectPx.Right - MASPropertyGridTokens.ToPx(72.0F, dpi), row.RowRectPx.Top, row.RowRectPx.Right, row.RowRectPx.Bottom)
            Dim countPaint As SKPaint = If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Micro, mutedText), Nothing)
            If countPaint IsNot Nothing Then DrawClippedText(canvas, If(row.IsCategoryCollapsed, "collapsed", "expanded"), countRect, countPaint, dpi)
        End Sub

        Private Shared Sub DrawValueCell(canvas As SKCanvas,
                                         ctx As MASThemeContext,
                                         row As MASPropertyGridRenderRow,
                                         valueRect As SKRect,
                                         valuePaint As SKPaint,
                                         mutedText As SKColor,
                                         accent As SKColor,
                                         dpi As Single)
            If row.KindName = "BooleanValue" Then
                DrawBooleanValue(canvas, ctx, row, valueRect, mutedText, accent, dpi)
                Return
            End If

            Dim kindWidth As Single = Math.Min(MASPropertyGridTokens.ToPx(72.0F, dpi), valueRect.Width * 0.34F)
            Dim kindRect As New SKRect(valueRect.Right - kindWidth - MASPropertyGridTokens.ToPx(6.0F, dpi), valueRect.Top + MASPropertyGridTokens.ToPx(5.0F, dpi), valueRect.Right - MASPropertyGridTokens.ToPx(6.0F, dpi), valueRect.Bottom - MASPropertyGridTokens.ToPx(5.0F, dpi))
            Dim textRect As New SKRect(valueRect.Left, valueRect.Top, kindRect.Left - MASPropertyGridTokens.ToPx(4.0F, dpi), valueRect.Bottom)
            DrawClippedText(canvas, row.ValueText, textRect, valuePaint, dpi)
            DrawKindPill(canvas, ctx, row.KindName, kindRect, If(row.IsEditable, accent, mutedText), dpi)
            If Not row.IsEditable Then
                Dim lockRect As New SKRect(kindRect.Left - MASPropertyGridTokens.ToPx(60.0F, dpi), kindRect.Top, kindRect.Left - MASPropertyGridTokens.ToPx(4.0F, dpi), kindRect.Bottom)
                DrawKindPill(canvas, ctx, "read-only", lockRect, mutedText, dpi)
            End If
        End Sub

        Private Shared Sub DrawBooleanValue(canvas As SKCanvas,
                                            ctx As MASThemeContext,
                                            row As MASPropertyGridRenderRow,
                                            valueRect As SKRect,
                                            mutedText As SKColor,
                                            accent As SKColor,
                                            dpi As Single)
            Dim switchWidth As Single = MASPropertyGridTokens.ToPx(42.0F, dpi)
            Dim switchHeight As Single = MASPropertyGridTokens.ToPx(20.0F, dpi)
            Dim switchRect As New SKRect(valueRect.Right - switchWidth - MASPropertyGridTokens.ToPx(8.0F, dpi), valueRect.MidY - switchHeight * 0.5F, valueRect.Right - MASPropertyGridTokens.ToPx(8.0F, dpi), valueRect.MidY + switchHeight * 0.5F)
            Dim isOn As Boolean = String.Equals(row.ValueText, "True", StringComparison.OrdinalIgnoreCase)
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = If(isOn AndAlso row.IsEditable, accent.WithAlpha(88), mutedText.WithAlpha(42))}
                canvas.DrawRoundRect(switchRect, switchHeight * 0.5F, switchHeight * 0.5F, fill)
            End Using
            Dim knobSize As Single = switchHeight - MASPropertyGridTokens.ToPx(4.0F, dpi)
            Dim knobLeft As Single = If(isOn, switchRect.Right - knobSize - MASPropertyGridTokens.ToPx(2.0F, dpi), switchRect.Left + MASPropertyGridTokens.ToPx(2.0F, dpi))
            Using knob As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = If(row.IsEditable, accent.WithAlpha(210), mutedText.WithAlpha(150))}
                canvas.DrawOval(New SKRect(knobLeft, switchRect.MidY - knobSize * 0.5F, knobLeft + knobSize, switchRect.MidY + knobSize * 0.5F), knob)
            End Using
            Dim textRect As New SKRect(valueRect.Left, valueRect.Top, switchRect.Left - MASPropertyGridTokens.ToPx(8.0F, dpi), valueRect.Bottom)
            DrawClippedText(canvas, If(isOn, "On", "Off"), textRect, If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Body, mutedText), Nothing), dpi)
        End Sub

        Private Shared Sub DrawKindPill(canvas As SKCanvas,
                                        ctx As MASThemeContext,
                                        text As String,
                                        rect As SKRect,
                                        color As SKColor,
                                        dpi As Single)
            If rect.Width <= 4.0F OrElse rect.Height <= 4.0F Then Return
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = color.WithAlpha(28)}
                canvas.DrawRoundRect(rect, rect.Height * 0.5F, rect.Height * 0.5F, fill)
            End Using
            Dim paint As SKPaint = If(ctx.Typography IsNot Nothing, ctx.Typography.GetPaint(MASTypography.MASTextStyle.Micro, color.WithAlpha(180)), Nothing)
            If paint IsNot Nothing Then DrawClippedText(canvas, NormalizeKindText(text), rect, paint, dpi)
        End Sub

        Private Shared Function NormalizeKindText(value As String) As String
            Dim text As String = If(value, String.Empty)
            Select Case text
                Case "BooleanValue"
                    Return "bool"
                Case "DateTimeText"
                    Return "date"
                Case "EnumValue"
                    Return "enum"
                Case "ObjectText"
                    Return "object"
                Case Else
                    Return text.ToLowerInvariant()
            End Select
        End Function

        Private Shared Sub DrawClippedText(canvas As SKCanvas, text As String, rect As SKRect, paint As SKPaint, dpi As Single)
            If canvas Is Nothing OrElse paint Is Nothing OrElse rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            Dim safeText As String = If(text, String.Empty)
            Dim baseline As Single = rect.Top + Math.Max(MASPropertyGridTokens.ToPx(18.0F, dpi), (rect.Height / 2.0F) + MASPropertyGridTokens.ToPx(5.0F, dpi))

            canvas.Save()
            canvas.ClipRect(rect)
            MASShapedText.Draw(canvas, safeText, rect.Left + MASPropertyGridTokens.ToPx(MASPropertyGridTokens.CellInsetDip, dpi), baseline, paint)
            canvas.Restore()
        End Sub

        Private Shared Function ResolveFrameTheme(ctx As MASThemeContext) As IMASFrameSurfaceTheme
            If ctx Is Nothing OrElse ctx.Theme Is Nothing Then Return Nothing
            Return MASAppThemeContracts.Families(ctx.Theme).FrameSurface
        End Function

        Private Shared Function ResolvePrimaryText(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.PrimaryText
            Return New SKColor(38, 46, 60, 255)
        End Function

        Private Shared Function ResolveMutedText(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.TextColorTheme IsNot Nothing Then Return ctx.TextColorTheme.MutedText
            Return New SKColor(88, 98, 114, 255)
        End Function

        Private Shared Function ResolveAccent(ctx As MASThemeContext) As SKColor
            If ctx IsNot Nothing AndAlso ctx.BrandTheme IsNot Nothing Then Return ctx.BrandTheme.BrandPrimary
            Return New SKColor(70, 128, 220, 255)
        End Function

    End Class

End Namespace
