Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public immutable dashboard widget layout snapshot. It captures the visual
    ''' widget order, text payload, spans, selected key, and viewport position, but
    ''' deliberately does not own chart engines, refresh providers, external storage,
    ''' or shell layout.
    ''' </summary>
    Public NotInheritable Class MASDashboardGridLayoutSnapshot
        Public Sub New(version As Integer,
                       title As String,
                       selectedWidgetKey As String,
                       topIndex As Integer,
                       widgets As IEnumerable(Of MASDashboardGridWidgetSnapshot))
            Me.Version = Math.Max(1, version)
            Me.Title = MASDashboardGridPolicy.NormalizeTitle(title)
            Me.SelectedWidgetKey = MASDashboardGridPolicy.NormalizeText(selectedWidgetKey)
            Me.TopIndex = Math.Max(0, topIndex)
            Dim copy As New List(Of MASDashboardGridWidgetSnapshot)()
            If widgets IsNot Nothing Then
                For Each widget As MASDashboardGridWidgetSnapshot In widgets
                    If widget IsNot Nothing Then copy.Add(widget)
                Next
            End If
            Me.Widgets = New ReadOnlyCollection(Of MASDashboardGridWidgetSnapshot)(copy)
        End Sub

        Public ReadOnly Property Version As Integer
        Public ReadOnly Property Title As String
        Public ReadOnly Property SelectedWidgetKey As String
        Public ReadOnly Property TopIndex As Integer
        Public ReadOnly Property Widgets As IReadOnlyList(Of MASDashboardGridWidgetSnapshot)
    End Class

    ''' <summary>
    ''' Public immutable widget entry used by MASDashboardGridLayoutSnapshot.
    ''' </summary>
    Public NotInheritable Class MASDashboardGridWidgetSnapshot
        Public Sub New(key As String,
                       title As String,
                       valueText As String,
                       caption As String,
                       spanColumns As Integer,
                       spanRows As Integer)
            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)
        End Sub

        Public ReadOnly Property Key As String
        Public ReadOnly Property Title As String
        Public ReadOnly Property ValueText As String
        Public ReadOnly Property Caption As String
        Public ReadOnly Property SpanColumns As Integer
        Public ReadOnly Property SpanRows As Integer
    End Class

    Partial Public NotInheritable Class MASDashboardGrid

        Public Function CreateLayoutSnapshot() As MASDashboardGridLayoutSnapshot
            Dim widgets As New List(Of MASDashboardGridWidgetSnapshot)()
            For Each widget As DashboardWidgetState In _widgets
                widgets.Add(New MASDashboardGridWidgetSnapshot(
                    widget.Key,
                    widget.Title,
                    widget.ValueText,
                    widget.Caption,
                    widget.SpanColumns,
                    widget.SpanRows))
            Next
            Return New MASDashboardGridLayoutSnapshot(1, _title, SelectedWidgetKey, _topIndex, widgets)
        End Function

        Public Function RestoreLayoutSnapshot(snapshot As MASDashboardGridLayoutSnapshot) As Boolean
            Return RestoreLayoutSnapshotCore(snapshot, DashboardGridTokens.MaxWidgets, "MASDashboardGrid.RestoreLayoutSnapshot")
        End Function

        Private Function RestoreLayoutSnapshotCore(snapshot As MASDashboardGridLayoutSnapshot,
                                                   maxWidgets As Integer,
                                                   refreshReason As String) As Boolean
            If snapshot Is Nothing Then Return False
            Dim capacity As Integer = Math.Max(0, maxWidgets)
            _widgets.Clear()
            _widgetIndexByKey.Clear()
            _hits.Clear()
            _hoverIndex = -1
            _pressedIndex = -1
            For Each widget As MASDashboardGridWidgetSnapshot In snapshot.Widgets
                If widget Is Nothing Then Continue For
                If _widgets.Count >= capacity Then Exit For
                If _widgetIndexByKey.ContainsKey(widget.Key) Then Continue For
                _widgetIndexByKey.Add(widget.Key, _widgets.Count)
                _widgets.Add(New DashboardWidgetState(
                    widget.Key,
                    widget.Title,
                    widget.ValueText,
                    widget.Caption,
                    widget.SpanColumns,
                    widget.SpanRows))
            Next
            _title = MASDashboardGridPolicy.NormalizeTitle(snapshot.Title)
            _selectedIndex = FindWidgetIndex(snapshot.SelectedWidgetKey)
            If _selectedIndex < 0 AndAlso _widgets.Count > 0 Then _selectedIndex = 0
            _topIndex = Math.Min(Math.Max(0, snapshot.TopIndex), ResolveWidgetViewportMaxTop())
            _topIndex = MASDashboardGridPolicy.EnsureVisibleIndex(_selectedIndex, _topIndex, _widgets.Count, Math.Max(1, _lastVisibleCount))
            RequestSizeLayoutRefreshForSizeAffectingChange(If(String.IsNullOrWhiteSpace(refreshReason), "MASDashboardGrid.RestoreLayoutSnapshot", refreshReason))
            InvalidateVisual()
            RaiseDashboardGridChangedSafe()
            RaiseSelectedWidgetChangedSafe()
            Return True
        End Function

        Public Function MoveWidget(widgetKey As String,
                                   targetIndex As Integer) As Boolean
            Dim sourceIndex As Integer = FindWidgetIndex(widgetKey)
            If sourceIndex < 0 Then Return False
            Dim normalizedTarget As Integer = MASDashboardGridPolicy.ClampIndex(targetIndex, _widgets.Count)
            If normalizedTarget < 0 OrElse normalizedTarget = sourceIndex Then Return False
            Dim selectedKey As String = SelectedWidgetKey
            Dim widget As DashboardWidgetState = _widgets(sourceIndex)
            _widgets.RemoveAt(sourceIndex)
            _widgets.Insert(normalizedTarget, widget)
            RebuildWidgetIndex()
            _selectedIndex = FindWidgetIndex(selectedKey)
            _topIndex = MASDashboardGridPolicy.EnsureVisibleIndex(_selectedIndex, _topIndex, _widgets.Count, Math.Max(1, _lastVisibleCount))
            RequestSizeLayoutRefreshForSizeAffectingChange("MASDashboardGrid.MoveWidget")
            InvalidateVisual()
            RaiseDashboardGridChangedSafe()
            Return True
        End Function

        Public Function ResizeWidget(widgetKey As String,
                                     spanColumns As Integer,
                                     spanRows As Integer) As Boolean
            Dim index As Integer = FindWidgetIndex(widgetKey)
            If index < 0 Then Return False
            Dim widget As DashboardWidgetState = _widgets(index)
            Dim columns As Integer = MASDashboardGridPolicy.ClampSpan(spanColumns, DashboardGridTokens.MaxSpanColumns)
            Dim rows As Integer = MASDashboardGridPolicy.ClampSpan(spanRows, DashboardGridTokens.MaxSpanRows)
            If widget.SpanColumns = columns AndAlso widget.SpanRows = rows Then Return False
            widget.SpanColumns = columns
            widget.SpanRows = rows
            RequestSizeLayoutRefreshForSizeAffectingChange("MASDashboardGrid.ResizeWidget")
            InvalidateVisual()
            RaiseDashboardGridChangedSafe()
            Return True
        End Function

        Public Function ResetWidgetViewport() As MASDashboardGrid
            _topIndex = 0
            If _widgets.Count > 0 Then _selectedIndex = MASDashboardGridPolicy.ClampIndex(_selectedIndex, _widgets.Count)
            InvalidateVisual()
            Return Me
        End Function

        Friend Function HasLayoutSnapshotForTests() As Boolean
            Dim snapshot As MASDashboardGridLayoutSnapshot = CreateLayoutSnapshot()
            Return snapshot IsNot Nothing AndAlso snapshot.Widgets.Count = _widgets.Count
        End Function

        Private Sub RebuildWidgetIndex()
            _widgetIndexByKey.Clear()
            For i As Integer = 0 To _widgets.Count - 1
                Dim key As String = _widgets(i).Key
                If Not _widgetIndexByKey.ContainsKey(key) Then _widgetIndexByKey.Add(key, i)
            Next
        End Sub
    End Class

End Namespace
