Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.TextRendering
Imports SkiaSharp

Namespace Nexamas.UI.TextInput

    ' CONTRACT:
    ' MASTextInputLayoutEngine is a geometry/calculation service only.
    '
    ' Stage 5B adds internal multiline/wrapping layout helpers while preserving
    ' the existing single-line contract and behavior.
    Friend NotInheritable Class MASTextInputLayoutEngine

        Private Const CARET_PADDING_PX As Single = 8.0F

        Friend Function BuildInlineLayout(contentR As SKRect,
                                          paint As SKPaint,
                                          scrollOffsetPx As Single,
                                          isRtl As Boolean,
                                          displayText As String,
                                          Optional crispRendering As Boolean = True) As MASTextInlineLayout

            Return MASTextInlineLayout.Create(contentR, paint, scrollOffsetPx, displayText, isRtl, crispRendering)
        End Function

        Friend Function BuildParagraphLayout(contentR As SKRect,
                                             paint As SKPaint,
                                             displayText As String,
                                             wordWrap As Boolean,
                                             verticalScrollOffsetPx As Single,
                                             horizontalScrollOffsetPx As Single,
                                             Optional normalizeLineEndings As Boolean = True,
                                             Optional crispRendering As Boolean = True,
                                             Optional caretIndex As Integer = -1) As MASTextParagraphLayout

            Dim document As MASTextMultilineDocument =
                MASTextMultilineDocument.FromText(
                    value:=If(displayText, String.Empty),
                    normalizeLineEndings:=normalizeLineEndings)

            Return MASTextParagraphLayout.Create(
                document:=document,
                contentR:=contentR,
                paint:=paint,
                verticalScrollOffsetPx:=verticalScrollOffsetPx,
                wordWrap:=wordWrap,
                horizontalScrollOffsetPx:=horizontalScrollOffsetPx,
                crispRendering:=crispRendering,
                caretIndex:=caretIndex)
        End Function

        Friend Function MeasureTextWidth(value As String, paint As SKPaint) As Single
            If String.IsNullOrEmpty(value) Then Return 0.0F
            If paint Is Nothing Then Return 0.0F
            Return Math.Max(0.0F, MASShapedText.MeasureWidth(value, paint))
        End Function

        Friend Function GetTextX(contentR As SKRect,
                                 paint As SKPaint,
                                 scrollOffsetPx As Single,
                                 isRtl As Boolean,
                                 displayText As String,
                                 Optional crispRendering As Boolean = True) As Single

            Return BuildInlineLayout(contentR, paint, scrollOffsetPx, isRtl, displayText, crispRendering).TextX
        End Function

        Friend Function GetCaretX(contentR As SKRect,
                                  paint As SKPaint,
                                  scrollOffsetPx As Single,
                                  caretIndex As Integer,
                                  isRtl As Boolean,
                                  displayText As String,
                                  Optional crispRendering As Boolean = True) As Single

            Return GetCaretXForIndex(contentR, paint, scrollOffsetPx, caretIndex, isRtl, displayText, crispRendering)
        End Function

        Friend Function GetCaretXForIndex(contentR As SKRect,
                                          paint As SKPaint,
                                          scrollOffsetPx As Single,
                                          index As Integer,
                                          isRtl As Boolean,
                                          displayText As String,
                                          Optional crispRendering As Boolean = True) As Single

            Dim text As String = If(displayText, String.Empty)
            Dim safeIndex As Integer = ClampIndex(index, text.Length)

            Return BuildInlineLayout(contentR, paint, scrollOffsetPx, isRtl, text, crispRendering).GetCaretX(safeIndex)
        End Function

        Friend Function GetIndexFromPoint(contentR As SKRect,
                                          paint As SKPaint,
                                          scrollOffsetPx As Single,
                                          ptPx As SKPoint,
                                          isRtl As Boolean,
                                          displayText As String,
                                          Optional crispRendering As Boolean = True) As Integer

            Dim text As String = If(displayText, String.Empty)

            Dim layout As MASTextInlineLayout =
                BuildInlineLayout(contentR, paint, scrollOffsetPx, isRtl, text, crispRendering)

            Return ClampIndex(layout.GetIndexFromX(ptPx.X), text.Length)
        End Function

        Friend Function GetMultilineIndexFromPoint(contentR As SKRect,
                                                   paint As SKPaint,
                                                   ptPx As SKPoint,
                                                   displayText As String,
                                                   wordWrap As Boolean,
                                                   verticalScrollOffsetPx As Single,
                                                   horizontalScrollOffsetPx As Single,
                                                   Optional crispRendering As Boolean = True) As Integer

            Dim layout As MASTextParagraphLayout =
                BuildParagraphLayout(
                    contentR:=contentR,
                    paint:=paint,
                    displayText:=displayText,
                    wordWrap:=wordWrap,
                    verticalScrollOffsetPx:=verticalScrollOffsetPx,
                    horizontalScrollOffsetPx:=horizontalScrollOffsetPx,
                    crispRendering:=crispRendering)

            Return layout.GetIndexFromPoint(ptPx)
        End Function

        Friend Function GetMultilineCaretPoint(contentR As SKRect,
                                               paint As SKPaint,
                                               caretIndex As Integer,
                                               displayText As String,
                                               wordWrap As Boolean,
                                               verticalScrollOffsetPx As Single,
                                               horizontalScrollOffsetPx As Single,
                                               Optional crispRendering As Boolean = True) As SKPoint

            Dim text As String = If(displayText, String.Empty)
            Dim safeCaret As Integer = ClampIndex(caretIndex, text.Length)

            Dim layout As MASTextParagraphLayout =
                BuildParagraphLayout(
                    contentR:=contentR,
                    paint:=paint,
                    displayText:=text,
                    wordWrap:=wordWrap,
                    verticalScrollOffsetPx:=verticalScrollOffsetPx,
                    horizontalScrollOffsetPx:=horizontalScrollOffsetPx,
                    crispRendering:=crispRendering,
                    caretIndex:=safeCaret)

            Return layout.GetCaretPoint(safeCaret)
        End Function

        Friend Function EnsureCaretVisible(contentR As SKRect,
                                           paint As SKPaint,
                                           currentScrollOffsetPx As Single,
                                           caretIndex As Integer,
                                           isRtl As Boolean,
                                           displayText As String,
                                           Optional crispRendering As Boolean = True) As Single

            If contentR.Width <= 2.0F Then Return 0.0F
            If paint Is Nothing Then Return 0.0F

            Dim text As String = If(displayText, String.Empty)
            Dim safeCaretIndex As Integer = ClampIndex(caretIndex, text.Length)
            Dim fullWidth As Single = MeasureTextWidth(text, paint)
            Dim visibleW As Single = Math.Max(0.0F, contentR.Width)

            If fullWidth <= visibleW + 0.5F Then Return 0.0F

            Dim scroll As Single = NormalizeScroll(currentScrollOffsetPx)

            Dim layout As MASTextInlineLayout =
                BuildInlineLayout(contentR, paint, scroll, isRtl, text, crispRendering)

            Dim caretX As Single = layout.GetCaretX(safeCaretIndex)
            Dim leftLimit As Single = contentR.Left + CARET_PADDING_PX
            Dim rightLimit As Single = contentR.Right - CARET_PADDING_PX

            If caretX < leftLimit Then
                If isRtl Then
                    scroll += (leftLimit - caretX)
                Else
                    scroll -= (leftLimit - caretX)
                End If
            ElseIf caretX > rightLimit Then
                If isRtl Then
                    scroll -= (caretX - rightLimit)
                Else
                    scroll += (caretX - rightLimit)
                End If
            End If

            Return EnsureScrollInRange(contentR, paint, scroll, isRtl, text)
        End Function

        Friend Function EnsureScrollInRange(contentR As SKRect,
                                            paint As SKPaint,
                                            currentScrollOffsetPx As Single,
                                            isRtl As Boolean,
                                            displayText As String) As Single

            If paint Is Nothing Then Return 0.0F

            Dim text As String = If(displayText, String.Empty)
            Dim fullWidth As Single = MeasureTextWidth(text, paint)
            Dim visibleW As Single = Math.Max(0.0F, contentR.Width)

            If fullWidth <= visibleW + 0.5F Then Return 0.0F

            Dim maxScroll As Single = Math.Max(0.0F, fullWidth - visibleW)
            Dim scroll As Single = NormalizeScroll(currentScrollOffsetPx)

            If isRtl Then
                If scroll > 0.0F Then scroll = 0.0F
                If scroll < -maxScroll Then scroll = -maxScroll
            Else
                If scroll < 0.0F Then scroll = 0.0F
                If scroll > maxScroll Then scroll = maxScroll
            End If

            Return scroll
        End Function

        Friend Function EnsureVerticalScrollInRange(contentR As SKRect,
                                                    paint As SKPaint,
                                                    displayText As String,
                                                    wordWrap As Boolean,
                                                    currentVerticalScrollPx As Single,
                                                    Optional crispRendering As Boolean = True) As Single

            If paint Is Nothing Then Return 0.0F
            If contentR.Height <= 1.0F Then Return 0.0F

            Dim layout As MASTextParagraphLayout =
                BuildParagraphLayout(
                    contentR:=contentR,
                    paint:=paint,
                    displayText:=displayText,
                    wordWrap:=wordWrap,
                    verticalScrollOffsetPx:=0.0F,
                    horizontalScrollOffsetPx:=0.0F,
                    crispRendering:=crispRendering)

            Dim maxScroll As Single = Math.Max(0.0F, layout.TotalHeight - contentR.Height)
            Dim scroll As Single = NormalizeScroll(currentVerticalScrollPx)

            If scroll < 0.0F Then scroll = 0.0F
            If scroll > maxScroll Then scroll = maxScroll

            Return scroll
        End Function

        Private Shared Function ClampIndex(value As Integer, maxLen As Integer) As Integer
            If value < 0 Then Return 0
            If value > maxLen Then Return maxLen
            Return value
        End Function

        Private Shared Function NormalizeScroll(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 0.0F
            If Math.Abs(value) < 0.01F Then Return 0.0F
            Return value
        End Function

    End Class

End Namespace
