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>
    ''' Friend-only Kanban board-window snapshot. It proves the control can keep a
    ''' large stable source projection while exposing only a bounded realized column
    ''' and card window to rendering/input diagnostics. It is not a task store, not a
    ''' workflow engine, not an external virtualizer, and not a public binding API.
    ''' </summary>
    Friend NotInheritable Class MASKanbanBoardWindowSnapshot

        Private ReadOnly _cardKeys As IReadOnlyList(Of String)

        Friend Sub New(firstColumnIndex As Integer,
                       lastColumnIndex As Integer,
                       totalColumns As Integer,
                       totalCards As Integer,
                       requestedColumnCapacity As Integer,
                       requestedCardCapacityPerColumn As Integer,
                       projectionRevision As Integer,
                       cardKeys As IEnumerable(Of String))
            Me.FirstColumnIndex = Math.Max(0, firstColumnIndex)
            Me.LastColumnIndex = lastColumnIndex
            Me.TotalColumns = Math.Max(0, totalColumns)
            Me.TotalCards = Math.Max(0, totalCards)
            Me.RequestedColumnCapacity = Math.Max(1, requestedColumnCapacity)
            Me.RequestedCardCapacityPerColumn = Math.Max(1, requestedCardCapacityPerColumn)
            Me.ProjectionRevision = projectionRevision

            Dim normalized As New List(Of String)()
            If cardKeys IsNot Nothing Then
                For Each key As String In cardKeys
                    Dim trimmed As String = If(key, String.Empty).Trim()
                    If trimmed.Length > 0 Then normalized.Add(trimmed)
                Next
            End If
            _cardKeys = New ReadOnlyCollection(Of String)(normalized.ToArray())
        End Sub

        Friend ReadOnly Property FirstColumnIndex As Integer
        Friend ReadOnly Property LastColumnIndex As Integer
        Friend ReadOnly Property TotalColumns As Integer
        Friend ReadOnly Property TotalCards As Integer
        Friend ReadOnly Property RequestedColumnCapacity As Integer
        Friend ReadOnly Property RequestedCardCapacityPerColumn As Integer
        Friend ReadOnly Property ProjectionRevision As Integer

        Friend ReadOnly Property RealizedCardKeys As IReadOnlyList(Of String)
            Get
                Return _cardKeys
            End Get
        End Property

        Friend ReadOnly Property RealizedColumnCount As Integer
            Get
                If TotalColumns <= 0 OrElse LastColumnIndex < FirstColumnIndex Then Return 0
                Return LastColumnIndex - FirstColumnIndex + 1
            End Get
        End Property

        Friend ReadOnly Property RealizedCardCount As Integer
            Get
                Return _cardKeys.Count
            End Get
        End Property

        Friend ReadOnly Property RequestedCardCapacity As Integer
            Get
                Return RequestedColumnCapacity * RequestedCardCapacityPerColumn
            End Get
        End Property

        Friend ReadOnly Property IsEmpty As Boolean
            Get
                Return TotalColumns <= 0 OrElse TotalCards <= 0 OrElse RealizedCardCount <= 0
            End Get
        End Property

        Friend ReadOnly Property IsBounded As Boolean
            Get
                Return RealizedColumnCount <= RequestedColumnCapacity AndAlso RealizedCardCount <= RequestedCardCapacity
            End Get
        End Property

        Friend ReadOnly Property CoversPartialBoard As Boolean
            Get
                Return TotalColumns > RealizedColumnCount OrElse TotalCards > RealizedCardCount
            End Get
        End Property

        Friend Function ContainsCardKey(cardKey As String) As Boolean
            Dim normalized As String = If(cardKey, String.Empty).Trim()
            If normalized.Length = 0 Then Return False
            For Each key As String In _cardKeys
                If String.Equals(key, normalized, StringComparison.OrdinalIgnoreCase) Then Return True
            Next
            Return False
        End Function

    End Class

    Partial Public NotInheritable Class MASKanbanBoard

#Region "Friend board-window diagnostics"

        Private _diagnosticColumnViewportCapacity As Integer = KanbanBoardTokens.MaxVisibleColumns
        Private _diagnosticCardViewportCapacityPerColumn As Integer = KanbanBoardTokens.MaxVisibleCardsPerColumn

        Friend ReadOnly Property BoardProjectionRevision As Integer
            Get
                Return _kanbanSourceRevision
            End Get
        End Property

        Friend Function CreateBoardWindowSnapshot(Optional requestedColumnCapacity As Integer = 0,
                                                  Optional requestedCardCapacityPerColumn As Integer = 0) As MASKanbanBoardWindowSnapshot
            Dim columnCapacity As Integer = ResolveDiagnosticColumnCapacity(requestedColumnCapacity)
            Dim cardCapacity As Integer = ResolveDiagnosticCardCapacity(requestedCardCapacityPerColumn)

            If _columns.Count <= 0 Then
                Return New MASKanbanBoardWindowSnapshot(0, -1, 0, 0, columnCapacity, cardCapacity, _kanbanSourceRevision, Array.Empty(Of String)())
            End If

            Dim firstColumn As Integer = Math.Max(0, Math.Min(_leftColumnIndex, _columns.Count - 1))
            Dim lastColumn As Integer = Math.Min(_columns.Count - 1, firstColumn + columnCapacity - 1)
            Dim keys As New List(Of String)()

            For columnIndex As Integer = firstColumn To lastColumn
                Dim column As KanbanColumnState = _columns(columnIndex)
                If column Is Nothing Then Continue For

                Dim firstCard As Integer = Math.Max(0, Math.Min(column.TopCardIndex, Math.Max(0, column.Cards.Count - 1)))
                Dim lastCard As Integer = Math.Min(column.Cards.Count - 1, firstCard + cardCapacity - 1)
                If lastCard < firstCard Then Continue For

                For cardIndex As Integer = firstCard To lastCard
                    Dim card As KanbanCardState = column.Cards(cardIndex)
                    If card IsNot Nothing Then keys.Add(card.Key)
                Next
            Next

            Return New MASKanbanBoardWindowSnapshot(firstColumn,
                                                    lastColumn,
                                                    _columns.Count,
                                                    _cardCount,
                                                    columnCapacity,
                                                    cardCapacity,
                                                    _kanbanSourceRevision,
                                                    keys)
        End Function

        Friend Function GetRealizedCardKeys(Optional requestedColumnCapacity As Integer = 0,
                                            Optional requestedCardCapacityPerColumn As Integer = 0) As IReadOnlyList(Of String)
            Return CreateBoardWindowSnapshot(requestedColumnCapacity, requestedCardCapacityPerColumn).RealizedCardKeys
        End Function

        Friend Function HasBoundedBoardWindow(totalCardThreshold As Integer,
                                              maxRealizedColumns As Integer,
                                              maxRealizedCardsPerColumn As Integer) As Boolean
            Dim snapshot As MASKanbanBoardWindowSnapshot = CreateBoardWindowSnapshot(maxRealizedColumns, maxRealizedCardsPerColumn)
            Return snapshot.TotalCards >= Math.Max(1, totalCardThreshold) AndAlso
                   snapshot.RealizedColumnCount <= Math.Max(1, maxRealizedColumns) AndAlso
                   snapshot.RealizedCardCount <= Math.Max(1, maxRealizedColumns) * Math.Max(1, maxRealizedCardsPerColumn) AndAlso
                   snapshot.CoversPartialBoard
        End Function

        Friend Function SetDiagnosticColumnViewportCapacity(capacity As Integer) As Boolean
            Dim normalized As Integer = Math.Max(1, Math.Min(Math.Max(1, capacity), KanbanBoardTokens.MaxColumns))
            If _diagnosticColumnViewportCapacity = normalized Then Return True
            _diagnosticColumnViewportCapacity = normalized
            _leftColumnIndex = Math.Max(0, Math.Min(_leftColumnIndex, Math.Max(0, _columns.Count - 1)))
            InvalidateVisual()
            Return True
        End Function

        Friend Function SetDiagnosticCardViewportCapacity(capacity As Integer) As Boolean
            Dim normalized As Integer = Math.Max(1, Math.Min(Math.Max(1, capacity), KanbanBoardTokens.LargeBoardProofCardsPerColumn))
            If _diagnosticCardViewportCapacityPerColumn = normalized Then Return True
            _diagnosticCardViewportCapacityPerColumn = normalized
            For Each column As KanbanColumnState In _columns
                If column IsNot Nothing Then column.TopCardIndex = Math.Max(0, Math.Min(column.TopCardIndex, ResolveColumnMaxTopForCapacity(column, normalized)))
            Next
            InvalidateVisual()
            Return True
        End Function

        Friend Function EnsureCardVisible(cardKey As String) As Boolean
            Dim location As Tuple(Of Integer, Integer) = FindCardLocation(cardKey)
            If location Is Nothing Then Return False
            If location.Item1 < 0 OrElse location.Item1 >= _columns.Count Then Return False

            _selectedColumnIndex = location.Item1
            _selectedCardIndex = location.Item2

            Dim columnCapacity As Integer = ResolveDiagnosticColumnCapacity(0)
            Dim maxLeft As Integer = Math.Max(0, _columns.Count - columnCapacity)
            If _selectedColumnIndex < _leftColumnIndex Then
                _leftColumnIndex = Math.Max(0, _selectedColumnIndex)
            ElseIf _selectedColumnIndex >= _leftColumnIndex + columnCapacity Then
                _leftColumnIndex = Math.Max(0, Math.Min(maxLeft, _selectedColumnIndex - columnCapacity + 1))
            Else
                _leftColumnIndex = Math.Max(0, Math.Min(_leftColumnIndex, maxLeft))
            End If

            Dim column As KanbanColumnState = _columns(_selectedColumnIndex)
            Dim cardCapacity As Integer = ResolveDiagnosticCardCapacity(0)
            Dim maxTop As Integer = ResolveColumnMaxTopForCapacity(column, cardCapacity)
            If _selectedCardIndex < column.TopCardIndex Then
                column.TopCardIndex = Math.Max(0, _selectedCardIndex)
            ElseIf _selectedCardIndex >= column.TopCardIndex + cardCapacity Then
                column.TopCardIndex = Math.Max(0, Math.Min(maxTop, _selectedCardIndex - cardCapacity + 1))
            Else
                column.TopCardIndex = Math.Max(0, Math.Min(column.TopCardIndex, maxTop))
            End If

            InvalidateVisual()
            RaiseSelectedCardChangedSafe()
            Return True
        End Function

        Private Function ResolveDiagnosticColumnCapacity(requestedCapacity As Integer) As Integer
            If requestedCapacity > 0 Then Return Math.Max(1, Math.Min(requestedCapacity, Math.Max(1, _columns.Count)))
            Return Math.Max(1, Math.Min(_diagnosticColumnViewportCapacity, Math.Max(1, _columns.Count)))
        End Function

        Private Function ResolveDiagnosticCardCapacity(requestedCapacity As Integer) As Integer
            If requestedCapacity > 0 Then Return Math.Max(1, requestedCapacity)
            Return Math.Max(1, _diagnosticCardViewportCapacityPerColumn)
        End Function

        Private Shared Function ResolveColumnMaxTopForCapacity(column As KanbanColumnState,
                                                               capacity As Integer) As Integer
            If column Is Nothing Then Return 0
            Return Math.Max(0, column.Cards.Count - Math.Max(1, capacity))
        End Function

#End Region

    End Class

End Namespace
