Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Friend-only immutable snapshot of the realized master-item window. It is used
    ''' by governance gates to prove large master lists stay bounded by the visible
    ''' viewport instead of treating every projected item as realized UI.
    ''' </summary>
    Friend NotInheritable Class MASMasterDetailItemWindowSnapshot
        Friend Sub New(version As Integer,
                       firstVisibleItemIndex As Integer,
                       lastVisibleItemIndex As Integer,
                       requestedViewportCapacity As Integer,
                       realizedItemCount As Integer,
                       totalItemCount As Integer,
                       selectedItemKey As String,
                       sourceRevision As Integer,
                       realizedItemKeys As IEnumerable(Of String))
            Me.Version = Math.Max(1, version)
            Me.FirstVisibleItemIndex = Math.Max(0, firstVisibleItemIndex)
            Me.LastVisibleItemIndex = lastVisibleItemIndex
            Me.RequestedViewportCapacity = Math.Max(1, requestedViewportCapacity)
            Me.RealizedItemCount = Math.Max(0, realizedItemCount)
            Me.TotalItemCount = Math.Max(0, totalItemCount)
            Me.SelectedItemKey = MASMasterDetailViewPolicy.NormalizeText(selectedItemKey)
            Me.SourceRevision = sourceRevision

            Dim copy As New List(Of String)()
            If realizedItemKeys IsNot Nothing Then
                For Each key As String In realizedItemKeys
                    Dim normalized As String = MASMasterDetailViewPolicy.NormalizeText(key)
                    If normalized.Length > 0 Then copy.Add(normalized)
                Next
            End If
            Me.RealizedItemKeys = New ReadOnlyCollection(Of String)(copy)
        End Sub

        Friend ReadOnly Property Version As Integer
        Friend ReadOnly Property FirstVisibleItemIndex As Integer
        Friend ReadOnly Property LastVisibleItemIndex As Integer
        Friend ReadOnly Property RequestedViewportCapacity As Integer
        Friend ReadOnly Property RealizedItemCount As Integer
        Friend ReadOnly Property TotalItemCount As Integer
        Friend ReadOnly Property SelectedItemKey As String
        Friend ReadOnly Property SourceRevision As Integer
        Friend ReadOnly Property RealizedItemKeys As IReadOnlyList(Of String)

        Friend ReadOnly Property HasBoundedItemWindow As Boolean
            Get
                Return RealizedItemCount <= RequestedViewportCapacity AndAlso
                       RealizedItemCount = RealizedItemKeys.Count AndAlso
                       RealizedItemCount <= Math.Max(1, MasterDetailViewTokens.MaxVisibleItems)
            End Get
        End Property

        Friend ReadOnly Property CoversPartialList As Boolean
            Get
                Return TotalItemCount > RealizedItemCount AndAlso TotalItemCount > RequestedViewportCapacity
            End Get
        End Property
    End Class

    Partial Public NotInheritable Class MASMasterDetailView

        Friend Function CreateMasterItemWindowSnapshot(Optional requestedViewportCapacity As Integer = 0) As MASMasterDetailItemWindowSnapshot
            Dim capacity As Integer = ResolveDiagnosticMasterItemViewportCapacity(requestedViewportCapacity)
            Dim total As Integer = _items.Count
            Dim first As Integer = If(total <= 0, 0, Math.Max(0, Math.Min(_topIndex, Math.Max(0, total - 1))))
            Dim last As Integer = If(total <= 0, -1, Math.Min(total - 1, first + capacity - 1))
            Dim keys As New List(Of String)()
            If total > 0 AndAlso last >= first Then
                For index As Integer = first To last
                    keys.Add(_items(index).Key)
                Next
            End If

            Return New MASMasterDetailItemWindowSnapshot(
                1,
                first,
                last,
                capacity,
                keys.Count,
                total,
                SelectedItemKey,
                _masterDetailSourceRevision,
                keys)
        End Function

        Friend Function GetRealizedMasterItemKeys(Optional requestedViewportCapacity As Integer = 0) As IReadOnlyList(Of String)
            Return CreateMasterItemWindowSnapshot(requestedViewportCapacity).RealizedItemKeys
        End Function

        Friend Function EnsureMasterItemVisible(itemKey As String,
                                                Optional requestedViewportCapacity As Integer = 0) As Boolean
            Dim index As Integer = FindItemIndex(itemKey)
            If index < 0 Then Return False
            Dim capacity As Integer = ResolveDiagnosticMasterItemViewportCapacity(requestedViewportCapacity)
            _topIndex = MASMasterDetailViewPolicy.EnsureVisibleIndex(index, _topIndex, _items.Count, capacity)
            _lastVisibleCount = capacity
            InvalidateVisual()
            Return True
        End Function

        Friend Function SetDiagnosticMasterItemViewportCapacity(capacity As Integer) As Boolean
            If capacity <= 0 Then Return False
            _lastVisibleCount = Math.Max(1, Math.Min(MasterDetailViewTokens.MaxVisibleItems, capacity))
            NormalizeViewState()
            InvalidateVisual()
            Return True
        End Function

        Friend Function HasBoundedMasterItemWindowForTests() As Boolean
            Dim records As New List(Of Object)()
            For index As Integer = 0 To MasterDetailViewTokens.LargeListProofItems - 1
                records.Add(MASMasterDetailSourceRecord.Create(
                    title:="Item " & index.ToString(System.Globalization.CultureInfo.InvariantCulture),
                    key:="item-" & index.ToString(System.Globalization.CultureInfo.InvariantCulture),
                    subtitle:="Source row " & index.ToString(System.Globalization.CultureInfo.InvariantCulture),
                    detailTitle:="Detail " & index.ToString(System.Globalization.CultureInfo.InvariantCulture),
                    detail:="Large master/detail proof body " & index.ToString(System.Globalization.CultureInfo.InvariantCulture),
                    badgeText:=index.ToString(System.Globalization.CultureInfo.InvariantCulture)))
            Next

            Dim source As MASListItemsSource = MASListItemsSource.FromEnumerable(records, AddressOf ResolveMasterDetailVirtualizationProbeKey)
            Dim definition As MASMasterDetailSourceDefinition = MASMasterDetailSourceDefinition.Create(
                keyField:="key",
                titleField:="title",
                subtitleField:="subtitle",
                detailTitleField:="detailTitle",
                detailTextField:="detail",
                badgeTextField:="badge")

            SetMasterDetailSource(source, definition, "MASMasterDetailView.ItemVirtualizationProof")
            If Not HasMasterDetailSource Then Return False
            If ItemCount <> MasterDetailViewTokens.LargeListProofItems Then Return False
            If MasterDetailSourceRevision < 0 Then Return False
            If Not SetDiagnosticMasterItemViewportCapacity(MasterDetailViewTokens.MaxVisibleItems) Then Return False

            Dim first As MASMasterDetailItemWindowSnapshot = CreateMasterItemWindowSnapshot()
            If first Is Nothing OrElse Not first.HasBoundedItemWindow OrElse Not first.CoversPartialList Then Return False
            If first.RealizedItemCount > MasterDetailViewTokens.MaxVisibleItems Then Return False
            If first.TotalItemCount <> MasterDetailViewTokens.LargeListProofItems Then Return False
            If first.RealizedItemKeys.Count = 0 OrElse first.RealizedItemKeys(0) <> "item-0" Then Return False

            If Not SelectItem("item-500") Then Return False
            Dim middle As MASMasterDetailItemWindowSnapshot = CreateMasterItemWindowSnapshot()
            If middle Is Nothing OrElse Not middle.HasBoundedItemWindow OrElse Not middle.CoversPartialList Then Return False
            If Not ContainsKey(middle.RealizedItemKeys, "item-500") Then Return False
            If middle.SelectedItemKey <> "item-500" Then Return False

            If Not EnsureMasterItemVisible("item-999") Then Return False
            Dim last As MASMasterDetailItemWindowSnapshot = CreateMasterItemWindowSnapshot()
            If last Is Nothing OrElse Not last.HasBoundedItemWindow OrElse Not last.CoversPartialList Then Return False
            If Not ContainsKey(last.RealizedItemKeys, "item-999") Then Return False
            If last.TotalItemCount <> MasterDetailViewTokens.LargeListProofItems Then Return False

            source.ReplaceItems(records, "MASMasterDetailView.ItemVirtualizationReplace")
            If ItemCount <> MasterDetailViewTokens.LargeListProofItems Then Return False
            If SelectedItemKey <> "item-500" Then Return False
            Dim refreshed As MASMasterDetailItemWindowSnapshot = CreateMasterItemWindowSnapshot()
            If refreshed Is Nothing OrElse Not refreshed.HasBoundedItemWindow OrElse refreshed.SourceRevision < 0 Then Return False

            Return True
        End Function

        Private Function ResolveDiagnosticMasterItemViewportCapacity(requestedViewportCapacity As Integer) As Integer
            Dim requested As Integer = If(requestedViewportCapacity > 0, requestedViewportCapacity, _lastVisibleCount)
            Return Math.Max(1, Math.Min(MasterDetailViewTokens.MaxVisibleItems, requested))
        End Function

        Private Shared Function ContainsKey(keys As IReadOnlyList(Of String),
                                            key As String) As Boolean
            If keys Is Nothing Then Return False
            For Each itemKey As String In keys
                If String.Equals(itemKey, key, StringComparison.Ordinal) Then Return True
            Next
            Return False
        End Function

        Private Shared Function ResolveMasterDetailVirtualizationProbeKey(item As Object) As String
            Dim record As MASMasterDetailSourceRecord = TryCast(item, MASMasterDetailSourceRecord)
            If record Is Nothing Then Return String.Empty
            Dim value As Object = Nothing
            If record.TryGetField("key", value) Then Return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)
            Return String.Empty
        End Function

    End Class

End Namespace
