Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports Nexamas.UI.Controls
Imports Nexamas.UI.TextInput
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public MustInherit Class MASInputBoxSkiaBase


#Region "Mouse"

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext, ptPx As SKPoint)
            Dim hit As Boolean = Me.HitTestPx(ctx, ptPx)

            If _selectionController.SetHover(hit) Then
                InvalidateVisual()
            End If

            If _selectionController.DragSelecting AndAlso Me.HasKeyboardFocus Then
                HandleDragSelectionMove(ctx, ptPx)
            End If
        End Sub

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            If _selectionController.ClearHoverAndPressIfIdle() Then
                InvalidateVisual()
            End If
        End Sub

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext,
                                                 ptPx As SKPoint,
                                                 button As Integer) As Boolean
            ValidateUIContract()

            If Not Me.Enabled Then Return False
            If button <> CInt(MouseButtons.Left) Then Return False

            If Not Me.HasKeyboardFocus Then RequestFocus()

            ResetCaretBlink()

            Dim idx As Integer = GetIndexFromPoint(ctx, ptPx)

            If IsDoubleClick(ptPx, idx) Then
                _selectionController.EndPointerGesture()

                Dim changed As Boolean = _controller.SelectWordAt(idx)
                RememberClick(ptPx, idx)

                If changed Then
                    AfterCaretMove(ctx)
                Else
                    ResetCaretBlink()
                    InvalidateVisual()
                End If

                Return True
            End If

            ' Text inputs are edit surfaces, not pressable command buttons.
            ' Mouse-down starts a caret/selection gesture only; applying the generic
            ' Pressed material state while the pointer is held can visually wash over
            ' the text layer until MouseUp. Keeping _selectionController.Pressed False makes the whole
            ' text-input family stable during click/drag selection while preserving
            ' hover, focus, caret, selection, and specialized adornment buttons.
            _selectionController.BeginTextSelectionGesture()

            Dim selectionChanged As Boolean = _controller.BeginSelection(idx)
            RememberClick(ptPx, idx)

            If selectionChanged Then
                AfterCaretMove(ctx)
            Else
                ResetCaretBlink()
                InvalidateVisual()
            End If

            Return True
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean
            ValidateUIContract()

            If button <> CInt(MouseButtons.Left) Then Return False

            Dim wasSelecting As Boolean = _selectionController.DragSelecting
            Dim wasPressed As Boolean = _selectionController.EndPointerGesture()

            If Not wasPressed Then Return False

            If wasSelecting Then
                Dim idx As Integer = GetIndexFromPoint(ctx, ptPx)
                Dim changed As Boolean

                If _controller.HasActiveSelectionGesture Then
                    changed = _controller.ExtendSelectionTo(idx)
                Else
                    changed = _controller.SetCaret(idx)
                End If

                changed = _controller.EndSelectionGesture() OrElse changed

                If changed Then
                    AfterCaretMove(ctx)
                Else
                    ResetCaretBlink()
                    InvalidateVisual()
                End If
            End If

            Return True
        End Function

        Friend Overrides Function WantsPointerWheel(ctx As MASThemeContext, ptPx As SKPoint) As Boolean
            If ctx Is Nothing OrElse Not Me.Enabled Then Return False
            Return _multilineController.Enabled
        End Function

        Friend Overrides Function CanHandlePointerWheel(ctx As MASThemeContext, delta As Integer, ptPx As SKPoint) As Boolean
            If Not WantsPointerWheel(ctx, ptPx) Then Return False
            If delta = 0 Then Return False

            Dim contentR As SKRect = GetContentRectPx(ctx)
            If contentR.Width <= 2.0F OrElse contentR.Height <= 2.0F Then Return False

            Dim paragraph As MASTextParagraphLayout = BuildCurrentParagraphLayout(ctx)
            If paragraph Is Nothing Then Return False

            Dim maxScroll As Single = Math.Max(0.0F, paragraph.TotalHeight - contentR.Height)
            If maxScroll <= 0.01F Then Return False

            Dim stepPx As Single = ResolveMultilineWheelStepPx(ctx)
            Dim target As Single = _multilineController.VerticalScrollOffsetPx

            If delta > 0 Then
                target -= stepPx
            ElseIf delta < 0 Then
                target += stepPx
            Else
                Return False
            End If

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

            Return Math.Abs(target - _multilineController.VerticalScrollOffsetPx) > 0.01F
        End Function

        Protected Overrides Sub OnMouseWheel(ctx As MASThemeContext,
                                             delta As Integer,
                                             ptPx As SKPoint)

            If Not Me.HasKeyboardFocus Then RequestFocus()

            If _multilineController.Enabled Then
                Dim stepPx As Single = ResolveMultilineWheelStepPx(ctx)

                If delta > 0 Then
                    _multilineController.ScrollVerticalBy(-stepPx)
                ElseIf delta < 0 Then
                    _multilineController.ScrollVerticalBy(stepPx)
                End If

                InvalidateTextLayout()
                InvalidateVisual()
            End If

            ResetCaretBlink()
        End Sub

        Private Function ResolveMultilineWheelStepPx(ctx As MASThemeContext) As Single
            Dim stepPx As Single = 48.0F
            If ctx IsNot Nothing Then
                Dim pText As SKPaint = CreateTextPaint(ctx)
                If pText IsNot Nothing Then
                    Dim fm As SKFontMetrics = MASTextMetrics.ResolveFontMetrics(pText)
                    stepPx = Math.Max(16.0F, MASTextMetrics.ResolveLineHeight(fm, pText) * 3.0F)
                End If
            End If
            Return stepPx
        End Function

        Private Sub HandleDragSelectionMove(ctx As MASThemeContext,
                                            ptPx As SKPoint)

            If ctx Is Nothing Then Return

            Dim beforeCaret As Integer = _state.CaretIndex
            Dim beforeStart As Integer = _state.SelectionStart
            Dim beforeLength As Integer = _state.SelectionLength
            Dim beforeAnchor As Integer = _state.SelectionAnchor

            Dim idx As Integer = GetIndexFromPoint(ctx, ptPx)
            Dim changed As Boolean = _controller.ExtendSelectionTo(idx)

            ' Even when the index does not change, keeping the caret visible while
            ' dragging near/outside the horizontal edges helps single-line scrolling
            ' advance smoothly over repeated mouse-move messages.
            If changed OrElse IsPointerOutsideContentHorizontally(ctx, ptPx) Then
                AfterCaretMove(ctx)
                Return
            End If

            If beforeCaret <> _state.CaretIndex OrElse
               beforeStart <> _state.SelectionStart OrElse
               beforeLength <> _state.SelectionLength OrElse
               beforeAnchor <> _state.SelectionAnchor Then

                AfterCaretMove(ctx)
            End If
        End Sub

        Private Function IsPointerOutsideContentHorizontally(ctx As MASThemeContext,
                                                             ptPx As SKPoint) As Boolean
            If ctx Is Nothing Then Return False

            Dim contentR As SKRect = GetContentRectPx(ctx)
            If contentR.Width <= 2.0F Then Return False

            Return ptPx.X < contentR.Left OrElse ptPx.X > contentR.Right
        End Function

