Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Governance proof that MASAgendaView can project a larger internal agenda
    ''' source while keeping day/week/month diagnostics bounded to a visible-range
    ''' window. This gate deliberately avoids recurrence, time-zone engines,
    ''' reminder transports, external providers, and public adapter commitments.
    ''' </summary>
    Friend NotInheritable Class MASAgendaViewSourceRangeWindowingGate
        Private Sub New()
        End Sub

        Friend Shared Function Passes() As Boolean
            Return Run().Passed
        End Function

        Friend Shared Function Run() As MASAgendaViewSourceRangeWindowingGateResult
            Dim failures As New List(Of String)()
            Try
                If Not MASAgendaViewPolicy.HasAgendaSourceProjectionPath() Then failures.Add("Agenda source projection path is missing.")
                If Not MASAgendaViewPolicy.HasVisibleRangeWindowingProofPath() Then failures.Add("Agenda visible range-window proof path is missing.")

                Dim anchor As New DateTime(2026, 1, 1, 9, 0, 0)
                Dim records As List(Of MASAgendaSourceRecord) = BuildRecords(anchor.Date)
                Dim source As MASListItemsSource = MASListItemsSource.FromEnumerable(records, AddressOf ResolveProbeKey)
                Dim definition As MASAgendaSourceDefinition = MASAgendaSourceDefinition.Create("title", "start", "end", "key", "detail")
                Dim agenda As MASAgendaView = MASAgendaView.Create("Proof agenda", anchor.Date).WithHours(8, 18).WithViewMode(MASAgendaViewMode.Day)
                agenda.SetNowProviderForTests(Function() anchor)
                agenda.SetAgendaSource(source, definition, "MASAgendaViewSourceRangeWindowingGate.Initial")

                Dim expectedTotal As Integer = AgendaViewTokens.LargeRangeProofDays * AgendaViewTokens.LargeRangeProofEventsPerDay
                If Not agenda.HasAgendaSource Then failures.Add("Agenda did not retain its source link.")
                If agenda.AgendaSourceRevision <> source.Revision Then failures.Add("Agenda source revision was not retained.")
                If agenda.EventCount <> expectedTotal Then failures.Add("Projected event count mismatch: expected " & expectedTotal.ToString(CultureInfo.InvariantCulture) & ", got " & agenda.EventCount.ToString(CultureInfo.InvariantCulture) & ".")
                If agenda.GetProjectedAgendaEventKeys().Count <> expectedTotal Then failures.Add("Projected key count mismatch.")

                agenda.SetDiagnosticVisibleEventCapacity(AgendaViewTokens.MaxVisibleEvents)
                Dim daySnapshot As MASAgendaVisibleRangeWindowSnapshot = agenda.CreateVisibleRangeWindowSnapshot()
                AssertSnapshot(failures, daySnapshot, MASAgendaViewMode.Day, expectedVisible:=AgendaViewTokens.LargeRangeProofEventsPerDay, expectedTotal:=expectedTotal, expectedCapacity:=AgendaViewTokens.MaxVisibleEvents, label:="Day")
                If Not daySnapshot.ContainsEvent("event-000-00") Then failures.Add("Day snapshot does not include the first visible source event.")

                Dim middleKey As String = "event-015-29"
                If Not agenda.EnsureEventVisible(middleKey) Then failures.Add("EnsureEventVisible failed for a middle source event.")
                If Not String.Equals(agenda.SelectedEventKey, middleKey, StringComparison.OrdinalIgnoreCase) Then failures.Add("Selected event key was not preserved after EnsureEventVisible.")
                Dim middleDaySnapshot As MASAgendaVisibleRangeWindowSnapshot = agenda.CreateVisibleRangeWindowSnapshot()
                If Not middleDaySnapshot.ContainsEvent(middleKey) Then failures.Add("Middle day snapshot does not contain the ensured middle event.")
                If Not middleDaySnapshot.IsBounded Then failures.Add("Middle day window is not bounded.")

                agenda.WithDate(anchor.Date.AddDays(14)).WithViewMode(MASAgendaViewMode.Week)
                agenda.SetDiagnosticVisibleEventCapacity(20)
                Dim weekSnapshot As MASAgendaVisibleRangeWindowSnapshot = agenda.CreateVisibleRangeWindowSnapshot()
                AssertSnapshot(failures, weekSnapshot, MASAgendaViewMode.Week, expectedVisible:=7 * AgendaViewTokens.LargeRangeProofEventsPerDay, expectedTotal:=expectedTotal, expectedCapacity:=20, label:="Week")

                agenda.WithDate(anchor.Date.AddDays(14)).WithViewMode(MASAgendaViewMode.Month)
                agenda.SetDiagnosticVisibleEventCapacity(24)
                Dim monthSnapshot As MASAgendaVisibleRangeWindowSnapshot = agenda.CreateVisibleRangeWindowSnapshot()
                AssertSnapshot(failures, monthSnapshot, MASAgendaViewMode.Month, expectedVisible:=31 * AgendaViewTokens.LargeRangeProofEventsPerDay, expectedTotal:=expectedTotal, expectedCapacity:=24, label:="Month")

                Dim lastKey As String = "event-041-29"
                If Not agenda.EnsureEventVisible(lastKey) Then failures.Add("EnsureEventVisible failed for the last source event.")
                If Not String.Equals(agenda.SelectedEventKey, lastKey, StringComparison.OrdinalIgnoreCase) Then failures.Add("Last event selection was not retained.")
                Dim lastSnapshot As MASAgendaVisibleRangeWindowSnapshot = agenda.CreateVisibleRangeWindowSnapshot(24)
                If Not lastSnapshot.ContainsEvent(lastKey) Then failures.Add("Last-event window does not contain the ensured source event.")
                If Not lastSnapshot.IsBounded Then failures.Add("Last-event window is not bounded.")
                If lastSnapshot.ProjectionRevision <> source.Revision Then failures.Add("Visible range snapshot revision does not match the source revision.")

                Dim updatedRecords As List(Of MASAgendaSourceRecord) = BuildRecords(anchor.Date.AddDays(1))
                source.ReplaceItems(updatedRecords, "MASAgendaViewSourceRangeWindowingGate.Replace")
                If agenda.AgendaSourceRevision <> source.Revision Then failures.Add("Agenda did not track source revision after ReplaceItems.")
                If agenda.EventCount <> expectedTotal Then failures.Add("Projected event count changed unexpectedly after source reset.")
                If agenda.SelectedEventKey.Length <> 0 AndAlso Not ContainsKey(agenda.GetProjectedAgendaEventKeys(), agenda.SelectedEventKey) Then failures.Add("Selection points at an event that no longer exists after source reset.")
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(ex, "MASAgendaViewSourceRangeWindowingGate")
                failures.Add(ex.GetType().Name & ": " & ex.Message)
            End Try

            Return New MASAgendaViewSourceRangeWindowingGateResult(failures.Count = 0, failures)
        End Function

        Private Shared Function BuildRecords(anchorDay As Date) As List(Of MASAgendaSourceRecord)
            Dim records As New List(Of MASAgendaSourceRecord)()
            For dayOffset As Integer = 0 To AgendaViewTokens.LargeRangeProofDays - 1
                Dim eventDay As Date = anchorDay.AddDays(dayOffset)
                For eventIndex As Integer = 0 To AgendaViewTokens.LargeRangeProofEventsPerDay - 1
                    Dim startHour As Integer = 8 + (eventIndex Mod 10)
                    Dim startMinute As Integer = (eventIndex Mod 2) * 15
                    Dim startValue As DateTime = eventDay.AddHours(startHour).AddMinutes(startMinute)
                    Dim endValue As DateTime = startValue.AddMinutes(30)
                    Dim key As String = "event-" & dayOffset.ToString("000", CultureInfo.InvariantCulture) & "-" & eventIndex.ToString("00", CultureInfo.InvariantCulture)
                    records.Add(MASAgendaSourceRecord.Create(key,
                                                             "Agenda event " & key,
                                                             startValue,
                                                             endValue,
                                                             "Source range proof"))
                Next
            Next
            Return records
        End Function

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


        Private Shared Function ContainsKey(keys As IReadOnlyList(Of String),
                                            key As String) As Boolean
            Dim normalized As String = If(key, String.Empty).Trim()
            If keys Is Nothing OrElse normalized.Length = 0 Then Return False
            For Each candidate As String In keys
                If String.Equals(candidate, normalized, StringComparison.OrdinalIgnoreCase) Then Return True
            Next
            Return False
        End Function

        Private Shared Sub AssertSnapshot(failures As List(Of String),
                                          snapshot As MASAgendaVisibleRangeWindowSnapshot,
                                          mode As MASAgendaViewMode,
                                          expectedVisible As Integer,
                                          expectedTotal As Integer,
                                          expectedCapacity As Integer,
                                          label As String)
            If snapshot Is Nothing Then
                failures.Add(label & " snapshot was not created.")
                Return
            End If
            If snapshot.Mode <> mode Then failures.Add(label & " snapshot mode mismatch.")
            If snapshot.TotalEvents <> expectedTotal Then failures.Add(label & " total event count mismatch.")
            If snapshot.VisibleEventCount <> expectedVisible Then failures.Add(label & " visible event count mismatch: expected " & expectedVisible.ToString(CultureInfo.InvariantCulture) & ", got " & snapshot.VisibleEventCount.ToString(CultureInfo.InvariantCulture) & ".")
            If snapshot.RequestedEventCapacity <> expectedCapacity Then failures.Add(label & " requested capacity mismatch.")
            If snapshot.RealizedEventCount > expectedCapacity Then failures.Add(label & " realized too many events.")
            If Not snapshot.IsBounded Then failures.Add(label & " snapshot is not bounded.")
            If Not snapshot.CoversPartialAgenda Then failures.Add(label & " snapshot does not prove a partial agenda window.")
        End Sub

    End Class

    Friend NotInheritable Class MASAgendaViewSourceRangeWindowingGateResult
        Private ReadOnly _failures As IReadOnlyList(Of String)

        Friend Sub New(passed As Boolean,
                       failures As IEnumerable(Of String))
            Me.Passed = passed
            Dim normalized As New List(Of String)()
            If failures IsNot Nothing Then
                For Each failure As String In failures
                    If Not String.IsNullOrWhiteSpace(failure) Then normalized.Add(failure.Trim())
                Next
            End If
            _failures = New ReadOnlyCollection(Of String)(normalized.ToArray())
        End Sub

        Friend ReadOnly Property Passed As Boolean

        Friend ReadOnly Property Failures As IReadOnlyList(Of String)
            Get
                Return _failures
            End Get
        End Property
    End Class

End Namespace
