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>
    ''' One realized agenda event in a bounded visible-range diagnostic window. It is
    ''' not a public calendar model and intentionally does not expose recurrence,
    ''' time-zone conversion, reminders, or provider metadata.
    ''' </summary>
    Friend NotInheritable Class MASAgendaVisibleEventWindowEntry

        Friend Sub New(eventKey As String,
                       eventDay As Date,
                       startMinute As Integer,
                       endMinute As Integer)
            Me.EventKey = Normalize(eventKey)
            Me.EventDay = MASAgendaViewPolicy.NormalizeDay(eventDay)
            Me.StartMinute = Math.Max(0, Math.Min(1439, startMinute))
            Me.EndMinute = Math.Max(0, Math.Min(1439, endMinute))
        End Sub

        Friend ReadOnly Property EventKey As String
        Friend ReadOnly Property EventDay As Date
        Friend ReadOnly Property StartMinute As Integer
        Friend ReadOnly Property EndMinute As Integer

        Friend ReadOnly Property IsValid As Boolean
            Get
                Return EventKey.Length > 0 AndAlso EndMinute > StartMinute
            End Get
        End Property

        Private Shared Function Normalize(value As String) As String
            If value Is Nothing Then Return String.Empty
            Return value.Trim()
        End Function

    End Class

    ''' <summary>
    ''' Friend-only Agenda visible-range window snapshot. It proves MASAgendaView can
    ''' hold a larger source projection while diagnostics realize a bounded subset of
    ''' the active day/week/month range. It is not a scheduler, recurrence engine,
    ''' provider adapter, or production marketing promotion.
    ''' </summary>
    Friend NotInheritable Class MASAgendaVisibleRangeWindowSnapshot

        Private ReadOnly _events As IReadOnlyList(Of MASAgendaVisibleEventWindowEntry)

        Friend Sub New(mode As MASAgendaViewMode,
                       rangeStart As Date,
                       rangeEnd As Date,
                       firstVisiblePosition As Integer,
                       lastVisiblePosition As Integer,
                       totalEvents As Integer,
                       visibleEventCount As Integer,
                       requestedEventCapacity As Integer,
                       projectionRevision As Integer,
                       events As IEnumerable(Of MASAgendaVisibleEventWindowEntry))
            Me.Mode = MASAgendaViewPolicy.NormalizeViewMode(mode)
            Me.RangeStart = MASAgendaViewPolicy.NormalizeDay(rangeStart)
            Me.RangeEnd = MASAgendaViewPolicy.NormalizeDay(rangeEnd)
            Me.FirstVisiblePosition = Math.Max(0, firstVisiblePosition)
            Me.LastVisiblePosition = lastVisiblePosition
            Me.TotalEvents = Math.Max(0, totalEvents)
            Me.VisibleEventCount = Math.Max(0, visibleEventCount)
            Me.RequestedEventCapacity = Math.Max(1, requestedEventCapacity)
            Me.ProjectionRevision = projectionRevision

            Dim normalized As New List(Of MASAgendaVisibleEventWindowEntry)()
            If events IsNot Nothing Then
                For Each item As MASAgendaVisibleEventWindowEntry In events
                    If item IsNot Nothing AndAlso item.IsValid Then normalized.Add(item)
                Next
            End If
            _events = New ReadOnlyCollection(Of MASAgendaVisibleEventWindowEntry)(normalized.ToArray())
        End Sub

        Friend ReadOnly Property Mode As MASAgendaViewMode
        Friend ReadOnly Property RangeStart As Date
        Friend ReadOnly Property RangeEnd As Date
        Friend ReadOnly Property FirstVisiblePosition As Integer
        Friend ReadOnly Property LastVisiblePosition As Integer
        Friend ReadOnly Property TotalEvents As Integer
        Friend ReadOnly Property VisibleEventCount As Integer
        Friend ReadOnly Property RequestedEventCapacity As Integer
        Friend ReadOnly Property ProjectionRevision As Integer

        Friend ReadOnly Property RealizedEvents As IReadOnlyList(Of MASAgendaVisibleEventWindowEntry)
            Get
                Return _events
            End Get
        End Property

        Friend ReadOnly Property RealizedEventCount As Integer
            Get
                Return _events.Count
            End Get
        End Property

        Friend ReadOnly Property IsEmpty As Boolean
            Get
                Return TotalEvents <= 0 OrElse VisibleEventCount <= 0 OrElse RealizedEventCount <= 0
            End Get
        End Property

        Friend ReadOnly Property IsBounded As Boolean
            Get
                Return RealizedEventCount <= RequestedEventCapacity AndAlso RealizedEventCount <= VisibleEventCount
            End Get
        End Property

        Friend ReadOnly Property CoversPartialAgenda As Boolean
            Get
                Return TotalEvents > RealizedEventCount OrElse VisibleEventCount > RealizedEventCount
            End Get
        End Property

        Friend Function ContainsEvent(eventKey As String) As Boolean
            Dim normalizedKey As String = If(eventKey, String.Empty).Trim()
            If normalizedKey.Length = 0 Then Return False
            For Each item As MASAgendaVisibleEventWindowEntry In _events
                If item IsNot Nothing AndAlso String.Equals(item.EventKey, normalizedKey, StringComparison.OrdinalIgnoreCase) Then Return True
            Next
            Return False
        End Function

    End Class

    Partial Public NotInheritable Class MASAgendaView

