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 MASTreeGrid

#Region "Helpers"

        Private Function CollapseOrMoveToParent(row As TreeGridRowState,
                                                isRtl As Boolean) As Boolean
            If isRtl Then Return ExpandOrMoveToChild(row, False)
            If row.Children.Count > 0 AndAlso row.Expanded Then
                SetRowExpanded(row.Key, False)
                Return True
            End If
            If row.Parent IsNot Nothing Then
                SetSelection(row.Parent.Key, True)
                Return True
            End If
            Return False
        End Function

        Private Function ExpandOrMoveToChild(row As TreeGridRowState,
                                             isRtl As Boolean) As Boolean
            If isRtl Then Return CollapseOrMoveToParent(row, False)
            If row.Children.Count = 0 Then Return False
            If Not row.Expanded Then
                SetRowExpanded(row.Key, True)
                Return True
            End If
            SetSelection(row.Children(0).Key, True)
            Return True
        End Function

        Private Sub SelectVisibleIndex(index As Integer,
                                       visible As List(Of VisibleTreeGridRow))
            If visible Is Nothing OrElse visible.Count = 0 Then Return
            Dim normalized As Integer = MASTreeGridPolicy.ClampIndex(index, visible.Count)
            If normalized < 0 Then Return
            SetSelection(visible(normalized).Row.Key, True)
        End Sub

        Private Sub SetSelection(rowKey As String,
                                 shouldRaiseEvent As Boolean)
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return
            Dim previousKey As String = _selectedRowKey
            Dim changed As Boolean = Not String.Equals(previousKey, row.Key, StringComparison.Ordinal)
            _selectedRowKey = row.Key
            NormalizeViewState()
            If changed Then StartSelectionMotion(previousKey, _selectedRowKey)
            InvalidateVisual()
            If changed AndAlso shouldRaiseEvent Then RaiseSelectedRowChangedSafe()
        End Sub

        Private Sub NormalizeViewState()
            Dim visible As List(Of VisibleTreeGridRow) = BuildVisibleRows()
            If visible.Count = 0 Then
                _selectedRowKey = String.Empty
                _topRowIndex = 0
                Return
            End If
            If _selectedRowKey.Length = 0 OrElse FindVisibleIndex(_selectedRowKey, visible) < 0 Then
                _selectedRowKey = visible(0).Row.Key
            End If
            _topRowIndex = EnsureVisibleRowIndex(FindVisibleIndex(_selectedRowKey, visible), _topRowIndex, visible.Count, _rowViewportCapacity)
        End Sub

        Private Function BuildVisibleRows() As List(Of VisibleTreeGridRow)
            If _visibleRowsCacheRevision <> _treeRevision Then
                _visibleRowsCache.Clear()
                _visibleRowIndexByKey.Clear()

                For Each root As TreeGridRowState In _roots
                    AppendVisibleRowIfAllowed(root, 0)
                Next

                For i As Integer = 0 To _visibleRowsCache.Count - 1
                    Dim visibleRow As VisibleTreeGridRow = _visibleRowsCache(i)
                    If visibleRow IsNot Nothing AndAlso visibleRow.Row IsNot Nothing Then _visibleRowIndexByKey(visibleRow.Row.Key) = i
                Next

                _visibleRowsCacheRevision = _treeRevision
            End If
            Return _visibleRowsCache
        End Function

        Private Function EnsureAncestorsExpanded(row As TreeGridRowState) As Boolean
            Dim changed As Boolean = False
            Dim current As TreeGridRowState = row.Parent
            While current IsNot Nothing
                If Not current.Expanded Then
                    current.Expanded = True
                    changed = True
                End If
                current = current.Parent
            End While
            Return changed
        End Function

        Private Function FindColumn(columnKey As String) As TreeGridColumnState
            Dim normalized As String = If(columnKey, String.Empty).Trim()
            For Each column As TreeGridColumnState In _columns
                If String.Equals(column.Key, normalized, StringComparison.OrdinalIgnoreCase) Then Return column
            Next
            Return Nothing
        End Function

        Private Function FindRow(rowKey As String) As TreeGridRowState
            Dim normalized As String = If(rowKey, String.Empty).Trim()
            If normalized.Length = 0 Then Return Nothing
            Dim row As TreeGridRowState = Nothing
            If _rowLookup.TryGetValue(normalized, row) Then Return row
            Return Nothing
        End Function

        Private Sub InvalidateTreeProjection(reason As String)
            StopSelectionMotion()
            If _treeRevision = Integer.MaxValue Then
                _treeRevision = 0
                _visibleRowsCacheRevision = -1
            Else
                _treeRevision += 1
            End If
            _visibleRowsCache.Clear()
            _visibleRowIndexByKey.Clear()
        End Sub

        Private Sub CollectRowKeys(rows As IEnumerable(Of TreeGridRowState),
                                   keys As List(Of String))
            For Each row As TreeGridRowState In rows
                keys.Add(row.Key)
                CollectRowKeys(row.Children, keys)
            Next
        End Sub

        Private Shared Function EnsureVisibleRowIndex(selectedIndex As Integer,
                                                     currentTopIndex As Integer,
                                                     visibleRowCount As Integer,
                                                     visibleCapacity As Integer) As Integer
            If visibleRowCount <= 0 Then Return 0
            Dim capacity As Integer = Math.Max(1, visibleCapacity)
            Dim maxTop As Integer = Math.Max(0, visibleRowCount - capacity)
            Dim top As Integer = Math.Max(0, Math.Min(currentTopIndex, maxTop))
            If selectedIndex < 0 Then Return top
            If selectedIndex < top Then Return Math.Max(0, selectedIndex)
            If selectedIndex >= top + capacity Then Return Math.Max(0, Math.Min(maxTop, selectedIndex - capacity + 1))
            Return top
        End Function

        Private Function HasScrollableRowViewport() As Boolean
            Return BuildVisibleRows().Count > Math.Max(1, _rowViewportCapacity)
        End Function

        Private Function ResolveRowViewportMaxTop() As Integer
            Return Math.Max(0, BuildVisibleRows().Count - Math.Max(1, _rowViewportCapacity))
        End Function

        Private Function CanScrollRowViewport(delta As Integer) As Boolean
            If delta = 0 Then Return False
            Dim maxTop As Integer = ResolveRowViewportMaxTop()
            If maxTop <= 0 Then Return False
            If delta < 0 Then Return _topRowIndex < maxTop
            If delta > 0 Then Return _topRowIndex > 0
            Return False
        End Function

        Private Function FindVisibleIndex(rowKey As String,
                                          visible As List(Of VisibleTreeGridRow)) As Integer
            If visible Is Nothing Then Return -1
            BuildVisibleRows()
            Dim index As Integer = -1
            If _visibleRowIndexByKey.TryGetValue(If(rowKey, String.Empty), index) Then Return index
            Return -1
        End Function

        Private Function HitTestRow(pt As SKPoint) As TreeGridRowHit
            For Each hit As TreeGridRowHit In _rowHits
                If hit.Rect.Contains(pt.X, pt.Y) Then Return hit
            Next
            Return Nothing
        End Function

        Private Sub ResetPointerState()
            If _hoverRowKey.Length = 0 AndAlso _pressedRowKey.Length = 0 Then Return
            _hoverRowKey = String.Empty
            _pressedRowKey = String.Empty
            InvalidateVisual()
        End Sub

        Private Shared Function GetLabelColumnRect(rect As SKRect,
                                                   dpi As Single,
                                                   isRtl As Boolean) As SKRect
            Dim labelWidth As Single = Math.Max(TreeGridTokens.LabelColumnMinWidthDip * dpi, rect.Width * TreeGridTokens.LabelColumnPreferredRatio)
            labelWidth = Math.Min(labelWidth, rect.Width * 0.64F)
            If isRtl Then Return New SKRect(rect.Right - labelWidth, rect.Top, rect.Right, rect.Bottom)
            Return New SKRect(rect.Left, rect.Top, rect.Left + labelWidth, rect.Bottom)
        End Function

        Private Function GetVisibleValueColumnRects(rect As SKRect,
                                                    labelRect As SKRect,
                                                    dpi As Single,
                                                    isRtl As Boolean,
                                                    maxVisibleColumns As Integer) As List(Of SKRect)
            Dim result As New List(Of SKRect)()
            If _columns.Count = 0 Then Return result

            Dim valueArea As SKRect = If(isRtl,
                                         New SKRect(rect.Left, rect.Top, labelRect.Left, rect.Bottom),
                                         New SKRect(labelRect.Right, rect.Top, rect.Right, rect.Bottom))
            If valueArea.Width <= 1.0F Then Return result

            Dim minValueColumnWidth As Single = Math.Max(72.0F * dpi, 1.0F)
            Dim maxColumnsFromSpace As Integer = Math.Max(1, CInt(Math.Floor(valueArea.Width / minValueColumnWidth)))
            Dim maxVisible As Integer = Math.Max(1, Math.Min(Math.Max(1, maxVisibleColumns), maxColumnsFromSpace))
            Dim maxLeft As Integer = Math.Max(0, _columns.Count - maxVisible)
            _leftColumnIndex = Math.Max(0, Math.Min(_leftColumnIndex, maxLeft))
            Dim visibleCount As Integer = Math.Min(maxVisible, _columns.Count - _leftColumnIndex)
            If visibleCount <= 0 Then Return result
            Dim totalWeight As Single = 0.0F
            For i As Integer = 0 To visibleCount - 1
                Dim columnIndex As Integer = _leftColumnIndex + i
                totalWeight += Math.Max(0.25F, _columns(columnIndex).WidthWeight)
            Next
            If totalWeight <= 0.0F Then Return result

            Dim cursor As Single = valueArea.Left
            For i As Integer = 0 To visibleCount - 1
                Dim columnIndex As Integer = _leftColumnIndex + i
                Dim width As Single = valueArea.Width * Math.Max(0.25F, _columns(columnIndex).WidthWeight) / totalWeight
                Dim itemRect As New SKRect(cursor, valueArea.Top, cursor + width, valueArea.Bottom)
                result.Add(PixelSnap.SnapRect(itemRect, dpi))
                cursor += width
            Next
            If isRtl Then result.Reverse()
            Return result
        End Function

        Private Sub DrawVerticalDivider(canvas As SKCanvas,
                                        rect As SKRect,
                                        dpi As Single,
                                        accent As SKColor,
                                        isRtl As Boolean)
            Dim x As Single = If(isRtl, rect.Right, rect.Left)
            Using stroke As New SKPaint()
                stroke.IsAntialias = True
                stroke.Style = SKPaintStyle.Stroke
                stroke.StrokeWidth = Math.Max(1.0F, dpi)
                stroke.Color = accent.WithAlpha(TreeGridTokens.DividerAlpha)
                canvas.DrawLine(x, rect.Top + 5.0F * dpi, x, rect.Bottom - 5.0F * dpi, stroke)
            End Using
        End Sub

        Private Shared Function Inset(rect As SKRect,
                                      insetPx As Single) As SKRect
            Return New SKRect(rect.Left + insetPx,
                              rect.Top + insetPx,
                              rect.Right - insetPx,
                              rect.Bottom - insetPx)
        End Function

        Private Shared Function InsetHorizontal(rect As SKRect,
                                                insetPx As Single) As SKRect
            Return New SKRect(rect.Left + insetPx,
                              rect.Top,
                              rect.Right - insetPx,
                              rect.Bottom)
        End Function

        Private Sub RaiseTreeGridChangedSafe()
            Try
                RaiseEvent TreeGridChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.TreeGridChanged")
            End Try
        End Sub

        Private Sub RaiseSelectedRowChangedSafe()
            Try
                RaiseEvent SelectedRowChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.SelectedRowChanged")
            End Try
        End Sub

        Private Sub RaiseRowExpandedChangedSafe()
            Try
                RaiseEvent RowExpandedChanged(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.RowExpandedChanged")
            End Try
        End Sub

        Friend Function CreateTreeGridReadinessManifest() As MASTreeGridReadinessManifest
            Return MASTreeGridReadinessManifest.CreateDefault()
        End Function

#End Region

    End Class

End Namespace
