Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Globalization
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Official Nexamas UI day-agenda visual surface. It owns product-readable
    ''' single-day event presentation, selection, keyboard navigation, RTL-aware layout,
    ''' and day navigation. Recurrence, reminders, booking storage, external calendar
    ''' providers, scheduler engines, and provider synchronization remain outside it.
    ''' </summary>
    Partial Public NotInheritable Class MASAgendaView
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"
        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _events As New List(Of AgendaEventState)()
        Private ReadOnly _eventHitRects As New List(Of AgendaEventHit)()
        Private _title As String = AgendaViewTokens.DefaultTitle
        Private _day As Date = DateTime.Today.Date
        Private _viewMode As MASAgendaViewMode = MASAgendaViewMode.Day
        Private _nowProvider As Func(Of DateTime) = Function() Nexamas.UI.Timing.MASTimeProvider.LocalNow()
        Private _startHour As Integer = 8
        Private _endHour As Integer = 18
        Private _selectedEventIndex As Integer = -1
        Private _hoverEventIndex As Integer = -1
        Private _pressedEventIndex As Integer = -1
        Private _topEventIndex As Integer
        Private _hoverPrevious As Boolean
        Private _hoverNext As Boolean
        Private _pressedPrevious As Boolean
        Private _pressedNext As Boolean
        Private _previousRect As SKRect = SKRect.Empty
        Private _nextRect As SKRect = SKRect.Empty
        Private _contentRect As SKRect = SKRect.Empty
        Private _eventsRect As SKRect = SKRect.Empty

#End Region

#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()
        End Sub

        Public Shared Function Create(Optional title As String = Nothing,
                                      Optional day As Nullable(Of Date) = Nothing) As MASAgendaView
            Dim control As New MASAgendaView()
            control._title = MASAgendaViewPolicy.NormalizeTitle(title)
            If day.HasValue Then control._day = MASAgendaViewPolicy.NormalizeDay(day.Value)
            Return control
        End Function

#End Region

