Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.General
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASKanbanBoard

#Region "Kanban board motion"

        Private _kanbanSelectionRunner As MASMotionTimelineRunner
        Private _kanbanSelectionFromColumnIndex As Integer = -1
        Private _kanbanSelectionFromCardIndex As Integer = -1
        Private _kanbanSelectionToColumnIndex As Integer = -1
        Private _kanbanSelectionToCardIndex As Integer = -1
        Private _kanbanSelectionProgress As Single = 1.0F
        Private _hasKanbanSelectionMotion As Boolean

        Private _kanbanDragRunner As MASMotionTimelineRunner
        Private _kanbanDragTargetColumnIndex As Integer = -1
        Private _kanbanDragProgress As Single = 1.0F
        Private _hasKanbanDragMotion As Boolean

        Private _kanbanDropRunner As MASMotionTimelineRunner
        Private _kanbanDropColumnIndex As Integer = -1
        Private _kanbanDropCardIndex As Integer = -1
        Private _kanbanDropProgress As Single = 1.0F
        Private _hasKanbanDropMotion As Boolean

        Private Sub StartKanbanCardSelectionMotion(previousColumnIndex As Integer,
                                                   previousCardIndex As Integer,
                                                   nextColumnIndex As Integer,
                                                   nextCardIndex As Integer)
            StopKanbanCardSelectionMotion(resetState:=False)

            If previousColumnIndex < 0 OrElse previousCardIndex < 0 OrElse nextColumnIndex < 0 OrElse nextCardIndex < 0 Then
                _hasKanbanSelectionMotion = False
                _kanbanSelectionProgress = 1.0F
                Return
            End If

            If previousColumnIndex = nextColumnIndex AndAlso previousCardIndex = nextCardIndex Then
                _hasKanbanSelectionMotion = False
                _kanbanSelectionProgress = 1.0F
                Return
            End If

            _kanbanSelectionFromColumnIndex = previousColumnIndex
            _kanbanSelectionFromCardIndex = previousCardIndex
            _kanbanSelectionToColumnIndex = nextColumnIndex
            _kanbanSelectionToCardIndex = nextCardIndex
            _kanbanSelectionProgress = 0.0F
            _hasKanbanSelectionMotion = True

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

            _kanbanSelectionRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASKanbanBoard.CardSelection",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyKanbanCardSelectionMotionFrame,
                completed:=AddressOf CompleteKanbanCardSelectionMotionFrame)
            _kanbanSelectionRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyKanbanCardSelectionMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _kanbanSelectionProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasKanbanSelectionMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteKanbanCardSelectionMotionFrame(frame As MASMotionFrame)
            StopKanbanCardSelectionMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopKanbanCardSelectionMotion(resetState As Boolean)
            If _kanbanSelectionRunner IsNot Nothing Then
                _kanbanSelectionRunner.Dispose()
                _kanbanSelectionRunner = Nothing
            End If

            If resetState Then
                _kanbanSelectionFromColumnIndex = -1
                _kanbanSelectionFromCardIndex = -1
                _kanbanSelectionToColumnIndex = -1
                _kanbanSelectionToCardIndex = -1
                _kanbanSelectionProgress = 1.0F
                _hasKanbanSelectionMotion = False
            End If
        End Sub

        Private Function IsKanbanCardSelectionMotionActive() As Boolean
            Return _hasKanbanSelectionMotion AndAlso _kanbanSelectionRunner IsNot Nothing AndAlso _kanbanSelectionRunner.IsRunning
        End Function

        Private Function ShouldSuppressKanbanCardSelectionFill(columnIndex As Integer,
                                                              cardIndex As Integer) As Boolean
            Return IsKanbanCardSelectionMotionActive() AndAlso
                   columnIndex = _kanbanSelectionToColumnIndex AndAlso
                   cardIndex = _kanbanSelectionToCardIndex
        End Function

        Private Sub StartKanbanDragFeedbackMotion(targetColumnIndex As Integer)
            StopKanbanDragFeedbackMotion(resetState:=False)

            If targetColumnIndex < 0 Then
                _hasKanbanDragMotion = False
                _kanbanDragProgress = 1.0F
                Return
            End If

            _kanbanDragTargetColumnIndex = targetColumnIndex
            _kanbanDragProgress = 0.0F
            _hasKanbanDragMotion = True

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

            _kanbanDragRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASKanbanBoard.DragFeedback",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyKanbanDragFeedbackMotionFrame,
                completed:=AddressOf CompleteKanbanDragFeedbackMotionFrame)
            _kanbanDragRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyKanbanDragFeedbackMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _kanbanDragProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasKanbanDragMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteKanbanDragFeedbackMotionFrame(frame As MASMotionFrame)
            StopKanbanDragFeedbackMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopKanbanDragFeedbackMotion(resetState As Boolean)
            If _kanbanDragRunner IsNot Nothing Then
                _kanbanDragRunner.Dispose()
                _kanbanDragRunner = Nothing
            End If

            If resetState Then
                _kanbanDragTargetColumnIndex = -1
                _kanbanDragProgress = 1.0F
                _hasKanbanDragMotion = False
            End If
        End Sub

        Private Function IsKanbanDragFeedbackMotionActive() As Boolean
            Return _hasKanbanDragMotion AndAlso _kanbanDragRunner IsNot Nothing AndAlso _kanbanDragRunner.IsRunning
        End Function

        Private Sub StartKanbanDropFeedbackMotion(columnIndex As Integer,
                                                  cardIndex As Integer)
            StopKanbanDropFeedbackMotion(resetState:=False)

            If columnIndex < 0 OrElse cardIndex < 0 Then
                _hasKanbanDropMotion = False
                _kanbanDropProgress = 1.0F
                Return
            End If

            _kanbanDropColumnIndex = columnIndex
            _kanbanDropCardIndex = cardIndex
            _kanbanDropProgress = 0.0F
            _hasKanbanDropMotion = True

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

            _kanbanDropRunner = MASMotionSystem.CreateTimelineRunner(
                owner:=Me,
                consumerName:="MASKanbanBoard.DropFeedback",
                plan:=plan,
                requestFrame:=AddressOf InvalidateVisual,
                applyFrame:=AddressOf ApplyKanbanDropFeedbackMotionFrame,
                completed:=AddressOf CompleteKanbanDropFeedbackMotionFrame)
            _kanbanDropRunner.Start()

            InvalidateVisual()
        End Sub

        Private Sub ApplyKanbanDropFeedbackMotionFrame(frame As MASMotionFrame)
            If frame Is Nothing Then Return
            _kanbanDropProgress = MASMotionSystem.ClampProgress(frame.Value)
            _hasKanbanDropMotion = True
            InvalidateVisual()
        End Sub

        Private Sub CompleteKanbanDropFeedbackMotionFrame(frame As MASMotionFrame)
            StopKanbanDropFeedbackMotion(resetState:=True)
            InvalidateVisual()
        End Sub

        Private Sub StopKanbanDropFeedbackMotion(resetState As Boolean)
            If _kanbanDropRunner IsNot Nothing Then
                _kanbanDropRunner.Dispose()
                _kanbanDropRunner = Nothing
            End If

            If resetState Then
                _kanbanDropColumnIndex = -1
                _kanbanDropCardIndex = -1
                _kanbanDropProgress = 1.0F
                _hasKanbanDropMotion = False
            End If
        End Sub

        Private Function IsKanbanDropFeedbackMotionActive() As Boolean
            Return _hasKanbanDropMotion AndAlso _kanbanDropRunner IsNot Nothing AndAlso _kanbanDropRunner.IsRunning
        End Function

        Private Sub DrawKanbanMotionOverlays(canvas As SKCanvas,
                                             ctx As MASThemeContext,
                                             plan As MASKanbanBoardLayoutPlan,
                                             accent As SKColor,
                                             isRtl As Boolean)
            DrawKanbanCardSelectionMotionOverlay(canvas, plan, accent)
            DrawKanbanDragFeedbackOverlay(canvas, plan, accent)
            DrawKanbanDropFeedbackOverlay(canvas, plan, accent)
        End Sub

        Private Sub DrawKanbanCardSelectionMotionOverlay(canvas As SKCanvas,
                                                         plan As MASKanbanBoardLayoutPlan,
                                                         accent As SKColor)
            If canvas Is Nothing OrElse plan Is Nothing OrElse Not IsKanbanCardSelectionMotionActive() Then Return

            Dim fromRect As SKRect = ResolveKanbanVisibleCardRect(_kanbanSelectionFromColumnIndex, _kanbanSelectionFromCardIndex)
            Dim toRect As SKRect = ResolveKanbanVisibleCardRect(_kanbanSelectionToColumnIndex, _kanbanSelectionToCardIndex)
            If fromRect.Width <= 1.0F OrElse fromRect.Height <= 1.0F OrElse toRect.Width <= 1.0F OrElse toRect.Height <= 1.0F Then Return

            Dim dpi As Single = Math.Max(0.64F, plan.Dpi)
            Dim rect As SKRect = PixelSnap.SnapRect(LerpRect(fromRect, toRect, MASMotionSystem.ClampProgress(_kanbanSelectionProgress)), dpi)
            Dim radius As Single = Math.Max(6.0F * dpi, plan.CardRadiusPx)

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

        Private Sub DrawKanbanDragFeedbackOverlay(canvas As SKCanvas,
                                                  plan As MASKanbanBoardLayoutPlan,
                                                  accent As SKColor)
            If canvas Is Nothing OrElse plan Is Nothing OrElse Not IsKanbanDragFeedbackMotionActive() Then Return

            Dim baseRect As SKRect = ResolveKanbanVisibleColumnRect(_kanbanDragTargetColumnIndex)
            If baseRect.Width <= 1.0F OrElse baseRect.Height <= 1.0F Then Return

            Dim dpi As Single = Math.Max(0.64F, plan.Dpi)
            Dim progress As Single = MASMotionSystem.ClampProgress(_kanbanDragProgress)
            Dim grow As Single = (1.0F - progress) * 2.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 = ResolveFadingAlpha(KanbanBoardTokens.ColumnSelectedAlpha, progress)
            If alpha = 0 Then Return

            Dim radius As Single = Math.Max(8.0F * dpi, plan.ColumnRadiusPx)
            Using fill As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = Math.Max(1.0F, 1.5F * dpi),
                .Color = accent.WithAlpha(alpha),
                .BlendMode = SKBlendMode.SrcOver
            }
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
        End Sub

        Private Sub DrawKanbanDropFeedbackOverlay(canvas As SKCanvas,
                                                  plan As MASKanbanBoardLayoutPlan,
                                                  accent As SKColor)
            If canvas Is Nothing OrElse plan Is Nothing OrElse Not IsKanbanDropFeedbackMotionActive() Then Return

            Dim baseRect As SKRect = ResolveKanbanVisibleCardRect(_kanbanDropColumnIndex, _kanbanDropCardIndex)
            If baseRect.Width <= 1.0F OrElse baseRect.Height <= 1.0F Then Return

            Dim dpi As Single = Math.Max(0.64F, plan.Dpi)
            Dim progress As Single = MASMotionSystem.ClampProgress(_kanbanDropProgress)
            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 = ResolveFadingAlpha(KanbanBoardTokens.CardSelectedAlpha, progress)
            If alpha = 0 Then Return

            Dim radius As Single = Math.Max(6.0F * dpi, plan.CardRadiusPx)
            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 ResolveKanbanVisibleCardRect(columnIndex As Integer,
                                                      cardIndex As Integer) As SKRect
            If columnIndex < 0 OrElse cardIndex < 0 Then Return SKRect.Empty
            For Each hit As KanbanCardHit In _cardHitRects
                If hit IsNot Nothing AndAlso hit.ColumnIndex = columnIndex AndAlso hit.CardIndex = cardIndex Then Return hit.Rect
            Next
            Return SKRect.Empty
        End Function

        Private Function ResolveKanbanVisibleColumnRect(columnIndex As Integer) As SKRect
            If columnIndex < 0 Then Return SKRect.Empty
            Dim visualIndex As Integer = columnIndex - _leftColumnIndex
            If visualIndex < 0 OrElse visualIndex >= _columnRects.Count Then Return SKRect.Empty
            Return _columnRects(visualIndex)
        End Function

        Private Shared Function ResolveFadingAlpha(baseAlpha As Byte,
                                                   progress As Single) As Byte
            Dim p As Single = MASMotionSystem.ClampProgress(progress)
            Dim alpha As Integer = CInt(Math.Round(CSng(baseAlpha) * (1.0F - p)))
            If alpha <= 0 Then Return CByte(0)
            If alpha >= 255 Then Return CByte(255)
            Return CByte(alpha)
        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)
            If disposing Then
                StopKanbanCardSelectionMotion(resetState:=True)
                StopKanbanDragFeedbackMotion(resetState:=True)
                StopKanbanDropFeedbackMotion(resetState:=True)
            End If
            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
