Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
Imports Nexamas.UI.Theming
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Values
Imports Nexamas.UI.Architecture
Imports SkiaSharp

Namespace Nexamas.UI.Rendering

    Partial Friend NotInheritable Class MASToastRenderer
        Friend Sub Render(canvas As SKCanvas,
                          ctx As MASThemeContext,
                          items As IList(Of MASToastItem),
                          layouts As Dictionary(Of Integer, MASToastLayoutInfo))

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

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

            For i As Integer = 0 To items.Count - 1
                Dim item As MASToastItem = items(i)
                If item Is Nothing Then Continue For

                Dim li As MASToastLayoutInfo = Nothing

                If Not layouts.TryGetValue(item.Id, li) Then Continue For

                RenderSingle(
                    canvas:=canvas,
                    ctx:=ctx,
                    dpi:=dpi,
                    item:=item,
                    layout:=li)
            Next
        End Sub


        Private Sub RenderSingle(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 dpi As Single,
                                 item As MASToastItem,
                                 layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If ctx.Theme Is Nothing OrElse MASAppThemeContracts.Families(ctx.Theme).FrameSurface Is Nothing Then Return

            Dim scope As MASContractRenderScope =
  MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASToastFloatContract)(
        NameOf(MASToastRenderer),
        _toastContract,
        MASRenderLayer.Surface,
        MASRenderLayer.FloatingChrome,
        MASRenderLayer.Text)

            scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.FloatingChrome,
    MASRenderLayer.Text)

            Dim radius As Single =
    MASRadiusResolver.ResolveSurfacePx(
        contract:=_toastContract,
        rPx:=layout.PanelRectPx,
        dpi:=dpi)

            Dim surfaceMaterialDrawn As Boolean = MASSurfaceMaterialComponentGateway.TryDrawFloatingToastSurface(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=layout.PanelRectPx,
                materialKey:=item.SurfaceMaterialKey,
                dpi:=dpi,
                cornerRadiusPx:=radius,
                opacity:=layout.Opacity,
                surfaceStrengthLevel:=6,
                borderStrengthLevel:=6,
                frameStrength:=MASSurfaceFrameStrength.Level6)

            If Not surfaceMaterialDrawn Then
                If Not _frameSurface.TryDrawSwitchableFloatingToastSurface(
                    canvas:=canvas,
                    ctx:=ctx,
                    r:=layout.PanelRectPx,
                    radius:=radius,
                    dpi:=dpi,
                    surfaceStrength:=MASSurfaceFrameStrength.Level6,
                    borderStrength:=MASSurfaceFrameStrength.Level6,
                    opacity:=layout.Opacity) Then

                    _frameSurface.Draw(
                        canvas:=canvas,
                        r:=layout.PanelRectPx,
                        radius:=radius,
                        dpi:=dpi,
                        surfaceTheme:=MASAppThemeContracts.Families(ctx.Theme).FrameSurface,
                        surfaceStrength:=MASSurfaceFrameStrength.Level6,
                        borderStrength:=MASSurfaceFrameStrength.Level6,
                        opacity:=layout.Opacity)
                End If
            End If

            DrawAccentStrip(canvas, ctx, item.Tone, layout, radius)
            DrawText(canvas, ctx, item, layout)
            DrawAction(canvas, ctx, item, layout)
            DrawUndo(canvas, ctx, item, layout)
            DrawClose(canvas, ctx, item, layout)
            DrawLifetimeBar(canvas, ctx, item.Tone, item.ShowLifetimeBar, layout)
        End Sub

        Private Sub DrawAccentStrip(canvas As SKCanvas,
                                    ctx As MASThemeContext,
                                    tone As MASToastTone,
                                    layout As MASToastLayoutInfo,
                                    radius As Single)

            If canvas Is Nothing Then Return
            If layout.AccentRectPx.Width <= 1.0F Then Return

            Dim accent As SKColor = ResolveTonePalette(ctx, tone).Accent

            Dim topCol As SKColor =
                ColorMath.WithAlpha(
                    ColorMath.Mix(accent, SKColors.White, 0.18F),
                    ToAlpha(144.0F * layout.Opacity))

            Dim midCol As SKColor =
                ColorMath.WithAlpha(accent, ToAlpha(128.0F * layout.Opacity))

            Dim botCol As SKColor =
                ColorMath.WithAlpha(
                    ColorMath.Mix(accent, SKColors.Black, 0.12F),
                    ToAlpha(112.0F * layout.Opacity))

            canvas.Save()

            Try
                _accentClipPath.Reset()
                _accentClipPath.AddRoundRect(layout.PanelRectPx, radius, radius)
                canvas.ClipPath(_accentClipPath, SKClipOperation.Intersect, True)

                _accentGradientColors(0) = topCol
                _accentGradientColors(1) = midCol
                _accentGradientColors(2) = botCol

                Using sh As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(layout.AccentRectPx.Left, layout.AccentRectPx.Top),
                        New SKPoint(layout.AccentRectPx.Left, layout.AccentRectPx.Bottom),
                        _accentGradientColors,
                        _accentGradientPositions,
                        SKShaderTileMode.Clamp)

                    Try
                        _accentPaint.Shader = sh
                        canvas.DrawRect(layout.AccentRectPx, _accentPaint)
                    Finally
                        _accentPaint.Shader = Nothing
                    End Try
                End Using

            Finally
                canvas.Restore()
            End Try
        End Sub

        Private Sub DrawText(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             item As MASToastItem,
                             layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return

            Dim titleBase As SKColor = ctx.TextColorTheme.PrimaryText
            Dim messageBase As SKColor = ctx.TextColorTheme.SecondaryText

            If ctx.ToastTheme IsNot Nothing AndAlso ctx.ToastTheme.Text IsNot Nothing Then
                titleBase = ctx.ToastTheme.Text.Title
                messageBase = ctx.ToastTheme.Text.Message
            End If

            Dim titleColor As SKColor =
                ColorMath.WithAlpha(titleBase, ToAlpha(255.0F * layout.Opacity))

            Dim bodyColor As SKColor =
                ColorMath.WithAlpha(messageBase, ToAlpha(236.0F * layout.Opacity))

            If layout.TitleRectPx.Width > 1.0F AndAlso Not String.IsNullOrWhiteSpace(item.Title) Then
                DrawWrappedText(
                    canvas:=canvas,
                    ctx:=ctx,
                    text:=item.Title,
                    bounds:=layout.TitleRectPx,
                    style:=MASTypography.MASTextStyle.Title,
                    color:=titleColor,
                    maxLines:=2)
            End If

            If layout.MessageRectPx.Width > 1.0F AndAlso Not String.IsNullOrWhiteSpace(item.Message) Then
                DrawWrappedText(
                    canvas:=canvas,
                    ctx:=ctx,
                    text:=item.Message,
                    bounds:=layout.MessageRectPx,
                    style:=MASTypography.MASTextStyle.Body,
                    color:=bodyColor,
                    maxLines:=ToastTokens.MaxBodyLines)
            End If
        End Sub

        Private Sub DrawAction(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               item As MASToastItem,
                               layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If item Is Nothing Then Return
            If String.IsNullOrWhiteSpace(item.ActionText) Then Return
            If layout.ActionRectPx.Width <= 1.0F OrElse layout.ActionRectPx.Height <= 1.0F Then Return

            Dim spec As New MASButtonSpec(
                text:=item.ActionText,
                personality:=MASButtonPersonality.Tal,
                intent:=ResolveActionIntent(item),
                isDefault:=False,
                isSelected:=False,
                isEnabled:=Not item.IsClosing)

            Dim state As MASButtonState =
                MASButtonState.CreateDefault(Not item.IsClosing)

            state.IsHovered = item.IsActionHovered
            state.IsPressed = item.IsActionPressed
            state.IsFocused = False

            _actionButton.RenderSingle(
    canvas:=canvas,
    ctx:=ctx,
    rect:=layout.ActionRectPx,
    dpi:=ctx.Dpi,
    spec:=spec,
    state:=state,
    contract:=_buttonContract)
        End Sub

        Private Shared Function ResolveActionIntent(item As MASToastItem) As MASButtonIntent
            If item Is Nothing Then Return MASButtonIntent.Primary

            Select Case item.Tone
                Case MASToastTone.Danger
                    Return MASButtonIntent.Danger
                Case Else
                    Return MASButtonIntent.Primary
            End Select
        End Function

        ''' <summary>Resolves the single compact glyph button size used by both Undo and Close.</summary>

        Private Sub DrawUndo(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             item As MASToastItem,
                             layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If item Is Nothing Then Return
            If item.UndoCallback Is Nothing Then Return
            If layout.UndoRectPx.Width <= 1.0F OrElse layout.UndoRectPx.Height <= 1.0F Then Return

            Dim state As GlyphButtonVisualState = GlyphButtonVisualState.Normal

            If item.IsClosing Then
                state = GlyphButtonVisualState.Disabled
            ElseIf item.IsUndoPressed Then
                state = GlyphButtonVisualState.Pressed
            ElseIf item.IsUndoHovered Then
                state = GlyphButtonVisualState.Hover
            End If

            DrawCompactGlyphButton(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=layout.UndoRectPx,
                state:=state,
                opacity:=layout.Opacity,
                intent:=GlyphButtonIntent.Neutral,
                iconKind:=MASIconKind.Undo)
        End Sub

        Private Sub DrawClose(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              item As MASToastItem,
                              layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If item Is Nothing Then Return
            If layout.CloseRectPx.Width <= 1.0F OrElse layout.CloseRectPx.Height <= 1.0F Then Return

            Dim state As GlyphButtonVisualState = GlyphButtonVisualState.Normal

            If item.IsClosing Then
                state = GlyphButtonVisualState.Disabled
            ElseIf item.IsClosePressed Then
                state = GlyphButtonVisualState.Pressed
            ElseIf item.IsCloseHovered Then
                state = GlyphButtonVisualState.Hover
            End If

            DrawCompactGlyphButton(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=layout.CloseRectPx,
                state:=state,
                opacity:=layout.Opacity,
                intent:=GlyphButtonIntent.Neutral,
                iconKind:=MASIconKind.Clear)
        End Sub

        ''' <summary>Draws Toast compact glyph commands through one shared renderer path.</summary>
        Private Sub DrawCompactGlyphButton(canvas As SKCanvas,
                                           ctx As MASThemeContext,
                                           boundsPx As SKRect,
                                           state As GlyphButtonVisualState,
                                           opacity As Single,
                                           intent As GlyphButtonIntent,
                                           iconKind As MASIconKind)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return
            If iconKind = MASIconKind.None Then Return

            _smallButton.Draw(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=boundsPx,
                state:=state,
                opacity:=opacity,
                intent:=intent,
                iconKind:=iconKind,
                iconStyle:=MASIconStyle.Outline,
                iconPaddingDip:=ToastTokens.ToastGlyphIconPadding)
        End Sub

        Private Sub DrawLifetimeBar(canvas As SKCanvas,
                                    ctx As MASThemeContext,
                                    tone As MASToastTone,
                                    showLifetimeBar As Boolean,
                                    layout As MASToastLayoutInfo)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If Not showLifetimeBar Then Return
            If layout.LifetimeBarRectPx.Width <= 1.0F OrElse layout.LifetimeBarRectPx.Height <= 1.0F Then Return

            Dim ratio As Single = Math.Max(0.0F, Math.Min(1.0F, layout.RemainingRatio))
            If ratio <= 0.0F Then Return

            Dim accent As SKColor = ResolveTonePalette(ctx, tone).LifetimeBar
            Dim barRect As SKRect = layout.LifetimeBarRectPx
            Dim radius As Single = barRect.Height * 0.5F

            _lifetimeTrackPaint.Color = ColorMath.WithAlpha(accent, ToAlpha(22.0F * layout.Opacity))
            canvas.DrawRoundRect(barRect, radius, radius, _lifetimeTrackPaint)

            Dim trackShineRect As SKRect = barRect
            trackShineRect.Bottom = trackShineRect.Top + Math.Max(1.0F, barRect.Height * 0.42F)

            _lifetimeTrackShinePaint.Color = ColorMath.WithAlpha(SKColors.White, ToAlpha(18.0F * layout.Opacity))
            canvas.DrawRoundRect(trackShineRect, radius, radius, _lifetimeTrackShinePaint)

            Dim fillRect As SKRect = barRect
            fillRect.Right = fillRect.Left + (barRect.Width * ratio)

            If fillRect.Width <= 0.75F Then Return

            canvas.Save()

            Try
                _lifetimeClipPath.Reset()
                _lifetimeClipPath.AddRoundRect(barRect, radius, radius)
                canvas.ClipPath(_lifetimeClipPath, SKClipOperation.Intersect, True)

                Dim fillRadius As Single = Math.Min(radius, fillRect.Width * 0.5F)

                _lifetimeFillGradientColors(0) = ColorMath.WithAlpha(ColorMath.Mix(accent, SKColors.White, 0.28F), ToAlpha(170.0F * layout.Opacity))
                _lifetimeFillGradientColors(1) = ColorMath.WithAlpha(accent, ToAlpha(142.0F * layout.Opacity))
                _lifetimeFillGradientColors(2) = ColorMath.WithAlpha(ColorMath.Mix(accent, SKColors.Black, 0.12F), ToAlpha(132.0F * layout.Opacity))

                Using fillShader As SKShader =
                    SKShader.CreateLinearGradient(
                        New SKPoint(fillRect.Left, fillRect.Top),
                        New SKPoint(fillRect.Right, fillRect.Bottom),
                        _lifetimeFillGradientColors,
                        _lifetimeFillGradientPositions,
                        SKShaderTileMode.Clamp)

                    Try
                        _lifetimeFillPaint.Shader = fillShader
                        canvas.DrawRoundRect(fillRect, fillRadius, radius, _lifetimeFillPaint)
                    Finally
                        _lifetimeFillPaint.Shader = Nothing
                    End Try
                End Using

                Dim fillShineRect As SKRect = fillRect
                fillShineRect.Bottom = fillShineRect.Top + Math.Max(1.0F, fillRect.Height * 0.44F)

                _lifetimeFillShinePaint.Color = ColorMath.WithAlpha(SKColors.White, ToAlpha(24.0F * layout.Opacity))
                canvas.DrawRoundRect(fillShineRect, Math.Min(radius, fillShineRect.Width * 0.5F), radius, _lifetimeFillShinePaint)

                If fillRect.Width > fillRect.Height * 1.6F Then
                    Dim capRadius As Single = Math.Max(0.75F, fillRect.Height * 0.22F)
                    Dim capCenter As New SKPoint(
                        fillRect.Right - (fillRect.Height * 0.46F),
                        fillRect.Top + (fillRect.Height * 0.42F))

                    _lifetimeCapPaint.Color = ColorMath.WithAlpha(SKColors.White, ToAlpha(34.0F * layout.Opacity))
                    canvas.DrawCircle(capCenter, capRadius, _lifetimeCapPaint)
                End If

            Finally
                canvas.Restore()
            End Try
        End Sub


        Private Shared Sub DrawWrappedText(canvas As SKCanvas,
                                           ctx As MASThemeContext,
                                           text As String,
                                           bounds As SKRect,
                                           style As MASTypography.MASTextStyle,
                                           color As SKColor,
                                           maxLines As Integer)

            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If bounds.Width <= 2.0F OrElse bounds.Height <= 2.0F Then Return
            If String.IsNullOrWhiteSpace(text) Then Return

            Dim p As SKPaint = ctx.Typography.GetPaint(style, color)
            If p Is Nothing Then Return

            Dim lines As List(Of String) =
                WrapLines(MASToastVisualLifecyclePolicy.NormalizeText(text, String.Empty), p, bounds.Width, maxLines)

            If lines.Count = 0 Then Return

            Dim lineH As Single = p.TextSize * MASTypography.WrapLineHeightMul
            Dim y As Single = bounds.Top - p.FontMetrics.Ascent
            Dim isRtl As Boolean = MASToastVisualLifecyclePolicy.ResolveTextIsRtl(text, String.Empty)

            For i As Integer = 0 To lines.Count - 1
                If y > bounds.Bottom + lineH Then Exit For

                Dim x As Single = bounds.Left
                If isRtl Then x = bounds.Right - MASShapedText.MeasureWidth(lines(i), p)
                MASShapedText.Draw(canvas, lines(i), x, y, p)
                y += lineH
            Next
        End Sub

        Private Shared Function ResolveTonePalette(ctx As MASThemeContext,
                                                   tone As MASToastTone) As ToastTonePaletteTheme

            If ctx IsNot Nothing AndAlso
               ctx.ToastTheme IsNot Nothing AndAlso
               ctx.ToastTheme.Tones IsNot Nothing Then

                Select Case tone
                    Case MASToastTone.Neutral
                        Return ctx.ToastTheme.Tones.Neutral
                    Case MASToastTone.Success
                        Return ctx.ToastTheme.Tones.Success
                    Case MASToastTone.Warning
                        Return ctx.ToastTheme.Tones.Warning
                    Case MASToastTone.Danger
                        Return ctx.ToastTheme.Tones.Danger
                    Case Else
                        Return ctx.ToastTheme.Tones.Info
                End Select
            End If

            If ctx Is Nothing OrElse ctx.SemanticTheme Is Nothing OrElse ctx.BrandTheme Is Nothing Then
                Return New ToastTonePaletteTheme(SKColors.Transparent, SKColors.Transparent)
            End If

            Dim accent As SKColor

            Select Case tone
                Case MASToastTone.Success
                    accent = ctx.SemanticTheme.Success
                Case MASToastTone.Warning
                    accent = ctx.SemanticTheme.Warning
                Case MASToastTone.Danger
                    accent = ctx.SemanticTheme.Danger
                Case MASToastTone.Neutral
                    accent = ctx.BrandTheme.BrandSilver
                Case Else
                    accent = ctx.SemanticTheme.Info
            End Select

            Return New ToastTonePaletteTheme(
                accent:=accent,
                lifetimeBar:=accent)
        End Function

        Private Shared Function ToAlpha(value As Single) As Byte
            Dim v As Integer = CInt(Math.Round(value))
            v = Math.Max(0, Math.Min(255, v))
            Return CByte(v)
        End Function
    End Class

End Namespace
