Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
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.Controls

    ''' <summary>
    ''' Public Nexamas UI empty-state control for product surfaces, dashboards, lists,
    ''' and data regions that have no content to display. It is visual-only: page layout,
    ''' DataGrid/ListView adapters, search execution, file-system state, actions, loading
    ''' handoff, and notification queues remain owned by their existing Nexamas UI systems.
    ''' </summary>
    Public NotInheritable Class MASEmptyState
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()

        Private _title As String = String.Empty
        Private _description As String = String.Empty
        Private _showIllustration As Boolean = True
        Private _disposedLocal As Boolean

#End Region

#Region "Constructor"

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

#End Region

#Region "Public API"

        Public Shared Function Create(Optional title As String = Nothing,
                                      Optional description As String = Nothing) As MASEmptyState
            Dim control As New MASEmptyState()
            control._title = MASEmptyStateContentPolicy.NormalizeText(title)
            control._description = MASEmptyStateContentPolicy.NormalizeText(description)
            Return control
        End Function

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = MASEmptyStateContentPolicy.NormalizeText(value)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return

                _title = normalized
                RaiseTextChanged()
                RequestSizeLayoutRefreshForSizeAffectingChange("MASEmptyState.Title")
                InvalidateVisual()
            End Set
        End Property

        Public Property Description As String
            Get
                Return _description
            End Get
            Set(value As String)
                Dim normalized As String = MASEmptyStateContentPolicy.NormalizeText(value)
                If String.Equals(_description, normalized, StringComparison.Ordinal) Then Return

                _description = normalized
                RaiseTextChanged()
                RequestSizeLayoutRefreshForSizeAffectingChange("MASEmptyState.Description")
                InvalidateVisual()
            End Set
        End Property

        Public Property ShowIllustration As Boolean
            Get
                Return _showIllustration
            End Get
            Set(value As Boolean)
                If _showIllustration = value Then Return

                _showIllustration = value
                RequestSizeLayoutRefreshForSizeAffectingChange("MASEmptyState.ShowIllustration")
                InvalidateVisual()
            End Set
        End Property

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

        Public Function WithDescription(value As String) As MASEmptyState
            Description = value
            Return Me
        End Function

        Public Function WithIllustration(value As Boolean) As MASEmptyState
            ShowIllustration = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASEmptyState
            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

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim titleSize As SKSize = MeasureText(safeContext, ResolveTitleText(), MASTypography.MASTextStyle.Title)
            Dim descriptionSize As SKSize = MeasureText(safeContext, ResolveDescriptionText(), MASTypography.MASTextStyle.Body)
            Dim illustrationHeight As Single = ResolveIllustrationSlotHeight()
            Dim textWidth As Single = Math.Max(titleSize.Width, descriptionSize.Width)

            Dim desiredWidth As Single = Math.Max(EmptyStateTokens.MinWidthDip,
                                                  EmptyStateTokens.PaddingHorizontalDip * 2.0F +
                                                  Math.Min(textWidth, EmptyStateTokens.MaxTextWidthDip))
            Dim desiredHeight As Single = Math.Max(EmptyStateTokens.MinHeightDip,
                                                   EmptyStateTokens.PaddingTopDip +
                                                   illustrationHeight +
                                                   If(_showIllustration, EmptyStateTokens.IllustrationTextGapDip, 0.0F) +
                                                   EmptyStateTokens.TitleLineHeightDip * MASEmptyStateContentPolicy.MaxTitleLines +
                                                   EmptyStateTokens.TitleDescriptionGapDip +
                                                   EmptyStateTokens.DescriptionLineHeightDip * MASEmptyStateContentPolicy.MaxDescriptionLines +
                                                   EmptyStateTokens.PaddingBottomDip)

            Dim minimum As New SKSize(EmptyStateTokens.MinWidthDip, EmptyStateTokens.MinHeightDip)
            Dim contentInset As New MASLayoutInset(
                EmptyStateTokens.PaddingHorizontalDip,
                EmptyStateTokens.PaddingTopDip,
                EmptyStateTokens.PaddingHorizontalDip,
                EmptyStateTokens.PaddingBottomDip)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=New SKSize(desiredWidth, desiredHeight),
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=intent,
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=False,
                contentInset:=contentInset,
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        End Function

        Private Function MeasureText(context As MASSizeContext,
                                     text As String,
                                     style As MASTypography.MASTextStyle) As SKSize
            Dim result As MASTextMeasureResult =
                MASLayoutTextMeasureGateway.Measure(
                    If(context Is Nothing, Nothing, context.IntegrationContext),
                    If(String.IsNullOrEmpty(text), " ", text),
                    style,
                    EmptyStateTokens.MaxTextWidthDip,
                    allowWrap:=True)

            Return result.Size
        End Function

        Private Function ResolveIllustrationSlotHeight() As Single
            If Not _showIllustration Then Return 0.0F
            Return EmptyStateTokens.IllustrationDiameterDip
        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