#Region "Public API"

        Public Event AgendaChanged As EventHandler
        Public Event SelectedEventChanged As EventHandler

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = MASAgendaViewPolicy.NormalizeTitle(value)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return
                _title = normalized
                RequestSizeLayoutRefreshForSizeAffectingChange("MASAgendaView.Title")
                InvalidateVisual()
            End Set
        End Property

        Public Property Day As Date
            Get
                Return _day
            End Get
            Set(value As Date)
                Dim normalized As Date = MASAgendaViewPolicy.NormalizeDay(value)
                If _day = normalized Then Return
                StopAgendaEventSelectionMotion(resetState:=True)
                _day = normalized
                ResetPointerState()
                NormalizeEventOrderAndSelection()
                InvalidateVisual()
                RaiseAgendaChangedSafe()
            End Set
        End Property
        Public Property ViewMode As MASAgendaViewMode
            Get
                Return _viewMode
            End Get
            Set(value As MASAgendaViewMode)
                Dim normalized As MASAgendaViewMode = MASAgendaViewPolicy.NormalizeViewMode(value)
                If _viewMode = normalized Then Return
                StopAgendaEventSelectionMotion(resetState:=True)
                _viewMode = normalized
                ResetPointerState()
                NormalizeEventOrderAndSelection()
                RequestSizeLayoutRefreshForSizeAffectingChange("MASAgendaView.ViewMode")
                InvalidateVisual()
                RaiseAgendaChangedSafe()
            End Set
        End Property
        Public Property StartHour As Integer
            Get
                Return _startHour
            End Get
            Set(value As Integer)
                Dim startValue As Integer = value
                Dim endValue As Integer = _endHour
                MASAgendaViewPolicy.NormalizeHourRange(startValue, endValue)
                If _startHour = startValue AndAlso _endHour = endValue Then Return
                _startHour = startValue
                _endHour = endValue
                NormalizeEventOrderAndSelection()
                InvalidateVisual()
            End Set
        End Property

        Public Property EndHour As Integer
            Get
                Return _endHour
            End Get
            Set(value As Integer)
                Dim startValue As Integer = _startHour
                Dim endValue As Integer = value
                MASAgendaViewPolicy.NormalizeHourRange(startValue, endValue)
                If _startHour = startValue AndAlso _endHour = endValue Then Return
                _startHour = startValue
                _endHour = endValue
                NormalizeEventOrderAndSelection()
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property EventCount As Integer
            Get
                Return _events.Count
            End Get
        End Property

        Public ReadOnly Property SelectedEventKey As String
            Get
                Dim item As AgendaEventState = GetSelectedEvent()
                If item Is Nothing Then Return String.Empty
                Return item.Key
            End Get
        End Property

        Public ReadOnly Property SummaryText As String
            Get
                Return MASAgendaViewPolicy.BuildSummary(GetVisibleEventCountForCurrentView(), _day, _viewMode)
            End Get
        End Property

        Public Function GetEventKeys() As IReadOnlyList(Of String)
            Dim keys As New List(Of String)()
            For Each item As AgendaEventState In _events
                keys.Add(item.Key)
            Next
            Return New ReadOnlyCollection(Of String)(keys)
        End Function

        Public Function AddEvent(eventKey As String,
                                 title As String,
                                 startTime As DateTime,
                                 endTime As DateTime,
                                 Optional detail As String = Nothing) As MASAgendaView
            If _events.Count >= AgendaViewTokens.MaxEvents Then Return Me
            ClearAgendaSourceLinkForManualMutation()
            Dim normalizedKey As String = MASAgendaViewPolicy.NormalizeKey(eventKey, "event", _events.Count + 1)
            If FindEventIndex(normalizedKey) >= 0 Then Return Me
            Dim eventDay As Date = MASAgendaViewPolicy.NormalizeEventDay(startTime, _day)
            Dim startMinute As Integer = MASAgendaViewPolicy.ClampMinuteToRange(MASAgendaViewPolicy.MinutesOfDay(startTime), _startHour, _endHour)
            Dim endMinute As Integer = MASAgendaViewPolicy.ClampMinuteToRange(MASAgendaViewPolicy.MinutesOfDay(endTime), _startHour, _endHour)
            If endMinute <= startMinute Then endMinute = Math.Min(_endHour * 60, startMinute + 30)
            _events.Add(New AgendaEventState(normalizedKey, MASAgendaViewPolicy.NormalizeEventTitle(title), MASAgendaViewPolicy.NormalizeEventDetail(detail), eventDay, startMinute, endMinute))
            NormalizeEventOrderAndSelection()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASAgendaView.AddEvent")
            InvalidateVisual()
            RaiseAgendaChangedSafe()
            Return Me
        End Function

        Public Function ClearEvents() As MASAgendaView
            If _events.Count = 0 Then Return Me
            ClearAgendaSourceLinkForManualMutation()
            StopAgendaEventSelectionMotion(resetState:=True)
            _events.Clear()
            _selectedEventIndex = -1
            _topEventIndex = 0
            ResetPointerState()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASAgendaView.ClearEvents")
            InvalidateVisual()
            RaiseAgendaChangedSafe()
            RaiseSelectedEventChangedSafe()
            Return Me
        End Function
        Public Function SelectEvent(eventKey As String) As Boolean
            Dim index As Integer = FindEventIndex(eventKey)
            If index < 0 Then Return False
            SetSelection(index, True)
            Return True
        End Function

        Public Function MovePreviousDay() As MASAgendaView
            Day = _day.AddDays(-1)
            Return Me
        End Function

        Public Function MoveNextDay() As MASAgendaView
            Day = _day.AddDays(1)
            Return Me
        End Function

        Public Function MovePreviousRange() As MASAgendaView
            Day = MASAgendaViewPolicy.MoveRange(_day, _viewMode, -1)
            Return Me
        End Function

        Public Function MoveNextRange() As MASAgendaView
            Day = MASAgendaViewPolicy.MoveRange(_day, _viewMode, 1)
            Return Me
        End Function

        Public Function MoveToday() As MASAgendaView
            Day = ResolveNow().Date
            Return Me
        End Function

        Friend Sub SetNowProviderForTests(provider As Func(Of DateTime))
            _nowProvider = If(provider, Function() Nexamas.UI.Timing.MASTimeProvider.LocalNow())
        End Sub

        Private Function ResolveNow() As DateTime
            Try
                If _nowProvider Is Nothing Then Return Nexamas.UI.Timing.MASTimeProvider.LocalNow()
                Return _nowProvider.Invoke()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(
                    masCaughtException1,
                    "MASAgendaView.ResolveNow")
                Return Nexamas.UI.Timing.MASTimeProvider.LocalNow()
            End Try
        End Function

        Public Function WithTitle(value As String) As MASAgendaView
            Title = value
            Return Me
        End Function

        Public Function WithDate(value As Date) As MASAgendaView
            Day = value
            Return Me
        End Function

        Public Function WithViewMode(value As MASAgendaViewMode) As MASAgendaView
            ViewMode = value
            Return Me
        End Function

        Public Function WithHours(startHour As Integer,
                                  endHour As Integer) As MASAgendaView
            Dim normalizedStart As Integer = startHour
            Dim normalizedEnd As Integer = endHour
            MASAgendaViewPolicy.NormalizeHourRange(normalizedStart, normalizedEnd)
            _startHour = normalizedStart
            _endHour = normalizedEnd
            NormalizeEventOrderAndSelection()
            InvalidateVisual()
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASAgendaView
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region

    End Class

End Namespace
