Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Public Nexamas UI loading skeleton for premium visual loading placeholders.
    ''' It is visual-only: data loading, progress lifetime, virtualization ownership,
    ''' render verification execution, and future motion clocks remain owned by their
    ''' existing Nexamas UI systems.
    ''' </summary>
    Public NotInheritable Class MASLoadingSkeleton
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

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

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()

        Private _rowCount As Integer = LoadingSkeletonTokens.DefaultRowCount
        Private _lineCount As Integer = LoadingSkeletonTokens.DefaultLineCount
        Private _avatarVisible As Boolean
        Private _blockMode As Boolean
        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 rowCount As Integer = 4) As MASLoadingSkeleton
            Dim control As New MASLoadingSkeleton()
            control._rowCount = MASLoadingSkeletonVisualPolicy.NormalizeRowCount(rowCount)
            Return control
        End Function

        Public Property RowCount As Integer
            Get
                Return _rowCount
            End Get
            Set(value As Integer)
                Dim normalized As Integer = MASLoadingSkeletonVisualPolicy.NormalizeRowCount(value)
                If _rowCount = normalized Then Return

                _rowCount = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.RowCount")
                InvalidateVisual()
            End Set
        End Property

        Public Property LineCount As Integer
            Get
                Return _lineCount
            End Get
            Set(value As Integer)
                Dim normalized As Integer = MASLoadingSkeletonVisualPolicy.NormalizeLineCount(value)
                If _lineCount = normalized Then Return

                _lineCount = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.LineCount")
                InvalidateVisual()
            End Set
        End Property

        Public Property AvatarVisible As Boolean
            Get
                Return _avatarVisible
            End Get
            Set(value As Boolean)
                If _avatarVisible = value Then Return

                _avatarVisible = value
                RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.AvatarVisible")
                InvalidateVisual()
            End Set
        End Property

        Public Function WithRows(Optional rowCount As Integer = 4) As MASLoadingSkeleton
            _blockMode = False
            AvatarVisible = False
            RowCount = rowCount
            LineCount = LoadingSkeletonTokens.DefaultLineCount
            RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.WithRows")
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithCard(Optional lineCount As Integer = 3) As MASLoadingSkeleton
            _blockMode = False
            AvatarVisible = False
            RowCount = 1
            LineCount = lineCount
            RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.WithCard")
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithAvatarText(Optional lineCount As Integer = 3) As MASLoadingSkeleton
            _blockMode = False
            AvatarVisible = True
            RowCount = 1
            LineCount = lineCount
            RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.WithAvatarText")
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithBlock(Optional blockCount As Integer = 1) As MASLoadingSkeleton
            _blockMode = True
            AvatarVisible = False
            RowCount = blockCount
            LineCount = 1
            RequestSizeLayoutRefreshForSizeAffectingChange("MASLoadingSkeleton.WithBlock")
            InvalidateVisual()
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASLoadingSkeleton
            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 rowHeight As Single = ResolveRowHeightDip()
            Dim rowTotal As Single = CSng(_rowCount) * rowHeight + CSng(Math.Max(0, _rowCount - 1)) * LoadingSkeletonTokens.RowGapDip
            Dim desiredWidth As Single = LoadingSkeletonTokens.MaxPreferredWidthDip
            Dim desiredHeight As Single = LoadingSkeletonTokens.PaddingTopDip + rowTotal + LoadingSkeletonTokens.PaddingBottomDip
            Dim minimum As New SKSize(LoadingSkeletonTokens.MinWidthDip,
                                      LoadingSkeletonTokens.PaddingTopDip + rowHeight + LoadingSkeletonTokens.PaddingBottomDip)
            Dim contentInset As New MASLayoutInset(
                LoadingSkeletonTokens.PaddingHorizontalDip,
                LoadingSkeletonTokens.PaddingTopDip,
                LoadingSkeletonTokens.PaddingHorizontalDip,
                LoadingSkeletonTokens.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 ResolveRowHeightDip() As Single
            Return MASLoadingSkeletonVisualPolicy.ResolveRowHeightDip(_blockMode, _avatarVisible, _lineCount)
        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 skeletonRect As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If skeletonRect.Width <= 1.0F OrElse skeletonRect.Height <= 1.0F Then Return

            _primitives.DrawCardSurface(canvas, ctx, skeletonRect, dpi, _contract, MASCardVisualStyle.Secondary)
            DrawSurfaceWash(canvas, ctx, skeletonRect, dpi)
            DrawRows(canvas, ctx, skeletonRect, dpi)
        End Sub

        Private Sub DrawSurfaceWash(canvas As SKCanvas,
                                    ctx As MASThemeContext,
                                    bounds As SKRect,
                                    dpi As Single)
            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 = ResolveAccentColor(ctx).WithAlpha(LoadingSkeletonTokens.SurfaceWashAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, paint)
            End Using
        End Sub

        Private Sub DrawRows(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             bounds As SKRect,
                             dpi As Single)
            Dim content As New SKRect(
                bounds.Left + LoadingSkeletonTokens.PaddingHorizontalDip * dpi,
                bounds.Top + LoadingSkeletonTokens.PaddingTopDip * dpi,
                bounds.Right - LoadingSkeletonTokens.PaddingHorizontalDip * dpi,
                bounds.Bottom - LoadingSkeletonTokens.PaddingBottomDip * dpi)

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

            Dim plan As MASLoadingSkeletonLayoutPlan = MASLoadingSkeletonVisualPolicy.BuildLayoutPlan(content, _rowCount, _lineCount, _blockMode, _avatarVisible, dpi)

            For index As Integer = 0 To plan.RowRects.Length - 1
                Dim rowRect As SKRect = plan.RowRects(index)
                If rowRect.Height > 1.0F Then
                    If _blockMode Then
                        DrawBlockRow(canvas, ctx, rowRect, dpi)
                    ElseIf _avatarVisible Then
                        DrawAvatarTextRow(canvas, ctx, rowRect, dpi)
                    Else
                        DrawTextRow(canvas, ctx, rowRect, dpi, index)
                    End If
                End If
            Next
        End Sub

        Private Sub DrawTextRow(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                rowRect As SKRect,
                                dpi As Single,
                                rowIndex As Integer)
            Dim lineHeight As Single = LoadingSkeletonTokens.LineHeightDip * dpi
            Dim lineGap As Single = LoadingSkeletonTokens.LineGapDip * dpi
            Dim totalHeight As Single = CSng(_lineCount) * lineHeight + CSng(Math.Max(0, _lineCount - 1)) * lineGap
            Dim y As Single = rowRect.MidY - totalHeight / 2.0F

            For lineIndex As Integer = 0 To _lineCount - 1
                Dim widthRatio As Single = MASLoadingSkeletonVisualPolicy.ResolveLineWidthRatio(rowIndex, lineIndex, _lineCount)
                Dim lineRect As New SKRect(
                    rowRect.Left,
                    y,
                    rowRect.Left + rowRect.Width * widthRatio,
                    y + lineHeight)
                DrawSkeletonPill(canvas, ctx, lineRect, dpi, lineIndex)
                y += lineHeight + lineGap
            Next
        End Sub

        Private Sub DrawAvatarTextRow(canvas As SKCanvas,
                                      ctx As MASThemeContext,
                                      rowRect As SKRect,
                                      dpi As Single)
            Dim avatarSize As Single = LoadingSkeletonTokens.AvatarDiameterDip * dpi
            Dim avatarRect As New SKRect(rowRect.Left,
                                         rowRect.MidY - avatarSize / 2.0F,
                                         rowRect.Left + avatarSize,
                                         rowRect.MidY + avatarSize / 2.0F)
            DrawAvatar(canvas, ctx, avatarRect, dpi)

            Dim textLeft As Single = avatarRect.Right + LoadingSkeletonTokens.AvatarTextGapDip * dpi
            If textLeft >= rowRect.Right Then Return

            Dim textRect As New SKRect(textLeft, rowRect.Top, rowRect.Right, rowRect.Bottom)
            DrawTextRow(canvas, ctx, textRect, dpi, 0)
        End Sub

        Private Sub DrawBlockRow(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 rowRect As SKRect,
                                 dpi As Single)
            DrawSkeletonPill(canvas, ctx, rowRect, dpi, 0)
        End Sub

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

            Dim radius As Single = Math.Max(1.0F, bounds.Width * LoadingSkeletonTokens.AvatarRadiusRatio)
            Using fill As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = ResolveBaseColor(ctx).WithAlpha(LoadingSkeletonTokens.AvatarAlpha)
            }
                canvas.DrawRoundRect(bounds, radius, radius, fill)
            End Using
        End Sub

        Private Sub DrawSkeletonPill(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     bounds As SKRect,
                                     dpi As Single,
                                     lineIndex As Integer)
            Dim rect As SKRect = PixelSnap.SnapRect(bounds, dpi)
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            Dim radius As Single = Math.Min(rect.Height / 2.0F, LoadingSkeletonTokens.CornerRadiusDip * dpi)
            radius = PixelSnap.SafeRadius(radius)
            Dim baseColor As SKColor = ResolveBaseColor(ctx).WithAlpha(LoadingSkeletonTokens.BarBaseAlpha)
            Dim accentColor As SKColor = ResolveAccentColor(ctx).WithAlpha(LoadingSkeletonTokens.BarAccentAlpha)

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = If(lineIndex Mod 2 = 0, baseColor, accentColor)
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using

            Using stroke As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, 1.0F * dpi),
                .Color = ResolveBaseColor(ctx).WithAlpha(LoadingSkeletonTokens.StrokeAlpha)
            }
                canvas.DrawRoundRect(rect, radius, radius, stroke)
            End Using
        End Sub

        Private Shared Function ResolveBaseColor(ctx As MASThemeContext) As SKColor
            Return MASLoadingSkeletonVisualPolicy.ResolveBaseColor(ctx)
        End Function

        Private Shared Function ResolveAccentColor(ctx As MASThemeContext) As SKColor
            Return MASLoadingSkeletonVisualPolicy.ResolveAccentColor(ctx)
        End Function

#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 "Governance"

        Friend Function CreateLoadingSkeletonReadinessManifest() As MASLoadingSkeletonReadinessManifest
            Return MASLoadingSkeletonReadinessManifest.CreateCurrent()
        End Function

#End Region

    End Class

End Namespace
