Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Motion

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Owns toast runtime state, queue promotion and lifetime progression.
    ''' This type is intentionally internal so external projects interact only through the toast service/control gateway.
    ''' </summary>
    Friend NotInheritable Class MASToastStateController

        Private ReadOnly _items As New List(Of MASToastItem)()
        Private ReadOnly _pending As New Queue(Of MASToastItem)()
        Private _nextId As Integer = 1

        Friend ReadOnly Property Items As List(Of MASToastItem)
            Get
                Return _items
            End Get
        End Property

        Friend ReadOnly Property PendingCount As Integer
            Get
                Return _pending.Count
            End Get
        End Property

        Friend ReadOnly Property HasAnyToast As Boolean
            Get
                Return _items.Count > 0 OrElse _pending.Count > 0
            End Get
        End Property

        Friend Function ShowToast(options As MASToastOptions,
                                  maxVisible As Integer,
                                  maxQueue As Integer,
                                  queueWhenFull As Boolean) As Integer

            If options Is Nothing Then Throw New ArgumentNullException(NameOf(options))

            maxVisible = MASToastVisualLifecyclePolicy.NormalizeMaxVisible(maxVisible)
            maxQueue = MASToastVisualLifecyclePolicy.NormalizeMaxQueue(maxQueue)

            PurgeClosed(maxVisible)

            Dim item As MASToastItem = CreateToastItem(options)

            If _items.Count < maxVisible Then
                _items.Add(item)
            ElseIf queueWhenFull AndAlso EnqueuePending(item, maxQueue) Then
                ' The toast is now owned by the pending queue and will be promoted deterministically.
            Else
                ReplaceOldestVisible(item, maxVisible)
            End If

            PromotePendingIfNeeded(maxVisible)
            Return item.Id
        End Function

        Friend Function FindItem(id As Integer) As MASToastItem
            For Each item As MASToastItem In _items
                If item IsNot Nothing AndAlso item.Id = id Then
                    Return item
                End If
            Next

            Return Nothing
        End Function

        Friend Function Dismiss(toastId As Integer, maxVisible As Integer) As Boolean
            If toastId <= 0 Then Return False

            Dim item As MASToastItem = FindItem(toastId)
            If item IsNot Nothing Then
                StartClosing(item)
                Return True
            End If

            If RemovePendingById(toastId) Then
                PromotePendingIfNeeded(Math.Max(1, maxVisible))
                Return True
            End If

            Return False
        End Function

        Friend Function ClearAllImmediate() As List(Of Integer)
            Dim closedIds As New List(Of Integer)()

            For Each item As MASToastItem In _items
                If item Is Nothing Then Continue For

                item.IsClosing = True
                item.CloseProgress = 1.0F
                closedIds.Add(item.Id)
            Next

            _pending.Clear()
            PurgeClosed(1)

            Return closedIds
        End Function

        Friend Sub StartClosing(item As MASToastItem)
            If item Is Nothing Then Return
            If item.IsClosing Then Return

            item.IsClosing = True
            item.CloseProgress = 0.0F
            item.CloseElapsedMs = 0
        End Sub

        Friend Function Tick(animationDtMs As Double,
                             lifetimeDtMs As Double,
                             maxVisible As Integer) As Boolean

            Dim changed As Boolean = False

            maxVisible = MASToastVisualLifecyclePolicy.NormalizeMaxVisible(maxVisible)
            PromotePendingIfNeeded(maxVisible)

            If animationDtMs <= 0.0R Then animationDtMs = 16.0R
            If lifetimeDtMs <= 0.0R Then lifetimeDtMs = animationDtMs

            For Each item As MASToastItem In _items
                If item Is Nothing Then Continue For

                If Not item.IsClosing Then
                    Dim animationDeltaMs As Integer = CInt(Math.Round(animationDtMs))
                    Dim openElapsedMs As Integer = item.OpenElapsedMs
                    Dim newOpen As Single = MASMotionSystem.AdvanceScalarProgress(MASMotionTokenId.FloatOpen, openElapsedMs, animationDeltaMs)
                    item.OpenElapsedMs = openElapsedMs

                    If Math.Abs(newOpen - item.OpenProgress) > 0.0001F Then
                        item.OpenProgress = newOpen
                        changed = True
                    End If

                    ' Round17 FIX1: hover and pointer press are visual interaction states only.
                    ' Lifetime keeps progressing while the toast, action, undo, or close
                    ' affordance is hovered or pressed.
                    If item.DurationMs > 0 AndAlso MASToastVisualLifecyclePolicy.ShouldAdvanceLifetime(item) Then
                        item.RemainingMs -= lifetimeDtMs

                        If item.RemainingMs <= 0.0R Then
                            StartClosing(item)
                            changed = True
                        Else
                            changed = True
                        End If
                    End If
                Else
                    Dim animationDeltaMs As Integer = CInt(Math.Round(animationDtMs))
                    Dim closeElapsedMs As Integer = item.CloseElapsedMs
                    Dim newClose As Single = MASMotionSystem.AdvanceScalarProgress(MASMotionTokenId.FloatClose, closeElapsedMs, animationDeltaMs)
                    item.CloseElapsedMs = closeElapsedMs

                    If Math.Abs(newClose - item.CloseProgress) > 0.0001F Then
                        item.CloseProgress = newClose
                        changed = True
                    End If
                End If
            Next

            Return changed
        End Function

        Friend Function HasActiveMotion(maxVisible As Integer) As Boolean
            maxVisible = Math.Max(1, maxVisible)

            If _pending.Count > 0 AndAlso _items.Count < maxVisible Then Return True

            For Each item As MASToastItem In _items
                If item Is Nothing Then Continue For

                If item.IsClosing Then
                    If item.CloseProgress < 1.0F Then Return True
                Else
                    If item.OpenProgress < 1.0F Then Return True
                    If item.DurationMs > 0 AndAlso item.RemainingMs > 0.0R Then Return True
                End If
            Next

            Return False
        End Function

        Friend Function PurgeClosed(maxVisible As Integer) As List(Of Integer)
            Dim closedIds As New List(Of Integer)()

            For i As Integer = _items.Count - 1 To 0 Step -1
                Dim item As MASToastItem = _items(i)

                If item Is Nothing Then
                    _items.RemoveAt(i)
                    Continue For
                End If

                If item.IsClosing AndAlso item.CloseProgress >= 1.0F Then
                    closedIds.Add(item.Id)
                    _items.RemoveAt(i)
                End If
            Next

            PromotePendingIfNeeded(Math.Max(1, maxVisible))
            Return closedIds
        End Function

        Friend Sub Clear()
            _items.Clear()
            _pending.Clear()
        End Sub

        Private Function CreateToastItem(options As MASToastOptions) As MASToastItem
            Dim ttl As String = MASToastVisualLifecyclePolicy.NormalizeTitle(options.Title)
            Dim msg As String = MASToastVisualLifecyclePolicy.ResolveMessageText(ttl, options.Message)

            Dim id As Integer = AllocateToastId()

            Dim duration As Integer = MASToastVisualLifecyclePolicy.NormalizeDurationMs(options.DurationMs)

            Return New MASToastItem With {
                .Id = id,
                .Title = ttl,
                .Message = msg,
                .Tone = MASToastVisualLifecyclePolicy.NormalizeTone(options.Tone),
                .DurationMs = duration,
                .RemainingMs = duration,
                .Dismissible = options.Dismissible,
                .ActionText = MASToastVisualLifecyclePolicy.NormalizeActionText(options.ActionText),
                .ActionCallback = options.ActionCallback,
                .UndoCallback = options.UndoCallback,
                .ShowLifetimeBar = options.ShowLifetimeBar,
                .SurfaceMaterialKey = MASToastVisualLifecyclePolicy.NormalizeSurfaceMaterial(options.SurfaceMaterial),
                .OpenProgress = 0.0F,
                .CloseProgress = 0.0F,
                .OpenElapsedMs = 0,
                .CloseElapsedMs = 0,
                .IsClosing = False,
                .IsHovered = False
            }
        End Function

        Private Function AllocateToastId() As Integer
            If _nextId <= 0 Then
                _nextId = 1
            End If

            Dim id As Integer = _nextId

            If _nextId >= Integer.MaxValue Then
                _nextId = 1
            Else
                _nextId += 1
            End If

            Return id
        End Function

        Private Sub PromotePendingIfNeeded(maxVisible As Integer)
            maxVisible = Math.Max(1, maxVisible)

            While _items.Count < maxVisible AndAlso _pending.Count > 0
                Dim nextItem As MASToastItem = _pending.Dequeue()
                If nextItem Is Nothing Then Continue While

                nextItem.OpenProgress = 0.0F
                nextItem.CloseProgress = 0.0F
                nextItem.OpenElapsedMs = 0
                nextItem.CloseElapsedMs = 0
                nextItem.IsClosing = False
                nextItem.IsHovered = False
                nextItem.IsActionHovered = False
                nextItem.IsActionPressed = False
                nextItem.IsUndoHovered = False
                nextItem.IsUndoPressed = False
                nextItem.IsCloseHovered = False
                nextItem.IsClosePressed = False

                _items.Add(nextItem)
            End While
        End Sub

        Private Function EnqueuePending(item As MASToastItem, maxQueue As Integer) As Boolean
            If item Is Nothing Then Return False
            If maxQueue <= 0 Then Return False

            While _pending.Count >= maxQueue
                _pending.Dequeue()
            End While

            _pending.Enqueue(item)
            Return True
        End Function

        Private Sub ReplaceOldestVisible(item As MASToastItem, maxVisible As Integer)
            If item Is Nothing Then Return

            maxVisible = Math.Max(1, maxVisible)

            Dim removeIndex As Integer = -1

            For i As Integer = 0 To _items.Count - 1
                Dim existing As MASToastItem = _items(i)
                If existing Is Nothing Then
                    removeIndex = i
                    Exit For
                End If

                If Not existing.IsClosing Then
                    removeIndex = i
                    Exit For
                End If
            Next

            If removeIndex < 0 AndAlso _items.Count >= maxVisible Then
                removeIndex = 0
            End If

            If removeIndex >= 0 AndAlso removeIndex < _items.Count Then
                _items.RemoveAt(removeIndex)
            End If

            While _items.Count >= maxVisible AndAlso _items.Count > 0
                _items.RemoveAt(0)
            End While

            _items.Add(item)
        End Sub

        Private Function RemovePendingById(toastId As Integer) As Boolean
            If toastId <= 0 OrElse _pending.Count = 0 Then Return False

            Dim removed As Boolean = False
            Dim rebuilt As New Queue(Of MASToastItem)()

            While _pending.Count > 0
                Dim item As MASToastItem = _pending.Dequeue()
                If item Is Nothing Then Continue While

                If Not removed AndAlso item.Id = toastId Then
                    removed = True
                Else
                    rebuilt.Enqueue(item)
                End If
            End While

            While rebuilt.Count > 0
                _pending.Enqueue(rebuilt.Dequeue())
            End While

            Return removed
        End Function



        Friend Function CreateToastReadinessManifest() As MASToastReadinessManifest
            Return MASToastReadinessManifest.CreateCurrent()
        End Function
    End Class

End Namespace