#End Region

#Region "Keyboard"

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            ValidateUIContract()

            If _multilineController.Enabled Then
                Dim shiftDown As Boolean = (Control.ModifierKeys And Keys.Shift) = Keys.Shift
                Dim ctrlDown As Boolean = (Control.ModifierKeys And Keys.Control) = Keys.Control

                If keyCode = Keys.Enter AndAlso Not ctrlDown Then
                    If IsReadOnlyBox Then
                        HandleConsumedInput()
                        Return True
                    End If

                    Dim changed As Boolean = _controller.InsertText(vbLf)
                    HandleTextMutationResult(ctx, changed)
                    Return True
                End If

                If keyCode = Keys.Up Then
                    HandleCaretMutationResult(ctx, MoveCaretMultilineVertical(ctx, -1, shiftDown))
                    Return True
                End If

                If keyCode = Keys.Down Then
                    HandleCaretMutationResult(ctx, MoveCaretMultilineVertical(ctx, 1, shiftDown))
                    Return True
                End If

                If keyCode = Keys.Home AndAlso Not ctrlDown Then
                    HandleCaretMutationResult(ctx, MoveCaretMultilineLineEdge(ctx, False, shiftDown))
                    Return True
                End If

                If keyCode = Keys.End AndAlso Not ctrlDown Then
                    HandleCaretMutationResult(ctx, MoveCaretMultilineLineEdge(ctx, True, shiftDown))
                    Return True
                End If
            End If

            Dim result As MASTextInputPipelineResult =
                _editingController.ProcessKey(
                    ctx:=CreateTextInputContext(ctx),
                    keyCode:=keyCode,
                    modifiers:=Control.ModifierKeys
                )

            ApplyInputPipelineResult(ctx, result)
            Return result.Handled
        End Function

        Protected Overrides Function OnTextInput(ctx As MASThemeContext,
                                                 text As String) As Boolean
            ValidateUIContract()

            Dim result As MASTextInputPipelineResult =
                _editingController.ProcessTextInput(
                    ctx:=CreateTextInputContext(ctx),
                    text:=text
                )

            ApplyInputPipelineResult(ctx, result)
            Return result.Handled
        End Function