#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 stateRect As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If stateRect.Width <= 1.0F OrElse stateRect.Height <= 1.0F Then Return

            _primitives.DrawCardSurface(canvas, ctx, stateRect, dpi, _contract, MASCardVisualStyle.Secondary)
            DrawSubtleWash(canvas, ctx, stateRect, dpi)
            DrawContent(canvas, ctx, stateRect, dpi)
        End Sub

        Private Sub DrawSubtleWash(canvas As SKCanvas,
                                   ctx As MASThemeContext,
                                   bounds As SKRect,
                                   dpi As Single)
            Dim accent As SKColor = MASEmptyStateContentPolicy.ResolveAccentColor(ctx)
            Dim radius As Single = MASVisualPrimitivesPainter.ResolveSurfaceRadiusPx(_contract, bounds, dpi)
            radius = PixelSnap.SafeRadius(radius)

            Using paint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = accent.WithAlpha(EmptyStateTokens.WashAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, paint)
            End Using
        End Sub

        Private Sub DrawContent(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                bounds As SKRect,
                                dpi As Single)
            If ctx.Typography Is Nothing Then Return

            Dim content As New SKRect(
                bounds.Left + EmptyStateTokens.PaddingHorizontalDip * dpi,
                bounds.Top + EmptyStateTokens.PaddingTopDip * dpi,
                bounds.Right - EmptyStateTokens.PaddingHorizontalDip * dpi,
                bounds.Bottom - EmptyStateTokens.PaddingBottomDip * dpi)

            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            Dim titleText As String = ResolveTitleText()
            Dim descriptionText As String = ResolveDescriptionText()
            Dim titlePaint As SKPaint = ctx.Typography.GetPaint(MASTypography.MASTextStyle.Title, MASEmptyStateContentPolicy.ResolveTitleColor(ctx))
            Dim descriptionPaint As SKPaint = ctx.Typography.GetPaint(MASTypography.MASTextStyle.Body, MASEmptyStateContentPolicy.ResolveDescriptionColor(ctx))
            If titlePaint Is Nothing OrElse descriptionPaint Is Nothing Then Return

            Dim textWidth As Single = Math.Min(content.Width, EmptyStateTokens.MaxTextWidthDip * dpi)
            Dim titleLines As List(Of String) = MASEmptyStateContentPolicy.BuildWrappedLines(titleText, titlePaint, textWidth, MASEmptyStateContentPolicy.MaxTitleLines)
            Dim descriptionLines As List(Of String) = MASEmptyStateContentPolicy.BuildWrappedLines(descriptionText, descriptionPaint, textWidth, MASEmptyStateContentPolicy.MaxDescriptionLines)
            Dim isRtl As Boolean = MASEmptyStateContentPolicy.ResolveTextIsRtl(titleText, descriptionText)
            Dim plan As MASEmptyStateContentPlan = MASEmptyStateContentPolicy.BuildContentPlan(content, dpi, _showIllustration, titleLines.Count, descriptionLines.Count)

            If plan.HasIllustration Then DrawIllustration(canvas, ctx, plan.IllustrationRect, dpi)
            DrawLines(canvas, titleLines, titlePaint, plan.TitleRect, plan.TitleLineHeightPx, isRtl)
            DrawLines(canvas, descriptionLines, descriptionPaint, plan.DescriptionRect, plan.DescriptionLineHeightPx, isRtl)
        End Sub

        Private Shared Sub DrawIllustration(canvas As SKCanvas,
                                            ctx As MASThemeContext,
                                            bounds As SKRect,
                                            dpi As Single)
            If canvas Is Nothing Then Return
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim accent As SKColor = MASEmptyStateContentPolicy.ResolveAccentColor(ctx)
            Dim muted As SKColor = MASEmptyStateContentPolicy.ResolveDescriptionColor(ctx)
            Dim radius As Single = Math.Max(1.0F, bounds.Width * EmptyStateTokens.IllustrationCornerRatio)

            Using haloPaint As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = accent.WithAlpha(EmptyStateTokens.IllustrationHaloAlpha)
            }
                canvas.DrawCircle(bounds.MidX, bounds.MidY, bounds.Width * 0.5F, haloPaint)
            End Using

            Dim card As New SKRect(bounds.Left + bounds.Width * 0.21F,
                                   bounds.Top + bounds.Height * 0.25F,
                                   bounds.Right - bounds.Width * 0.21F,
                                   bounds.Bottom - bounds.Height * 0.21F)

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = SKColors.White.WithAlpha(EmptyStateTokens.IllustrationCardAlpha)
            }
                canvas.DrawRoundRect(card, radius, radius, fill)
            End Using

            Using stroke As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, EmptyStateTokens.IllustrationStrokeDip * dpi),
                .Color = accent.WithAlpha(EmptyStateTokens.IllustrationStrokeAlpha)
            }
                canvas.DrawRoundRect(card, radius, radius, stroke)
            End Using

            Dim iconRect As New SKRect(
                card.Left + card.Width * 0.27F,
                card.Top + card.Height * 0.18F,
                card.Right - card.Width * 0.27F,
                card.Bottom - card.Height * 0.18F)

            MASIconPainter.Draw(
                canvas:=canvas,
                bounds:=iconRect,
                dpi:=dpi,
                kind:=MASEmptyStateContentPolicy.ResolveIllustrationKind(),
                color:=muted.WithAlpha(EmptyStateTokens.IllustrationLineAlpha),
                style:=MASIconStyle.Outline)
        End Sub

        Private Shared Sub DrawLines(canvas As SKCanvas,
                                     lines As List(Of String),
                                     paint As SKPaint,
                                     bounds As SKRect,
                                     lineHeight As Single,
                                     isRtl As Boolean)
            If canvas Is Nothing OrElse lines Is Nothing OrElse paint Is Nothing Then Return
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim baseline As Single = bounds.Top - paint.FontMetrics.Ascent
            For Each line As String In lines
                If baseline > bounds.Bottom + lineHeight Then Exit For
                If Not String.IsNullOrEmpty(line) Then
                    Dim textWidth As Single = MASShapedText.MeasureWidth(line, paint)
                    Dim x As Single = If(isRtl, bounds.Right - textWidth, bounds.Left + (bounds.Width - textWidth) / 2.0F)
                    MASShapedText.Draw(canvas, line, x, baseline, paint)
                End If
                baseline += lineHeight
            Next
        End Sub

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If _disposedLocal Then Return
            _disposedLocal = True

            If disposing Then
                Try
                    _primitives.Dispose()
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
                End Try
            End If

            MyBase.Dispose(disposing)
        End Sub

#End Region

#Region "Helpers"

        Private Function ResolveTitleText() As String
            Return MASEmptyStateContentPolicy.ResolveTitleText(_title)
        End Function

        Private Function ResolveDescriptionText() As String
            Return MASEmptyStateContentPolicy.ResolveDescriptionText(_description)
        End Function

#End Region

#Region "Governance"

        Friend Function CreateEmptyStateReadinessManifest() As MASEmptyStateReadinessManifest
            Return MASEmptyStateReadinessManifest.CreateCurrent()
        End Function

#End Region

    End Class

End Namespace
