Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Official Nexamas UI dashboard metric card surface. It owns compact metric
    ''' presentation, value/subtitle/trend text, semantic tone, optional inline
    ''' sparkline display, RTL-aware composition, and MAS-owned premium drawing.
    ''' Metric storage, analytics collection, telemetry refresh, chart engines,
    ''' dashboard layout, provider sync, and live data polling remain outside it.
    ''' </summary>
    Public NotInheritable Class MASMetricCard
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _sparklineSamples As New List(Of Double)()
        Private _title As String = MetricCardTokens.DefaultTitle
        Private _valueText As String = MetricCardTokens.DefaultValueText
        Private _subtitle As String = String.Empty
        Private _trendText As String = String.Empty
        Private _tone As MASToastTone = MASToastTone.Info
        Private _showTrend As Boolean = True
        Private _isCompact As Boolean
        Private _contentRect As SKRect = SKRect.Empty

#End Region

#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()
        End Sub

        Public Shared Function Create(Optional title As String = Nothing,
                                      Optional valueText As String = Nothing) As MASMetricCard
            Dim control As New MASMetricCard()
            control._title = MASMetricCardPolicy.NormalizeTitle(title)
            control._valueText = MASMetricCardPolicy.NormalizeValueText(valueText)
            Return control
        End Function

#End Region

#Region "Public API"

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = MASMetricCardPolicy.NormalizeTitle(value)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return
                _title = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASMetricCard.Title")
                InvalidateVisual()
            End Set
        End Property

        Public Property ValueText As String
            Get
                Return _valueText
            End Get
            Set(value As String)
                Dim normalized As String = MASMetricCardPolicy.NormalizeValueText(value)
                If String.Equals(_valueText, normalized, StringComparison.Ordinal) Then Return
                _valueText = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASMetricCard.ValueText")
                InvalidateVisual()
            End Set
        End Property

        Public Property Subtitle As String
            Get
                Return _subtitle
            End Get
            Set(value As String)
                Dim normalized As String = MASMetricCardPolicy.NormalizeSubtitle(value)
                If String.Equals(_subtitle, normalized, StringComparison.Ordinal) Then Return
                _subtitle = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASMetricCard.Subtitle")
                InvalidateVisual()
            End Set
        End Property

        Public Property TrendText As String
            Get
                Return _trendText
            End Get
            Set(value As String)
                Dim normalized As String = MASMetricCardPolicy.NormalizeTrendText(value)
                If String.Equals(_trendText, normalized, StringComparison.Ordinal) Then Return
                _trendText = normalized
                InvalidateVisual()
            End Set
        End Property

        Public Property Tone As MASToastTone
            Get
                Return _tone
            End Get
            Set(value As MASToastTone)
                Dim normalized As MASToastTone = MASMetricCardPolicy.NormalizeTone(value)
                If _tone = normalized Then Return
                _tone = normalized
                InvalidateVisual()
            End Set
        End Property

        Public Property ShowTrend As Boolean
            Get
                Return _showTrend
            End Get
            Set(value As Boolean)
                If _showTrend = value Then Return
                _showTrend = value
                InvalidateVisual()
            End Set
        End Property

        Public Property IsCompact As Boolean
            Get
                Return _isCompact
            End Get
            Set(value As Boolean)
                If _isCompact = value Then Return
                _isCompact = value
                RequestSizeLayoutRefreshForSizeAffectingChange("MASMetricCard.IsCompact")
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property HasTrendSamples As Boolean
            Get
                Return _sparklineSamples.Count >= 2
            End Get
        End Property

        Public Function SetMetric(title As String,
                                  valueText As String,
                                  Optional subtitle As String = Nothing,
                                  Optional trendText As String = Nothing,
                                  Optional tone As MASToastTone = MASToastTone.Info) As MASMetricCard
            _title = MASMetricCardPolicy.NormalizeTitle(title)
            _valueText = MASMetricCardPolicy.NormalizeValueText(valueText)
            _subtitle = MASMetricCardPolicy.NormalizeSubtitle(subtitle)
            _trendText = MASMetricCardPolicy.NormalizeTrendText(trendText)
            _tone = MASMetricCardPolicy.NormalizeTone(tone)
            RequestSizeLayoutRefreshForSizeAffectingChange("MASMetricCard.SetMetric")
            InvalidateVisual()
            Return Me
        End Function

        Public Function SetMetricValue(value As Double,
                                       Optional suffix As String = Nothing) As MASMetricCard
            ValueText = MASMetricCardPolicy.BuildFormattedValue(value, suffix)
            Return Me
        End Function

        Public Function SetTrendSamples(samples As IEnumerable(Of Double)) As MASMetricCard
            _sparklineSamples.Clear()
            _sparklineSamples.AddRange(MASMetricCardPolicy.NormalizeSparklineSamples(samples))
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearTrendSamples() As MASMetricCard
            If _sparklineSamples.Count = 0 Then Return Me
            _sparklineSamples.Clear()
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithTitle(value As String) As MASMetricCard
            Title = value
            Return Me
        End Function

        Public Function WithValueText(value As String) As MASMetricCard
            ValueText = value
            Return Me
        End Function

        Public Function WithSubtitle(value As String) As MASMetricCard
            Subtitle = value
            Return Me
        End Function

        Public Function WithTrend(value As String,
                                  Optional tone As MASToastTone = MASToastTone.Info) As MASMetricCard
            TrendText = value
            Me.Tone = tone
            Return Me
        End Function

        Public Function WithTone(value As MASToastTone) As MASMetricCard
            Tone = value
            Return Me
        End Function

        Public Function WithTrendVisible(value As Boolean) As MASMetricCard
            ShowTrend = value
            Return Me
        End Function

        Public Function WithCompact(value As Boolean) As MASMetricCard
            IsCompact = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASMetricCard
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region

#Region "Layout"

        Friend Function GetPreferredLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetPreferredLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).DesiredSize
        End Function

        Friend Function GetMinLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetMinLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).MinSize
        End Function

        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return MASIntrinsicControlSizeMetrics.EnsureContext(Nothing)
            Return context.ToSizeContext()
        End Function

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim height As Single = If(_isCompact, MetricCardTokens.CompactHeightDip, MetricCardTokens.PreferredHeightDip)
            Dim desired As New SKSize(MetricCardTokens.PreferredWidthDip, height)
            Dim minimum As New SKSize(MetricCardTokens.ContractMinWidthDip, MetricCardTokens.ContractMinHeightDip)
            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(MetricCardTokens.MaxWidthDip, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=New MASLayoutInset(MetricCardTokens.PaddingDip,
                                                 MetricCardTokens.PaddingDip,
                                                 MetricCardTokens.PaddingDip,
                                                 MetricCardTokens.PaddingDip),
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        End Function

#End Region

#Region "Rendering"

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim responsive As MASResponsiveLayoutProfile = MASResponsiveLayoutResolver.FromTheme(ctx, bounds, SizeIntent, MASMetricCardLayoutPlan.Thresholds)
            Dim plan As MASMetricCardLayoutPlan = MASMetricCardLayoutPlan.Create(responsive, _isCompact, _showTrend, _trendText, _subtitle, _sparklineSamples.Count)

            _primitives.DrawCardSurface(canvas, ctx, bounds, dpi, _contract, MASCardVisualStyle.Secondary)
            _contentRect = PixelSnap.SnapRect(Inset(bounds, plan.PaddingPx), dpi)
            If _contentRect.Width <= 1.0F OrElse _contentRect.Height <= 1.0F Then Return

            Dim toneColor As SKColor = MASMetricCardPolicy.ResolveToneColor(ctx, _tone)
            Dim isRtl As Boolean = MASMetricCardPolicy.ResolveRightToLeft()
            DrawAccentRail(canvas, _contentRect, plan, toneColor, isRtl)
            DrawHeader(canvas, ctx, _contentRect, plan, toneColor, isRtl)
            DrawBody(canvas, ctx, _contentRect, plan, toneColor, isRtl)
            If plan.ShowSparkline Then DrawSparkline(canvas, _contentRect, plan, toneColor)
        End Sub

        Private Sub DrawAccentRail(canvas As SKCanvas,
                                   content As SKRect,
                                   plan As MASMetricCardLayoutPlan,
                                   toneColor As SKColor,
                                   isRtl As Boolean)
            Dim railWidth As Single = plan.AccentRailWidthPx
            If railWidth <= 0.0F Then Return
            Dim rail As SKRect
            If isRtl Then
                rail = New SKRect(content.Right - railWidth, content.Top + 2.0F * plan.Dpi, content.Right, content.Bottom - 2.0F * plan.Dpi)
            Else
                rail = New SKRect(content.Left, content.Top + 2.0F * plan.Dpi, content.Left + railWidth, content.Bottom - 2.0F * plan.Dpi)
            End If
            Using paint As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(MetricCardTokens.AccentAlpha)}
                canvas.DrawRoundRect(rail, railWidth * 0.5F, railWidth * 0.5F, paint)
            End Using
        End Sub

        Private Sub DrawHeader(canvas As SKCanvas,
                               ctx As MASThemeContext,
                               content As SKRect,
                               plan As MASMetricCardLayoutPlan,
                               toneColor As SKColor,
                               isRtl As Boolean)
            If Not plan.ShowHeader OrElse plan.HeaderHeightPx <= 0.0F Then Return
            Dim textInset As Single = plan.TextInsetPx
            Dim header As New SKRect(content.Left + textInset, content.Top, content.Right - textInset, content.Top + plan.HeaderHeightPx)
            Dim titleRect As SKRect = header
            If plan.ShowTrendPill Then
                Dim pillWidth As Single = ResolveTrendPillWidth(plan)
                Dim pillRect As SKRect
                If isRtl Then
                    pillRect = New SKRect(header.Left, header.Top, header.Left + pillWidth, header.Top + plan.TrendPillHeightPx)
                    titleRect = New SKRect(pillRect.Right + 8.0F * plan.Dpi, header.Top, header.Right, header.Bottom)
                Else
                    pillRect = New SKRect(header.Right - pillWidth, header.Top, header.Right, header.Top + plan.TrendPillHeightPx)
                    titleRect = New SKRect(header.Left, header.Top, pillRect.Left - 8.0F * plan.Dpi, header.Bottom)
                End If
                DrawTrendPill(canvas, ctx, pillRect, plan, toneColor)
            End If

            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _title, titleRect, plan.Dpi, MASTypography.MASTextStyle.Small, MASMetricCardPolicy.ResolveTitleColor(ctx), If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left), False)
        End Sub

        Private Function ResolveTrendPillWidth(plan As MASMetricCardLayoutPlan) As Single
            Dim normalized As String = MASMetricCardPolicy.NormalizeTrendText(_trendText)
            Dim estimated As Single = CSng(Math.Max(2, normalized.Length)) * 6.6F * plan.Dpi * plan.ResponsiveProfile.ContentScale + 22.0F * plan.Dpi
            Return Math.Max(plan.TrendPillMinWidthPx, Math.Min(estimated, 118.0F * plan.Dpi * plan.ResponsiveProfile.ChromeScale))
        End Function

        Private Sub DrawTrendPill(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  rect As SKRect,
                                  plan As MASMetricCardLayoutPlan,
                                  toneColor As SKColor)
            Dim radius As Single = plan.TrendPillRadiusPx
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(MetricCardTokens.PillFillAlpha)}
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
            Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, plan.Dpi), .Color = toneColor.WithAlpha(CByte(Math.Min(255, MetricCardTokens.PillFillAlpha + 42)))}
                canvas.DrawRoundRect(Inset(rect, 0.5F * plan.Dpi), Math.Max(1.0F, radius - 0.5F * plan.Dpi), Math.Max(1.0F, radius - 0.5F * plan.Dpi), stroke)
            End Using
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _trendText, Inset(rect, 8.0F * plan.Dpi), plan.Dpi, MASTypography.MASTextStyle.Micro, toneColor.WithAlpha(MetricCardTokens.TrendTextAlpha), TypographyTextPrimitives.TextAlign.Center, False)
        End Sub

        Private Sub DrawBody(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             content As SKRect,
                             plan As MASMetricCardLayoutPlan,
                             toneColor As SKColor,
                             isRtl As Boolean)
            Dim textInset As Single = plan.TextInsetPx
            Dim bodyTop As Single = content.Top + plan.HeaderHeightPx + plan.BodyGapPx
            Dim valueRect As New SKRect(content.Left + textInset,
                                        bodyTop,
                                        content.Right - textInset,
                                        bodyTop + plan.ValueHeightPx)
            Dim subtitleRect As New SKRect(content.Left + textInset,
                                           valueRect.Bottom + 2.0F * plan.Dpi,
                                           content.Right - textInset,
                                           valueRect.Bottom + 2.0F * plan.Dpi + plan.SubtitleHeightPx)
            Dim align As TypographyTextPrimitives.TextAlign = If(isRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _valueText, valueRect, plan.Dpi, MASTypography.MASTextStyle.Title, MASMetricCardPolicy.ResolveValueColor(ctx), align, False)
            If plan.ShowSubtitle Then
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _subtitle, subtitleRect, plan.Dpi, MASTypography.MASTextStyle.Micro, MASMetricCardPolicy.ResolveSubtitleColor(ctx), align, False)
            End If

            If Not plan.ShowAccentMarker Then Return
            Using wash As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(MetricCardTokens.AccentWashAlpha)}
                Dim markerSize As Single = 8.0F * plan.Dpi * plan.ResponsiveProfile.ChromeScale
                Dim marker As New SKRect(content.Right - textInset - markerSize, subtitleRect.MidY - markerSize * 0.5F, content.Right - textInset, subtitleRect.MidY + markerSize * 0.5F)
                If isRtl Then marker = New SKRect(content.Left + textInset, subtitleRect.MidY - markerSize * 0.5F, content.Left + textInset + markerSize, subtitleRect.MidY + markerSize * 0.5F)
                canvas.DrawOval(marker, wash)
            End Using
        End Sub

        Private Sub DrawSparkline(canvas As SKCanvas,
                                  content As SKRect,
                                  plan As MASMetricCardLayoutPlan,
                                  toneColor As SKColor)
            If _sparklineSamples.Count < 2 OrElse plan.SparklineHeightPx <= 0.0F Then Return
            Dim insetX As Single = plan.TextInsetPx
            Dim height As Single = plan.SparklineHeightPx
            Dim rect As New SKRect(content.Left + insetX,
                                   content.Bottom - height,
                                   content.Right - insetX,
                                   content.Bottom)
            If rect.Width <= 8.0F OrElse rect.Height <= 4.0F Then Return

            Dim minValue As Double = _sparklineSamples(0)
            Dim maxValue As Double = _sparklineSamples(0)
            For Each sample As Double In _sparklineSamples
                If sample < minValue Then minValue = sample
                If sample > maxValue Then maxValue = sample
            Next
            Dim span As Double = Math.Max(0.0001R, maxValue - minValue)

            Using linePath As New SKPath()
                Using fillPath As New SKPath()
                    For i As Integer = 0 To _sparklineSamples.Count - 1
                        Dim x As Single = rect.Left + (CSng(i) / CSng(_sparklineSamples.Count - 1)) * rect.Width
                        Dim ratio As Single = CSng((_sparklineSamples(i) - minValue) / span)
                        Dim y As Single = rect.Bottom - ratio * rect.Height
                        If i = 0 Then
                            linePath.MoveTo(x, y)
                            fillPath.MoveTo(x, rect.Bottom)
                            fillPath.LineTo(x, y)
                        Else
                            linePath.LineTo(x, y)
                            fillPath.LineTo(x, y)
                        End If
                    Next
                    fillPath.LineTo(rect.Right, rect.Bottom)
                    fillPath.Close()
                    Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(MetricCardTokens.SparklineFillAlpha)}
                        canvas.DrawPath(fillPath, fill)
                    End Using
                    Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.35F * plan.Dpi, 1.0F), .StrokeCap = SKStrokeCap.Round, .StrokeJoin = SKStrokeJoin.Round, .Color = toneColor.WithAlpha(MetricCardTokens.SparklineAlpha)}
                        canvas.DrawPath(linePath, stroke)
                    End Using
                End Using
            End Using
        End Sub

#End Region

#Region "Helpers"

        Private Shared Function Inset(rect As SKRect,
                                      insetPx As Single) As SKRect
            Return New SKRect(rect.Left + insetPx,
                              rect.Top + insetPx,
                              rect.Right - insetPx,
                              rect.Bottom - insetPx)
        End Function

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return False
        End Function

        Protected Overrides Function WantsPointerFocus() As Boolean
            Return False
        End Function

        Friend Function CreateMetricCardReadinessManifest() As MASMetricCardReadinessManifest
            Return MASMetricCardReadinessManifest.CreateDefault()
        End Function

#End Region

    End Class

End Namespace