#End Region

#Region "Input pipeline bridge"

        Private Function CreateTextInputContext(ctx As MASThemeContext) As MASTextInputContext
            Return New MASTextInputContext() With {
                .Controller = _controller,
                .IsEnabled = Me.Enabled,
                .HasKeyboardFocus = Me.HasKeyboardFocus,
                .IsReadOnly = IsReadOnlyBox,
                .RequestFocus = Sub()
                                    If Not Me.HasKeyboardFocus Then RequestFocus()
                                End Sub,
                .GetRemainingCapacity = Function()
                                            Return GetRemainingCapacity()
                                        End Function,
                .NormalizeIncomingText = Function(value As String, allowedSpace As Integer)
                                             Return NormalizeIncomingText(value, allowedSpace)
                                         End Function,
                .CopySelection = Function()
                                     Return CopySelection()
                                 End Function,
                .CutSelection = Function()
                                    Return CutSelection()
                                End Function,
                .PasteClipboard = Function()
                                      Return PasteClipboard()
                                  End Function,
                .MoveVisualCaret = Function(direction As Integer, extendSelection As Boolean)
                                       Return MoveCaretVisual(ctx, direction, extendSelection)
                                   End Function,
                .BeginComposition = Function(value As String, selectionStart As Integer, selectionLength As Integer)
                                        Return _controller.BeginComposition(value, selectionStart, selectionLength)
                                    End Function,
                .UpdateComposition = Function(value As String, selectionStart As Integer, selectionLength As Integer)
                                         Return _controller.UpdateComposition(value, selectionStart, selectionLength)
                                     End Function,
                .CommitComposition = Function(value As String)
                                         Return _controller.CommitComposition(value)
                                     End Function,
                .CancelComposition = Function()
                                         Return _controller.CancelComposition()
                                     End Function
            }
        End Function

        Private Sub ApplyInputPipelineResult(ctx As MASThemeContext,
                                             result As MASTextInputPipelineResult)
            If result Is Nothing OrElse Not result.Handled Then Return

            Select Case result.Kind
                Case MASTextInputPipelineResultKind.CaretChanged
                    HandleCaretMutationResult(ctx, result.Changed)

                Case MASTextInputPipelineResultKind.TextChanged
                    HandleTextMutationResult(ctx, result.Changed)

                Case MASTextInputPipelineResultKind.Consumed
                    HandleConsumedInput()

                Case MASTextInputPipelineResultKind.VisualChanged
                    HandleCompositionVisualResult(ctx, result.Changed)
            End Select
        End Sub

#End Region

