Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.General
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASTransferList

#Region "Transfer selection / movement motion"

        Private _transferSelectionRunner As MASMotionTimelineRunner
        Private _transferSelectionPanelAvailable As Boolean
        Private _transferSelectionFromIndex As Integer = -1
        Private _transferSelectionToIndex As Integer = -1
        Private _transferSelectionProgress As Single = 1.0F
        Private _hasTransferSelectionMotion As Boolean

        Private _transferPulseRunner As MASMotionTimelineRunner
        Private _transferPulsePanelAvailable As Boolean
        Private _transferPulseIndex As Integer = -1
        Private _transferPulseProgress As Single = 1.0F
        Private _hasTransferPulseMotion As Boolean

        Private Sub StartTransferListSelectionMotion(availablePanel As Boolean,
                                                     previousIndex As Integer,
                                                     nextIndex As Integer)
            StopTransferListSelectionMotion(resetState:=False)

            If previousIndex < 0 OrElse nextIndex < 0 OrElse previousIndex = nextIndex Then
                _hasTransferSelectionMotion = False
                _transferSelectionProgress = 1.0F
                Return
            End If

            _transferSelectionPanelAvailable = availablePanel
            _transferSelectionFromIndex = previousIndex
            _transferSelectionToIndex = nextIndex
            _transferSelectionProgress = 0.0F
            _hasTransferSelectionMotion = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.RowSelectionMove, 0.0F, 1.0F)
            If Not plan.ShouldAnimate Then
                CompleteTransferListSelectionMotionFrame(Nothing)
                Return
            End If

            _transferSelectionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASTransferList.Selection",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyTransferListSelectionMotionFrame,
                completed:=AddressOf CompleteTransferListSelectionMotionFrame)
            _transferSelectionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyTransferListSelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _transferSelectionProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasTransferSelectionMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteTransferListSelectionMotionFrame(frame As MASMotionFrame)
            StopTransferListSelectionMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopTransferListSelectionMotion(resetState As Boolean)
            If _transferSelectionRunner IsNot Nothing Then
                _transferSelectionRunner.Dispose()
                _transferSelectionRunner = Nothing
            End If

            If resetState Then
                _transferSelectionPanelAvailable = True
                _transferSelectionFromIndex = -1
                _transferSelectionToIndex = -1
                _transferSelectionProgress = 1.0F
                _hasTransferSelectionMotion = False
            End If
        End Sub

        Private Function IsTransferListSelectionMotionActive() As Boolean
            Return _hasTransferSelectionMotion AndAlso _transferSelectionRunner IsNot Nothing AndAlso _transferSelectionRunner.IsRunning
        End Function

        Private Function ShouldSuppressTransferListSelectionFill(availablePanel As Boolean,
                                                                 index As Integer) As Boolean
            Return IsTransferListSelectionMotionActive() AndAlso
                   _transferSelectionPanelAvailable = availablePanel AndAlso
                   index = _transferSelectionToIndex
        End Function

        Private Sub StartTransferListTransferPulse(availablePanel As Boolean,
                                                   targetIndex As Integer)
            StopTransferListTransferPulse(resetState:=False)

            If targetIndex < 0 Then
                _hasTransferPulseMotion = False
                _transferPulseProgress = 1.0F
                Return
            End If

            _transferPulsePanelAvailable = availablePanel
            _transferPulseIndex = targetIndex
            _transferPulseProgress = 0.0F
            _hasTransferPulseMotion = True

            Dim plan As MASMotionTransitionPlan = MASMotionSystem.CreateTransitionPlan(MASMotionTokenId.DropFeedback, 0.0F, 1.0F)
            If Not plan.ShouldAnimate Then
                CompleteTransferListTransferPulseFrame(Nothing)
                Return
            End If

            _transferPulseRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASTransferList.TransferPulse",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyTransferListTransferPulseFrame,
                completed:=AddressOf CompleteTransferListTransferPulseFrame)
            _transferPulseRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyTransferListTransferPulseFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _transferPulseProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasTransferPulseMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteTransferListTransferPulseFrame(frame As MASMotionFrame)
            StopTransferListTransferPulse(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopTransferListTransferPulse(resetState As Boolean)
            If _transferPulseRunner IsNot Nothing Then
                _transferPulseRunner.Dispose()
                _transferPulseRunner = Nothing
            End If

            If resetState Then
                _transferPulsePanelAvailable = False
                _transferPulseIndex = -1
                _transferPulseProgress = 1.0F
                _hasTransferPulseMotion = False
            End If
        End Sub

        Private Function IsTransferListTransferPulseActive() As Boolean
            Return _hasTransferPulseMotion AndAlso _transferPulseRunner IsNot Nothing AndAlso _transferPulseRunner.IsRunning
        End Function

        Private Sub DrawTransferListMotionOverlays(canvas As SKCanvas,
                                                   dpi As Single,
                                                   plan As MASTransferListLayoutPlan,
                                                   availablePanel As Boolean,
                                                   accent As SKColor)
            DrawTransferListSelectionMotionOverlay(canvas, dpi, plan, availablePanel, accent)
            DrawTransferListTransferPulseOverlay(canvas, dpi, plan, availablePanel, accent)
        End Sub

        Private Sub DrawTransferListSelectionMotionOverlay(canvas As SKCanvas,
                                                           dpi As Single,
                                                           plan As MASTransferListLayoutPlan,
                                                           availablePanel As Boolean,
                                                           accent As SKColor)
            If canvas Is Nothing OrElse plan Is Nothing OrElse Not IsTransferListSelectionMotionActive() Then Return
            If _transferSelectionPanelAvailable <> availablePanel Then Return

            Dim fromRect As SKRect = ResolveTransferListVisibleRowRect(availablePanel, _transferSelectionFromIndex, plan, dpi)
            Dim toRect As SKRect = ResolveTransferListVisibleRowRect(availablePanel, _transferSelectionToIndex, plan, dpi)
            If fromRect.Width <= 1.0F OrElse fromRect.Height <= 1.0F OrElse toRect.Width <= 1.0F OrElse toRect.Height <= 1.0F Then Return

            Dim rect As SKRect = PixelSnap.SnapRect(LerpRect(fromRect, toRect, MASMotionSystem.ClampProgress(_transferSelectionProgress)), dpi)
            Dim radius As Single = Math.Max(6.0F * dpi, rect.Height * 0.34F)

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = accent.WithAlpha(TransferListTokens.RowSelectedAlpha),
                .BlendMode = SKBlendMode.SrcOver
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
        End Sub

        Private Sub DrawTransferListTransferPulseOverlay(canvas As SKCanvas,
                                                         dpi As Single,
                                                         plan As MASTransferListLayoutPlan,
                                                         availablePanel As Boolean,
                                                         accent As SKColor)
            If canvas Is Nothing OrElse plan Is Nothing OrElse Not IsTransferListTransferPulseActive() Then Return
            If _transferPulsePanelAvailable <> availablePanel Then Return

            Dim baseRect As SKRect = ResolveTransferListVisibleRowRect(availablePanel, _transferPulseIndex, plan, dpi)
            If baseRect.Width <= 1.0F OrElse baseRect.Height <= 1.0F Then Return

            Dim progress As Single = MASMotionSystem.ClampProgress(_transferPulseProgress)
            Dim grow As Single = (1.0F - progress) * 3.0F * dpi
            Dim rect As SKRect = PixelSnap.SnapRect(New SKRect(baseRect.Left - grow,
                                                               baseRect.Top - grow,
                                                               baseRect.Right + grow,
                                                               baseRect.Bottom + grow), dpi)
            Dim alpha As Byte = CByte(Math.Max(0, Math.Min(CInt(TransferListTokens.RowSelectedAlpha), CInt(TransferListTokens.RowSelectedAlpha * (1.0F - progress)))))
            If alpha = 0 Then Return

            Dim radius As Single = Math.Max(6.0F * dpi, rect.Height * 0.34F)
            Using fill As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = accent.WithAlpha(alpha),
                .BlendMode = SKBlendMode.SrcOver
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
        End Sub

        Private Function ResolveTransferListVisibleRowRect(availablePanel As Boolean,
                                                           index As Integer,
                                                           plan As MASTransferListLayoutPlan,
                                                           dpi As Single) As SKRect
            If plan Is Nothing OrElse index < 0 Then Return SKRect.Empty

            Dim source As List(Of String) = If(availablePanel, _available, _selected)
            If source Is Nothing OrElse index >= source.Count Then Return SKRect.Empty

            Dim panelRect As SKRect = If(availablePanel, _availablePanelRect, _selectedPanelRect)
            If panelRect.Width <= 1.0F OrElse panelRect.Height <= 1.0F Then Return SKRect.Empty

            Dim filterText As String = If(availablePanel, _availableFilterText, _selectedFilterText)
            Dim topIndex As Integer = If(availablePanel, _availableTopIndex, _selectedTopIndex)
            Dim visibleIndexes As List(Of Integer) = BuildVisibleIndexes(source, filterText)
            If visibleIndexes.Count <= 0 Then Return SKRect.Empty

            Dim safeTop As Integer = Math.Max(0, Math.Min(topIndex, Math.Max(0, visibleIndexes.Count - 1)))
            Dim visualIndex As Integer = -1
            For visual As Integer = 0 To visibleIndexes.Count - safeTop - 1
                If visibleIndexes(safeTop + visual) = index Then
                    visualIndex = visual
                    Exit For
                End If
            Next
            If visualIndex < 0 OrElse visualIndex >= plan.MaxVisibleRows Then Return SKRect.Empty

            Dim pad As Single = plan.PanelPaddingPx
            Dim headerHeight As Single = TransferListTokens.HeaderHeightDip * dpi * plan.ResponsiveProfile.ChromeScale
            Dim rowsTop As Single = panelRect.Top + pad + headerHeight + plan.RowGapPx
            If plan.ShowSearch AndAlso plan.SearchHeightPx > 1.0F Then rowsTop += plan.SearchHeightPx + plan.RowGapPx

            Dim left As Single = panelRect.Left + pad
            Dim right As Single = panelRect.Right - pad
            Dim top As Single = rowsTop + CSng(visualIndex) * (plan.RowHeightPx + plan.RowGapPx)
            Dim bottom As Single = top + plan.RowHeightPx
            If bottom > panelRect.Bottom - pad + 0.5F * dpi Then Return SKRect.Empty

            Return PixelSnap.SnapRect(New SKRect(left, top, right, bottom), dpi)
        End Function

        Private Shared Function LerpRect(fromRect As SKRect,
                                         toRect As SKRect,
                                         progress As Single) As SKRect
            Dim p As Single = MASMotionSystem.ClampProgress(progress)
            Return New SKRect(
                fromRect.Left + ((toRect.Left - fromRect.Left) * p),
                fromRect.Top + ((toRect.Top - fromRect.Top) * p),
                fromRect.Right + ((toRect.Right - fromRect.Right) * p),
                fromRect.Bottom + ((toRect.Bottom - fromRect.Bottom) * p))
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            StopTransferListSelectionMotion(resetState:=True)
            StopTransferListTransferPulse(resetState:=True)
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
