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

    Partial Public NotInheritable Class MASKanbanBoard
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"
        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _columns As New List(Of KanbanColumnState)()
        Private ReadOnly _columnLookup As New Dictionary(Of String, KanbanColumnState)(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _columnIndexByKey As New Dictionary(Of String, Integer)(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _cardLocationByKey As New Dictionary(Of String, Tuple(Of Integer, Integer))(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _columnRects As New List(Of SKRect)()
        Private ReadOnly _cardHitRects As New List(Of KanbanCardHit)()
        Private _title As String = KanbanBoardTokens.DefaultTitle
        Private _selectedColumnIndex As Integer = -1
        Private _selectedCardIndex As Integer = -1
        Private _hoverColumnIndex As Integer = -1
        Private _hoverCardIndex As Integer = -1
        Private _pressedColumnIndex As Integer = -1
        Private _pressedCardIndex As Integer = -1
        Private _dragTargetColumnIndex As Integer = -1
        Private _draggingCard As Boolean
        Private _hasPressedPoint As Boolean
        Private _pressedPointPx As SKPoint
        Private _leftColumnIndex As Integer
        Private _cardCount As Integer
        Private _contentRect As SKRect = SKRect.Empty
        Private _boardRect As SKRect = SKRect.Empty
        Private _lastDpi As Single = 1.0F
        Private _lastLayoutPlan As MASKanbanBoardLayoutPlan

#End Region

#Region "Constructor / Factory"


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

        Public Shared Function Create(Optional title As String = Nothing) As MASKanbanBoard
            Dim control As New MASKanbanBoard()
            control._title = MASKanbanBoardPolicy.NormalizeTitle(title)
            Return control
        End Function

#End Region

#Region "Public API"


        Public Event BoardChanged As EventHandler
        Public Event SelectedCardChanged As EventHandler
        Public Event CardMoved As EventHandler

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

        Public ReadOnly Property ColumnCount As Integer
            Get
                Return _columns.Count
            End Get
        End Property

        Public ReadOnly Property CardCount As Integer
            Get
                Return _cardCount
            End Get
        End Property

        Public ReadOnly Property SelectedColumnKey As String
            Get
                Dim column As KanbanColumnState = GetSelectedColumn()
                If column Is Nothing Then Return String.Empty
                Return column.Key
            End Get
        End Property

        Public ReadOnly Property SelectedCardKey As String
            Get
                Dim card As KanbanCardState = GetSelectedCard()
                If card Is Nothing Then Return String.Empty
                Return card.Key
            End Get
        End Property

        Public ReadOnly Property SummaryText As String
            Get
                Return MASKanbanBoardPolicy.BuildSummary(ColumnCount, CardCount)
            End Get
        End Property

        Public Function GetColumnKeys() As IReadOnlyList(Of String)
            Dim keys As New List(Of String)()
            For Each column As KanbanColumnState In _columns
                keys.Add(column.Key)
            Next
            Return New ReadOnlyCollection(Of String)(keys)
        End Function

        Public Function GetCardKeys(columnKey As String) As IReadOnlyList(Of String)
            Dim column As KanbanColumnState = FindColumn(columnKey)
            Dim keys As New List(Of String)()
            If column IsNot Nothing Then
                For Each card As KanbanCardState In column.Cards
                    keys.Add(card.Key)
                Next
            End If
            Return New ReadOnlyCollection(Of String)(keys)
        End Function

        Public Function AddColumn(columnKey As String,
                                  title As String) As MASKanbanBoard
            ClearKanbanSourceLinkForManualMutation()
            If _columns.Count >= KanbanBoardTokens.MaxColumns Then Return Me
            Dim normalizedKey As String = MASKanbanBoardPolicy.NormalizeKey(columnKey, "column", _columns.Count + 1)
            If FindColumn(normalizedKey) IsNot Nothing Then Return Me
            Dim column As New KanbanColumnState(normalizedKey, MASKanbanBoardPolicy.NormalizeColumnTitle(title))
            _columnIndexByKey.Add(column.Key, _columns.Count)
            _columns.Add(column)
            _columnLookup.Add(column.Key, column)
            If _selectedColumnIndex < 0 Then
                _selectedColumnIndex = 0
                _selectedCardIndex = -1
            End If
            NormalizeSelection()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASKanbanBoard.AddColumn")
            InvalidateVisual()
            RaiseBoardChangedSafe()
            Return Me
        End Function

        Public Function AddCard(columnKey As String,
                                cardKey As String,
                                title As String,
                                Optional detail As String = Nothing,
                                Optional badgeText As String = Nothing) As MASKanbanBoard
            ClearKanbanSourceLinkForManualMutation()
            If CardCount >= KanbanBoardTokens.MaxCards Then Return Me
            Dim column As KanbanColumnState = FindColumn(columnKey)
            If column Is Nothing Then Return Me
            Dim normalizedKey As String = MASKanbanBoardPolicy.NormalizeKey(cardKey, "card", CardCount + 1)
            If FindCard(normalizedKey) IsNot Nothing Then Return Me
            Dim columnIndex As Integer = ResolveColumnIndex(column)
            If columnIndex < 0 Then Return Me
            column.Cards.Add(New KanbanCardState(normalizedKey,
                                                 MASKanbanBoardPolicy.NormalizeCardTitle(title),
                                                 MASKanbanBoardPolicy.NormalizeCardDetail(detail),
                                                 MASKanbanBoardPolicy.NormalizeBadgeText(badgeText)))
            _cardCount += 1
            _cardLocationByKey(normalizedKey) = Tuple.Create(columnIndex, column.Cards.Count - 1)
            If _selectedColumnIndex < 0 Then _selectedColumnIndex = columnIndex
            If _selectedColumnIndex = columnIndex AndAlso _selectedCardIndex < 0 Then _selectedCardIndex = 0
            NormalizeSelection()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASKanbanBoard.AddCard")
            InvalidateVisual()
            RaiseBoardChangedSafe()
            Return Me
        End Function

        Public Function SelectCard(cardKey As String) As Boolean
            Dim location As Tuple(Of Integer, Integer) = FindCardLocation(cardKey)
            If location Is Nothing Then Return False
            SetSelection(location.Item1, location.Item2, True)
            Return True
        End Function

        Public Function MoveCard(cardKey As String,
                                 targetColumnKey As String) As Boolean
            Dim targetColumn As KanbanColumnState = FindColumn(targetColumnKey)
            If targetColumn Is Nothing Then Return False
            Dim location As Tuple(Of Integer, Integer) = FindCardLocation(cardKey)
            If location Is Nothing Then Return False
            If location.Item1 < 0 OrElse location.Item1 >= _columns.Count Then Return False
            If location.Item2 < 0 OrElse location.Item2 >= _columns(location.Item1).Cards.Count Then Return False
            If Object.ReferenceEquals(_columns(location.Item1), targetColumn) Then Return True
            Return MoveCardToIndex(cardKey, targetColumnKey, targetColumn.Cards.Count)
        End Function

        Public Function MoveCardToIndex(cardKey As String,
                                        targetColumnKey As String,
                                        targetCardIndex As Integer) As Boolean
            Dim dropRule As MASKanbanBoardDropRuleSnapshot = CreateDropRuleSnapshot(cardKey, targetColumnKey, targetCardIndex)
            If dropRule Is Nothing OrElse Not dropRule.IsLegalTarget Then Return False
            If dropRule.IsNoOp Then Return True

            ClearKanbanSourceLinkForManualMutation()
            Dim targetColumn As KanbanColumnState = FindColumn(dropRule.TargetColumnKey)
            If targetColumn Is Nothing Then Return False

            Dim location As Tuple(Of Integer, Integer) = FindCardLocation(dropRule.CardKey)
            If location Is Nothing Then Return False
            If location.Item1 < 0 OrElse location.Item1 >= _columns.Count Then Return False

            Dim sourceColumn As KanbanColumnState = _columns(location.Item1)
            If sourceColumn Is Nothing OrElse location.Item2 < 0 OrElse location.Item2 >= sourceColumn.Cards.Count Then Return False

            Dim targetColumnIndex As Integer = ResolveColumnIndex(targetColumn)
            If targetColumnIndex < 0 Then Return False

            Dim card As KanbanCardState = sourceColumn.Cards(location.Item2)
            sourceColumn.Cards.RemoveAt(location.Item2)
            targetColumn.Cards.Insert(ResolveInsertCardIndex(targetColumn, dropRule.ResolvedTargetCardIndex), card)

            ReindexCardLocationsForColumn(location.Item1)
            If targetColumnIndex <> location.Item1 Then ReindexCardLocationsForColumn(targetColumnIndex)

            Dim selectedIndex As Integer = ResolveCardIndex(targetColumn, card.Key)
            SetSelection(targetColumnIndex, selectedIndex, False)
            NormalizeSelection()
            StartKanbanDropFeedbackMotion(targetColumnIndex, selectedIndex)
            RequestSizeLayoutRefreshForSizeAffectingChange("MASKanbanBoard.MoveCardToIndex")
            InvalidateVisual()
            RaiseBoardChangedSafe()
            RaiseCardMovedSafe()
            RaiseSelectedCardChangedSafe()
            Return True
        End Function
        Public Function MoveSelectedCardToColumn(targetColumnKey As String) As Boolean
            Dim selectedKey As String = SelectedCardKey
            If selectedKey.Length = 0 Then Return False
            Return MoveCard(selectedKey, targetColumnKey)
        End Function

        Public Function MoveSelectedCardBy(delta As Integer) As Boolean
            Dim selectedKey As String = SelectedCardKey
            Dim selectedColumnKey As String = SelectedColumnKey
            If selectedKey.Length = 0 OrElse selectedColumnKey.Length = 0 OrElse delta = 0 Then Return False
            Return MoveCardToIndex(selectedKey, selectedColumnKey, _selectedCardIndex + delta)
        End Function

        Public Function ClearBoard() As MASKanbanBoard
            ClearKanbanSourceLinkForManualMutation()
            If _columns.Count = 0 Then Return Me
            _columns.Clear()
            _columnLookup.Clear()
            _columnIndexByKey.Clear()
            _cardLocationByKey.Clear()
            _cardCount = 0
            _selectedColumnIndex = -1
            _selectedCardIndex = -1
            _leftColumnIndex = 0
            RequestSizeLayoutRefreshForSizeAffectingChange("MASKanbanBoard.ClearBoard")
            InvalidateVisual()
            RaiseBoardChangedSafe()
            RaiseSelectedCardChangedSafe()
            Return Me
        End Function

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

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

#End Region

    End Class

End Namespace