#Region "Win32 IME bridge"

        ' CONTRACT:
        ' Stage 4B integration point.
        ' The WinForms host may forward WM_IME_* messages for the focused text input here.
        ' This method translates system IME messages into internal composition commands,
        ' then routes them through the existing input pipeline.
        Public Function ProcessWin32ImeMessage(hwnd As IntPtr,
                                               message As Integer,
                                               wParam As IntPtr,
                                               lParam As IntPtr) As Boolean _
                                               Implements IMASTextInputControl.ProcessWin32ImeMessage
            ValidateUIContract()

            If Not Me.Enabled Then Return False

            Dim ctx As MASThemeContext = TryGetContext()
            If ctx Is Nothing Then Return False

            Return _imeController.ProcessWin32Message(
                hwnd:=hwnd,
                message:=message,
                wParam:=wParam,
                lParam:=lParam,
                isEnabled:=Me.Enabled,
                dispatch:=Function(command As MASTextCompositionCommand)
                              Return ProcessImeCompositionCommand(ctx, command)
                          End Function,
                candidatePointProvider:=Function()
                                            Return GetImeCandidatePointPx(ctx)
                                        End Function)
        End Function

        Private Function ProcessImeCompositionCommand(ctx As MASThemeContext,
                                                      command As MASTextCompositionCommand) As Boolean
            If command Is Nothing Then Return False

            Dim result As MASTextInputPipelineResult =
                _editingController.ProcessComposition(
                    ctx:=CreateTextInputContext(ctx),
                    command:=command)

            ApplyInputPipelineResult(ctx, result)
            Return result IsNot Nothing AndAlso result.Handled
        End Function

        Private Function GetImeCandidatePointPx(ctx As MASThemeContext) As SKPoint
            If ctx Is Nothing Then Return New SKPoint(0.0F, 0.0F)

            Dim contentR As SKRect = GetContentRectPx(ctx)
            Dim pText As SKPaint = CreateTextPaint(ctx)
            Dim drawText As String = GetDisplayText()

            Return _imeController.ResolveCandidatePoint(
                ctx:=ctx,
                contentR:=contentR,
                paint:=pText,
                layout:=_layoutEngine,
                state:=_state,
                scrollOffsetPx:=_controller.ScrollOffsetPx,
                displayText:=drawText,
                isRtl:=ResolveIsRtl(drawText))
        End Function

#End Region

#Region "Clipboard"

        Private Function GetSelectedText() As String
            Return _state.SelectedText
        End Function

        Private Function CopySelection() As Boolean
            Return _clipboardController.CopySelection(_state)
        End Function

        Private Function CutSelection() As Boolean
            Return _clipboardController.CutSelection(
                state:=_state,
                controller:=_controller,
                isReadOnly:=IsReadOnlyBox)
        End Function

        Private Function PasteClipboard() As Boolean
            Return _clipboardController.PasteClipboard(
                controller:=_controller,
                isReadOnly:=IsReadOnlyBox,
                getRemainingCapacity:=Function()
                                          Return GetRemainingCapacity()
                                      End Function,
                normalizeIncomingText:=Function(value As String, allowedSpace As Integer)
                                           Return NormalizeIncomingText(value, allowedSpace)
                                       End Function)
        End Function

#End Region

#Region "Visual caret movement"

        Private Function MoveCaretVisual(ctx As MASThemeContext,
                                         direction As Integer,
                                         extendSelection As Boolean) As Boolean

            If ctx Is Nothing Then Return False

            Dim beforeCaret As Integer = _state.CaretIndex
            Dim beforeStart As Integer = _state.SelectionStart
            Dim beforeLength As Integer = _state.SelectionLength
            Dim beforeAnchor As Integer = _state.SelectionAnchor

            Dim contentR As SKRect = GetContentRectPx(ctx)
            If contentR.Width <= 2.0F Then Return False

            Dim pText As SKPaint = CreateTextPaint(ctx)
            If pText Is Nothing Then Return False

            Dim drawText As String = GetDisplayText()
            Dim isRtl As Boolean = ResolveIsRtl(drawText)

            Dim inlineLayout As MASTextInlineLayout =
                _layoutEngine.BuildInlineLayout(
                    contentR:=contentR,
                    paint:=pText,
                    scrollOffsetPx:=_controller.ScrollOffsetPx,
                    isRtl:=isRtl,
                    displayText:=drawText,
                    crispRendering:=_textCrispRendering
                )

            Dim target As Integer =
                inlineLayout.GetNextVisualCaretIndex(
                    currentIndex:=_state.CaretIndex,
                    direction:=direction
                )

            If extendSelection Then
                _controller.ExtendSelectionTo(target)
            Else
                _controller.SetCaret(target)
            End If

            Return beforeCaret <> _state.CaretIndex OrElse
                   beforeStart <> _state.SelectionStart OrElse
                   beforeLength <> _state.SelectionLength OrElse
                   beforeAnchor <> _state.SelectionAnchor
        End Function