#Region "Friend visible-range diagnostics"

        Private _diagnosticVisibleEventCapacity As Integer = AgendaViewTokens.MaxVisibleEvents

        Friend ReadOnly Property AgendaProjectionRevision As Integer
            Get
                Return _agendaSourceRevision
            End Get
        End Property

        Friend Function CreateVisibleRangeWindowSnapshot(Optional requestedEventCapacity As Integer = 0) As MASAgendaVisibleRangeWindowSnapshot
            Dim capacity As Integer = ResolveDiagnosticVisibleEventCapacity(requestedEventCapacity)
            Dim visible As List(Of Integer) = GetVisibleEventIndicesForCurrentView()
            Dim rangeStart As Date = MASAgendaViewPolicy.ResolveRangeStart(_day, _viewMode)
            Dim rangeEnd As Date = MASAgendaViewPolicy.ResolveRangeEndInclusive(_day, _viewMode)

            If visible.Count <= 0 Then
                Return New MASAgendaVisibleRangeWindowSnapshot(_viewMode,
                                                               rangeStart,
                                                               rangeEnd,
                                                               0,
                                                               -1,
                                                               _events.Count,
                                                               0,
                                                               capacity,
                                                               _agendaSourceRevision,
                                                               Array.Empty(Of MASAgendaVisibleEventWindowEntry)())
            End If

            Dim firstPosition As Integer = Math.Max(0, Math.Min(_topEventIndex, visible.Count - 1))
            Dim lastPosition As Integer = Math.Min(visible.Count - 1, firstPosition + capacity - 1)
            Dim realized As New List(Of MASAgendaVisibleEventWindowEntry)()
            For position As Integer = firstPosition To lastPosition
                Dim eventIndex As Integer = visible(position)
                If eventIndex < 0 OrElse eventIndex >= _events.Count Then Continue For
                Dim item As AgendaEventState = _events(eventIndex)
                If item IsNot Nothing Then realized.Add(New MASAgendaVisibleEventWindowEntry(item.Key, item.EventDay, item.StartMinute, item.EndMinute))
            Next

            Return New MASAgendaVisibleRangeWindowSnapshot(_viewMode,
                                                           rangeStart,
                                                           rangeEnd,
                                                           firstPosition,
                                                           lastPosition,
                                                           _events.Count,
                                                           visible.Count,
                                                           capacity,
                                                           _agendaSourceRevision,
                                                           realized)
        End Function

        Friend Function GetRealizedAgendaEventKeys(Optional requestedEventCapacity As Integer = 0) As IReadOnlyList(Of String)
            Dim snapshot As MASAgendaVisibleRangeWindowSnapshot = CreateVisibleRangeWindowSnapshot(requestedEventCapacity)
            Dim keys As New List(Of String)()
            For Each item As MASAgendaVisibleEventWindowEntry In snapshot.RealizedEvents
                If item IsNot Nothing Then keys.Add(item.EventKey)
            Next
            Return New ReadOnlyCollection(Of String)(keys)
        End Function

        Friend Function HasBoundedVisibleRangeWindow(Optional requestedEventCapacity As Integer = 0) As Boolean
            Dim snapshot As MASAgendaVisibleRangeWindowSnapshot = CreateVisibleRangeWindowSnapshot(requestedEventCapacity)
            Return snapshot.IsBounded
        End Function

        Friend Function SetDiagnosticVisibleEventCapacity(capacity As Integer) As Boolean
            If capacity <= 0 Then Return False
            _diagnosticVisibleEventCapacity = Math.Max(1, Math.Min(capacity, AgendaViewTokens.LargeRangeProofEvents))
            _topEventIndex = MASAgendaViewPolicy.EnsureVisibleEventPosition(_topEventIndex, _topEventIndex, GetVisibleEventCountForCurrentView(), _diagnosticVisibleEventCapacity)
            Return True
        End Function

        Friend Function EnsureEventVisible(eventKey As String) As Boolean
            Dim eventIndex As Integer = FindEventIndex(eventKey)
            If eventIndex < 0 OrElse eventIndex >= _events.Count Then Return False
            Dim item As AgendaEventState = _events(eventIndex)
            If item Is Nothing Then Return False

            If Not IsEventVisibleInCurrentView(item) Then _day = MASAgendaViewPolicy.NormalizeDay(item.EventDay)

            Dim visible As List(Of Integer) = GetVisibleEventIndicesForCurrentView()
            Dim position As Integer = visible.IndexOf(eventIndex)
            If position < 0 Then Return False
            _selectedEventIndex = eventIndex
            _topEventIndex = MASAgendaViewPolicy.EnsureVisibleEventPosition(position, _topEventIndex, visible.Count, _diagnosticVisibleEventCapacity)
            ResetPointerState()
            InvalidateVisual()
            Return True
        End Function

        Private Function ResolveDiagnosticVisibleEventCapacity(requestedEventCapacity As Integer) As Integer
            Dim capacity As Integer = If(requestedEventCapacity > 0, requestedEventCapacity, _diagnosticVisibleEventCapacity)
            If capacity <= 0 Then capacity = AgendaViewTokens.MaxVisibleEvents
            Return Math.Max(1, Math.Min(capacity, AgendaViewTokens.LargeRangeProofEvents))
        End Function

#End Region

    End Class

End Namespace
