Option Strict On
Option Explicit On

Imports System
Imports SkiaSharp

Namespace Nexamas.UI.TextInput

    ' CONTRACT:
    ' MASTextMultilineVisualNavigation performs caret movement using wrapped visual lines.
    '
    ' Responsibilities:
    ' - Move caret up/down by visual wrapped line.
    ' - Preserve preferred visual X while moving vertically.
    ' - Resolve Home/End inside the current visual line.
    '
    ' It must not:
    ' - Mutate MASTextInputState.
    ' - Render.
    ' - Handle keyboard or mouse directly.
    Friend NotInheritable Class MASTextMultilineVisualNavigation

        Friend Function MoveVisualLineUp(layout As MASTextParagraphLayout,
                                         currentTextIndex As Integer,
                                         preferredX As Single?) As Integer
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0

            Dim currentLine As MASTextWrappedLine = layout.GetLineFromTextIndex(currentTextIndex)
            If currentLine Is Nothing Then Return layout.ClampTextIndex(currentTextIndex)

            Dim targetLineIndex As Integer = currentLine.WrappedLineIndex - 1
            If targetLineIndex < 0 Then Return 0

            Return ResolveIndexOnVisualLine(layout, targetLineIndex, currentTextIndex, preferredX)
        End Function

        Friend Function MoveVisualLineDown(layout As MASTextParagraphLayout,
                                           currentTextIndex As Integer,
                                           preferredX As Single?) As Integer
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0

            Dim currentLine As MASTextWrappedLine = layout.GetLineFromTextIndex(currentTextIndex)
            If currentLine Is Nothing Then Return layout.ClampTextIndex(currentTextIndex)

            Dim targetLineIndex As Integer = currentLine.WrappedLineIndex + 1
            If targetLineIndex >= layout.LineCount Then Return layout.Text.Length

            Return ResolveIndexOnVisualLine(layout, targetLineIndex, currentTextIndex, preferredX)
        End Function

        Friend Function MoveToVisualLineStart(layout As MASTextParagraphLayout,
                                              currentTextIndex As Integer) As Integer
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0

            Dim line As MASTextWrappedLine = layout.GetLineFromTextIndex(currentTextIndex)
            If line Is Nothing Then Return layout.ClampTextIndex(currentTextIndex)

            Return line.StartIndex
        End Function

        Friend Function MoveToVisualLineEnd(layout As MASTextParagraphLayout,
                                            currentTextIndex As Integer) As Integer
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0

            Dim line As MASTextWrappedLine = layout.GetLineFromTextIndex(currentTextIndex)
            If line Is Nothing Then Return layout.ClampTextIndex(currentTextIndex)

            Return line.EndIndexExclusive
        End Function

        Friend Function GetPreferredX(layout As MASTextParagraphLayout,
                                      currentTextIndex As Integer) As Single
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0.0F

            Dim pt As SKPoint = layout.GetCaretPoint(currentTextIndex)
            Return pt.X
        End Function

        Private Shared Function ResolveIndexOnVisualLine(layout As MASTextParagraphLayout,
                                                         wrappedLineIndex As Integer,
                                                         currentTextIndex As Integer,
                                                         preferredX As Single?) As Integer
            If layout Is Nothing OrElse layout.LineCount <= 0 Then Return 0
            If wrappedLineIndex < 0 Then Return 0
            If wrappedLineIndex >= layout.LineCount Then Return layout.Text.Length

            Dim targetLine As MASTextWrappedLine = layout.Lines(wrappedLineIndex)
            If targetLine Is Nothing OrElse targetLine.InlineLayout Is Nothing Then
                Return layout.ClampTextIndex(currentTextIndex)
            End If

            Dim x As Single
            If preferredX.HasValue Then
                x = preferredX.Value
            Else
                x = layout.GetCaretPoint(currentTextIndex).X
            End If

            Dim localIndex As Integer = targetLine.InlineLayout.GetIndexFromX(x)
            Return targetLine.ToGlobalIndex(localIndex)
        End Function

    End Class

End Namespace