#End Region


#Region "Multiline caret movement"

        Private Function MoveCaretMultilineVertical(ctx As MASThemeContext,
                                                    direction As Integer,
                                                    extendSelection As Boolean) As Boolean
            If ctx Is Nothing Then Return False

            Dim paragraph As MASTextParagraphLayout = BuildCurrentParagraphLayout(ctx)
            If paragraph Is Nothing Then Return False

            Dim nav As New MASTextMultilineVisualNavigation()
            Dim currentIndex As Integer = _state.CaretIndex
            Dim preferredX As Single? = nav.GetPreferredX(paragraph, currentIndex)

            Dim target As Integer
            If direction < 0 Then
                target = nav.MoveVisualLineUp(paragraph, currentIndex, preferredX)
            Else
                target = nav.MoveVisualLineDown(paragraph, currentIndex, preferredX)
            End If

            If extendSelection Then
                Return _controller.ExtendSelectionTo(target)
            End If

            Return _controller.SetCaret(target)
        End Function

        Private Function MoveCaretMultilineLineEdge(ctx As MASThemeContext,
                                                    moveToEnd As Boolean,
                                                    extendSelection As Boolean) As Boolean
            If ctx Is Nothing Then Return False

            Dim paragraph As MASTextParagraphLayout = BuildCurrentParagraphLayout(ctx)
            If paragraph Is Nothing Then Return False

            Dim nav As New MASTextMultilineVisualNavigation()
            Dim target As Integer

            If moveToEnd Then
                target = nav.MoveToVisualLineEnd(paragraph, _state.CaretIndex)
            Else
                target = nav.MoveToVisualLineStart(paragraph, _state.CaretIndex)
            End If

            If extendSelection Then
                Return _controller.ExtendSelectionTo(target)
            End If

            Return _controller.SetCaret(target)
        End Function

        Private Function BuildCurrentParagraphLayout(ctx As MASThemeContext) As MASTextParagraphLayout
            If ctx Is Nothing Then Return Nothing

            Dim contentR As SKRect = GetContentRectPx(ctx)
            If contentR.Width <= 2.0F OrElse contentR.Height <= 2.0F Then Return Nothing

            Dim pText As SKPaint = CreateTextPaint(ctx)
            If pText Is Nothing Then Return Nothing

            Return _layoutEngine.BuildParagraphLayout(
                contentR:=contentR,
                paint:=pText,
                displayText:=GetDisplayText(),
                wordWrap:=_multilineController.WordWrap,
                verticalScrollOffsetPx:=_multilineController.VerticalScrollOffsetPx,
                horizontalScrollOffsetPx:=_multilineController.ResolveHorizontalScrollOffset(_controller.ScrollOffsetPx),
                caretIndex:=_state.CaretIndex
            )
        End Function

#End Region

#Region "Post input hooks"

        Private Sub HandleCaretMutationResult(ctx As MASThemeContext,
                                              changed As Boolean)

            If changed Then
                AfterCaretMove(ctx)
            Else
                HandleConsumedInput()
            End If
        End Sub

        Private Sub HandleTextMutationResult(ctx As MASThemeContext,
                                             changed As Boolean)

            If changed Then
                AfterTextEdit(ctx, True)
            Else
                HandleConsumedInput()
            End If
        End Sub

        Private Sub HandleConsumedInput()
            ResetCaretBlink()
            InvalidateVisual()
        End Sub

        Private Sub HandleCompositionVisualResult(ctx As MASThemeContext,
                                                  changed As Boolean)
            ResetCaretBlink()

            If changed Then
                InvalidateTextLayout()
            End If

            InvalidateVisual()
        End Sub

        Private Sub AfterCaretMove(ctx As MASThemeContext)
            InvalidateTextLayout()
            EnsureCaretVisible(ctx)
            ResetCaretBlink()
            InvalidateVisual()
        End Sub

        Private Sub AfterTextEdit(ctx As MASThemeContext,
                                  changed As Boolean)

            If Not changed Then Return

            InvalidateTextLayout()
            EnsureCaretVisible(ctx)
            ResetCaretBlink()
            InvalidateVisual()
            RaiseTextChanged()
        End Sub

#End Region

    End Class

End Namespace
