Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Globalization
Imports Nexamas.UI.Values

Namespace Nexamas.UI.Components

    Friend Enum MASDashboardWidgetRefreshState
        Ready = 0
        Loading = 1
        Faulted = 2
    End Enum

    ''' <summary>
    ''' Friend-only provider refresh record for DashboardGrid. It is a bounded
    ''' product-control contract used by gates and internal adapters; it is not a
    ''' public provider API, not a chart engine, and not external storage.
    ''' </summary>
    Friend NotInheritable Class MASDashboardGridProviderRecord
        Friend Sub New(key As String,
                       title As String,
                       valueText As String,
                       Optional caption As String = Nothing,
                       Optional spanColumns As Integer = 1,
                       Optional spanRows As Integer = 1,
                       Optional refreshState As MASDashboardWidgetRefreshState = MASDashboardWidgetRefreshState.Ready,
                       Optional refreshMessage As String = Nothing)
            Me.Key = MASDashboardGridPolicy.NormalizeKey(key, "widget", 1)
            Me.Title = MASDashboardGridPolicy.NormalizeWidgetTitle(title)
            Me.ValueText = MASDashboardGridPolicy.NormalizeValue(valueText)
            Me.Caption = MASDashboardGridPolicy.NormalizeCaption(caption)
            Me.SpanColumns = MASDashboardGridPolicy.ClampSpan(spanColumns, DashboardGridTokens.MaxSpanColumns)
            Me.SpanRows = MASDashboardGridPolicy.ClampSpan(spanRows, DashboardGridTokens.MaxSpanRows)
            Me.RefreshState = refreshState
            Me.RefreshMessage = MASDashboardGridPolicy.NormalizeCaption(refreshMessage)
        End Sub

        Friend ReadOnly Property Key As String
        Friend ReadOnly Property Title As String
        Friend ReadOnly Property ValueText As String
        Friend ReadOnly Property Caption As String
        Friend ReadOnly Property SpanColumns As Integer
        Friend ReadOnly Property SpanRows As Integer
        Friend ReadOnly Property RefreshState As MASDashboardWidgetRefreshState
        Friend ReadOnly Property RefreshMessage As String

        Friend ReadOnly Property IsStructurallyValid As Boolean
            Get
                Return Key.Length > 0 AndAlso Title.Length > 0
            End Get
        End Property

        Friend Function ToWidgetSnapshot() As MASDashboardGridWidgetSnapshot
            Return New MASDashboardGridWidgetSnapshot(Key, Title, ValueText, Caption, SpanColumns, SpanRows)
        End Function
    End Class

    ''' <summary>
    ''' Friend-only refresh snapshot. It captures the last provider-driven widget
    ''' replacement, selected-key preservation, revision, and error/loading states.
    ''' </summary>
    Friend NotInheritable Class MASDashboardGridProviderRefreshSnapshot
        Private ReadOnly _widgetKeys As IReadOnlyList(Of String)
        Private ReadOnly _errorWidgetKeys As IReadOnlyList(Of String)

        Friend Sub New(revision As Integer,
                       reason As String,
                       isRefreshing As Boolean,
                       selectedWidgetKey As String,
                       totalWidgets As Integer,
                       errorWidgetKeys As IEnumerable(Of String),
                       widgetKeys As IEnumerable(Of String))
            Me.Revision = Math.Max(0, revision)
            Me.Reason = MASDashboardGridPolicy.NormalizeText(reason)
            Me.IsRefreshing = isRefreshing
            Me.SelectedWidgetKey = MASDashboardGridPolicy.NormalizeText(selectedWidgetKey)
            Me.TotalWidgets = Math.Max(0, totalWidgets)

            Dim errors As New List(Of String)()
            If errorWidgetKeys IsNot Nothing Then
                For Each key As String In errorWidgetKeys
                    Dim normalized As String = MASDashboardGridPolicy.NormalizeText(key)
                    If normalized.Length > 0 Then errors.Add(normalized)
                Next
            End If
            _errorWidgetKeys = New ReadOnlyCollection(Of String)(errors.ToArray())

            Dim keys As New List(Of String)()
            If widgetKeys IsNot Nothing Then
                For Each key As String In widgetKeys
                    Dim normalized As String = MASDashboardGridPolicy.NormalizeText(key)
                    If normalized.Length > 0 Then keys.Add(normalized)
                Next
            End If
            _widgetKeys = New ReadOnlyCollection(Of String)(keys.ToArray())
        End Sub

        Friend ReadOnly Property Revision As Integer
        Friend ReadOnly Property Reason As String
        Friend ReadOnly Property IsRefreshing As Boolean
        Friend ReadOnly Property SelectedWidgetKey As String
        Friend ReadOnly Property TotalWidgets As Integer

        Friend ReadOnly Property WidgetKeys As IReadOnlyList(Of String)
            Get
                Return _widgetKeys
            End Get
        End Property

        Friend ReadOnly Property ErrorWidgetKeys As IReadOnlyList(Of String)
            Get
                Return _errorWidgetKeys
            End Get
        End Property

        Friend ReadOnly Property ErrorWidgetCount As Integer
            Get
                Return _errorWidgetKeys.Count
            End Get
        End Property

        Friend ReadOnly Property HasProviderState As Boolean
            Get
                Return Revision > 0 AndAlso Reason.Length > 0
            End Get
        End Property

        Friend ReadOnly Property HasErrorState As Boolean
            Get
                Return ErrorWidgetCount > 0
            End Get
        End Property

        Friend ReadOnly Property IsStructurallyValid As Boolean
            Get
                Return TotalWidgets = _widgetKeys.Count AndAlso Revision >= 0
            End Get
        End Property

        Friend Function ContainsWidgetKey(widgetKey As String) As Boolean
            Dim normalized As String = MASDashboardGridPolicy.NormalizeText(widgetKey)
            If normalized.Length = 0 Then Return False
            For Each key As String In _widgetKeys
                If String.Equals(key, normalized, StringComparison.Ordinal) Then Return True
            Next
            Return False
        End Function
    End Class

    Partial Public NotInheritable Class MASDashboardGrid

#Region "Friend provider refresh contract"

        Private ReadOnly _widgetProviderStates As New Dictionary(Of String, MASDashboardWidgetRefreshState)(StringComparer.Ordinal)
        Private ReadOnly _widgetProviderMessages As New Dictionary(Of String, String)(StringComparer.Ordinal)
        Private _widgetProviderRefreshRevision As Integer
        Private _widgetProviderIsRefreshing As Boolean
        Private _widgetProviderLastReason As String = String.Empty

        Friend Function RefreshWidgetsFromProvider(records As IEnumerable(Of MASDashboardGridProviderRecord),
                                                   Optional reason As String = "") As Boolean
            If records Is Nothing Then Return False

            Dim selectedKey As String = SelectedWidgetKey
            Dim snapshots As New List(Of MASDashboardGridWidgetSnapshot)()
            Dim states As New Dictionary(Of String, MASDashboardWidgetRefreshState)(StringComparer.Ordinal)
            Dim messages As New Dictionary(Of String, String)(StringComparer.Ordinal)
            Dim usedKeys As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

            _widgetProviderIsRefreshing = True
            Try
                For Each record As MASDashboardGridProviderRecord In records
                    If record Is Nothing OrElse Not record.IsStructurallyValid Then Continue For
                    If snapshots.Count >= DashboardGridTokens.MaxLargeLayoutProofWidgets Then Exit For
                    Dim key As String = ResolveUniqueProviderWidgetKey(record.Key, snapshots.Count + 1, usedKeys)
                    snapshots.Add(New MASDashboardGridWidgetSnapshot(key,
                                                                     record.Title,
                                                                     record.ValueText,
                                                                     record.Caption,
                                                                     record.SpanColumns,
                                                                     record.SpanRows))
                    states(key) = record.RefreshState
                    messages(key) = MASDashboardGridPolicy.NormalizeCaption(record.RefreshMessage)
                Next

                If selectedKey.Length = 0 AndAlso snapshots.Count > 0 Then selectedKey = snapshots(0).Key
                Dim snapshot As New MASDashboardGridLayoutSnapshot(1, _title, selectedKey, _topIndex, snapshots)
                If Not RestoreLayoutSnapshotCore(snapshot, DashboardGridTokens.MaxLargeLayoutProofWidgets, "MASDashboardGrid.ProviderRefresh") Then Return False

                _widgetProviderStates.Clear()
                _widgetProviderMessages.Clear()
                For Each pair As KeyValuePair(Of String, MASDashboardWidgetRefreshState) In states
                    _widgetProviderStates(pair.Key) = pair.Value
                Next
                For Each pair As KeyValuePair(Of String, String) In messages
                    _widgetProviderMessages(pair.Key) = pair.Value
                Next

                _widgetProviderRefreshRevision += 1
                _widgetProviderLastReason = MASDashboardGridPolicy.NormalizeText(If(reason, String.Empty))
                If _widgetProviderLastReason.Length = 0 Then _widgetProviderLastReason = "ProviderRefresh"
                InvalidateVisual()
                Return True
            Finally
                _widgetProviderIsRefreshing = False
            End Try
        End Function

        Friend Function CreateProviderRefreshSnapshot() As MASDashboardGridProviderRefreshSnapshot
            Dim keys As New List(Of String)()
            Dim errorKeys As New List(Of String)()
            For Each widget As DashboardWidgetState In _widgets
                If widget Is Nothing Then Continue For
                keys.Add(widget.Key)
                Dim state As MASDashboardWidgetRefreshState = ResolveProviderRefreshState(widget.Key)
                If state = MASDashboardWidgetRefreshState.Faulted Then errorKeys.Add(widget.Key)
            Next

            Return New MASDashboardGridProviderRefreshSnapshot(_widgetProviderRefreshRevision,
                                                               _widgetProviderLastReason,
                                                               _widgetProviderIsRefreshing,
                                                               SelectedWidgetKey,
                                                               _widgets.Count,
                                                               errorKeys,
                                                               keys)
        End Function

        Friend Function HasWidgetProviderRefreshContractForTests() As Boolean
            Dim firstRecords As New List(Of MASDashboardGridProviderRecord) From {
                New MASDashboardGridProviderRecord("sales", "Sales", "42", "Ready"),
                New MASDashboardGridProviderRecord("traffic", "Traffic", "128", "Ready"),
                New MASDashboardGridProviderRecord("risk", "Risk", "--", "Provider failed", 1, 1, MASDashboardWidgetRefreshState.Faulted, "Provider failed"),
                New MASDashboardGridProviderRecord("queue", "Queue", "7", "Loading", 1, 1, MASDashboardWidgetRefreshState.Loading, "Refreshing")
            }

            If Not RefreshWidgetsFromProvider(firstRecords, "Initial provider proof") Then Return False
            If Not SelectWidget("traffic") Then Return False
            Dim first As MASDashboardGridProviderRefreshSnapshot = CreateProviderRefreshSnapshot()
            If first Is Nothing OrElse Not first.IsStructurallyValid OrElse Not first.HasProviderState OrElse Not first.HasErrorState Then Return False
            If Not first.ContainsWidgetKey("risk") Then Return False

            Dim secondRecords As New List(Of MASDashboardGridProviderRecord) From {
                New MASDashboardGridProviderRecord("sales", "Sales", "43", "Updated"),
                New MASDashboardGridProviderRecord("traffic", "Traffic", "144", "Selected key should survive"),
                New MASDashboardGridProviderRecord("risk", "Risk", "0", "Recovered")
            }

            If Not RefreshWidgetsFromProvider(secondRecords, "Second provider proof") Then Return False
            Dim second As MASDashboardGridProviderRefreshSnapshot = CreateProviderRefreshSnapshot()
            Return second IsNot Nothing AndAlso
                   second.IsStructurallyValid AndAlso
                   second.HasProviderState AndAlso
                   Not second.HasErrorState AndAlso
                   String.Equals(SelectedWidgetKey, "traffic", StringComparison.Ordinal) AndAlso
                   second.Revision > first.Revision
        End Function

        Private Function ResolveProviderWidgetValueText(item As DashboardWidgetState) As String
            If item Is Nothing Then Return String.Empty
            Dim state As MASDashboardWidgetRefreshState = ResolveProviderRefreshState(item.Key)
            If state = MASDashboardWidgetRefreshState.Faulted Then Return "Error"
            If state = MASDashboardWidgetRefreshState.Loading Then Return "Loading"
            Return item.ValueText
        End Function

        Private Function ResolveProviderWidgetCaption(item As DashboardWidgetState) As String
            If item Is Nothing Then Return String.Empty
            Dim message As String = String.Empty
            If _widgetProviderMessages.TryGetValue(item.Key, message) AndAlso message.Length > 0 Then Return message
            Return item.Caption
        End Function

        Private Function ResolveProviderRefreshState(widgetKey As String) As MASDashboardWidgetRefreshState
            Dim normalized As String = MASDashboardGridPolicy.NormalizeKey(widgetKey, "widget", 1)
            Dim state As MASDashboardWidgetRefreshState = MASDashboardWidgetRefreshState.Ready
            If _widgetProviderStates.TryGetValue(normalized, state) Then Return state
            Return MASDashboardWidgetRefreshState.Ready
        End Function

        Private Shared Function ResolveUniqueProviderWidgetKey(rawKey As String,
                                                               index As Integer,
                                                               usedKeys As HashSet(Of String)) As String
            Dim normalized As String = MASDashboardGridPolicy.NormalizeKey(rawKey, "widget", index)
            If usedKeys Is Nothing Then Return normalized
            If Not usedKeys.Contains(normalized) Then
                usedKeys.Add(normalized)
                Return normalized
            End If

            Dim suffix As Integer = 2
            Dim baseKey As String = normalized
            If baseKey.Length > 56 Then baseKey = baseKey.Substring(0, 56)
            Do
                Dim candidate As String = baseKey & "-" & suffix.ToString(CultureInfo.InvariantCulture)
                If Not usedKeys.Contains(candidate) Then
                    usedKeys.Add(candidate)
                    Return candidate
                End If
                suffix += 1
            Loop While suffix < 100000

            Return MASDashboardGridPolicy.NormalizeKey("widget", "widget", index)
        End Function

#End Region

    End Class

End Namespace
