Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
Imports Nexamas.UI.Layout
Imports Nexamas.UI.TextInput
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Public NotInheritable Class MASPathTextBox
        Inherits MASInputBoxSkiaBase

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASPathTextBox))

#Region "Fields"

        Private _allowWildcards As Boolean
        Private _trimOuterSpacesOnAssign As Boolean = True
        Private _normalizeSlashes As Boolean = False
        Private _preferredSlash As Char = "\"c
        Private _browseHover As Boolean
        Private _browsePressed As Boolean

#End Region

#Region "Events"

        Friend Event PathBrowseRequested As EventHandler

#End Region

#Region "Ctor / Factory"

        Public Sub New()
            Me.Placeholder = MASPathTextBoxPolicy.ResolveDefaultPlaceholder()
        End Sub

        Public Sub New(path As String)
            Me.New()
            Me.Text = NormalizePathText(path)
        End Sub

        Friend Shared Function [Default]() As MASPathTextBox
            Return New MASPathTextBox()
        End Function

        Public Shared Function Create(Optional path As String = Nothing) As MASPathTextBox
            Return New MASPathTextBox(path)
        End Function

#End Region

#Region "Fluent API"

        Friend Overloads Sub SetBounds(x As Single,
                                       y As Single,
                                       width As Single,
                                       height As Single)

            Me.SetLocalLayoutBoundsInternal(
                New SKRect(x, y, x + width, y + height))
        End Sub

        Friend Function WithBounds(x As Single,
                                   y As Single,
                                   width As Single,
                                   height As Single) As MASPathTextBox

            SetBounds(x, y, width, height)
            Return Me
        End Function

        Public Function WithText(value As String) As MASPathTextBox
            Me.Text = NormalizePathText(value)
            Return Me
        End Function

        Public Function WithPath(value As String) As MASPathTextBox
            Me.Text = NormalizePathText(value)
            Return Me
        End Function

        Public Function WithPlaceholder(value As String) As MASPathTextBox
            Me.Placeholder = MASPathTextBoxPolicy.NormalizePlaceholder(value)
            Return Me
        End Function

        Public Function WithMaxLength(value As Integer) As MASPathTextBox
            Me.MaxLength = MASPathTextBoxPolicy.NormalizeMaxLength(value)
            Return Me
        End Function

        Public Function AllowPathWildcards() As MASPathTextBox
            Me.AllowWildcards = True
            Return Me
        End Function

        Public Function DisallowPathWildcards() As MASPathTextBox
            Me.AllowWildcards = False
            Return Me
        End Function

        Public Function EnableSlashNormalization() As MASPathTextBox
            Me.NormalizeSlashes = True
            Return Me
        End Function

        Public Function DisableSlashNormalization() As MASPathTextBox
            Me.NormalizeSlashes = False
            Return Me
        End Function

        Public Function UseBackslashes() As MASPathTextBox
            Me.PreferredSlash = "\"c
            Return Me
        End Function

        Public Function UseForwardSlashes() As MASPathTextBox
            Me.PreferredSlash = "/"c
            Return Me
        End Function

        Public Function TrimOuterSpaces() As MASPathTextBox
            Me.TrimOuterSpacesOnAssign = True
            Return Me
        End Function

        Public Function PreserveOuterSpaces() As MASPathTextBox
            Me.TrimOuterSpacesOnAssign = False
            Return Me
        End Function

        Public Function AsReadOnly() As MASPathTextBox
            Me.ReadOnly = True
            Return Me
        End Function

        Public Function AsEditable() As MASPathTextBox
            Me.ReadOnly = False
            Return Me
        End Function

        Public Function WithValidation(state As MASTextValidationState,
                                       Optional message As String = Nothing) As MASPathTextBox

            Me.SetValidation(state, message)
            Return Me
        End Function

        Public Function WithoutValidation() As MASPathTextBox
            Me.ClearValidation()
            Return Me
        End Function

        Public Function ShowValidation(value As Boolean) As MASPathTextBox
            Me.ShowValidationIndicator = value
            Return Me
        End Function

        Friend Function OnTextChanged(handler As Action(Of String)) As MASPathTextBox

            If handler Is Nothing Then
                Throw New ArgumentNullException(NameOf(handler))
            End If

            AddHandler Me.TextChanged,
                Sub(sender As Object, e As EventArgs)
                    handler(Me.Text)
                End Sub

            Return Me
        End Function

        Friend Function OnPathChanged(handler As Action(Of String)) As MASPathTextBox
            Return OnTextChanged(handler)
        End Function

        Friend Function OnBrowseRequested(handler As Action) As MASPathTextBox
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))

            AddHandler Me.PathBrowseRequested,
                Sub(sender As Object, e As EventArgs)
                    handler()
                End Sub

            Return Me
        End Function

        Public Function FocusInput() As MASPathTextBox
            Me.FocusText()
            Return Me
        End Function

        Public Function SelectTextAll() As MASPathTextBox
            Me.SelectAll()
            Return Me
        End Function

        Public Function ClearPath() As MASPathTextBox
            Me.Clear()
            Return Me
        End Function

#End Region

#Region "Path API"

        Public Property AllowWildcards As Boolean
            Get
                Return _allowWildcards
            End Get
            Set(value As Boolean)

                If _allowWildcards = value Then Return

                _allowWildcards = value
                NormalizeCurrentText()
            End Set
        End Property

        Public Property TrimOuterSpacesOnAssign As Boolean
            Get
                Return _trimOuterSpacesOnAssign
            End Get
            Set(value As Boolean)

                If _trimOuterSpacesOnAssign = value Then Return

                _trimOuterSpacesOnAssign = value
                NormalizeCurrentText()
            End Set
        End Property

        Public Property NormalizeSlashes As Boolean
            Get
                Return _normalizeSlashes
            End Get
            Set(value As Boolean)

                If _normalizeSlashes = value Then Return

                _normalizeSlashes = value
                NormalizeCurrentText()
            End Set
        End Property

        Public Property PreferredSlash As Char
            Get
                Return _preferredSlash
            End Get
            Set(value As Char)

                Dim normalized As Char = MASPathTextBoxPolicy.NormalizePreferredSlash(value, _preferredSlash)
                If _preferredSlash = normalized Then Return

                _preferredSlash = normalized
                NormalizeCurrentText()
            End Set
        End Property

        Public ReadOnly Property NormalizedPathText As String
            Get
                Return NormalizePathText(Me.Text)
            End Get
        End Property

        Public ReadOnly Property HasInvalidPathChars As Boolean
            Get
                Return MASPathTextBoxPolicy.HasInvalidPathChars(Me.Text, _allowWildcards)
            End Get
        End Property

        Public ReadOnly Property LooksLikeAbsolutePath As Boolean
            Get
                Return MASPathTextBoxPolicy.LooksLikeAbsolutePath(NormalizedPathText)
            End Get
        End Property

#End Region

#Region "Overrides"

        Protected Overrides ReadOnly Property HasLeftIcon As Boolean
            Get
                Return True
            End Get
        End Property

        Protected Overrides ReadOnly Property RightReservedWidthDip As Single
            Get
                Return MASPathTextBoxPolicy.BrowseReservedWidthDip
            End Get
        End Property

        Protected Overrides ReadOnly Property InteractionKindValue As Integer
            Get
                Return CInt(CommonEnums.ControlKind.PathTextBox)
            End Get
        End Property

        Protected Overrides Sub DrawLeftIcon(canvas As SKCanvas,
                                             iconRect As SKRect,
                                             dpi As Single)
            If canvas Is Nothing Then Return
            If iconRect.Width <= 1.0F OrElse iconRect.Height <= 1.0F Then Return

            Dim ctx As MASThemeContext = TryGetContext()
            Dim iconColor As SKColor = Nexamas.UI.Values.Color.Text.Muted.WithAlpha(190)

            If ctx IsNot Nothing AndAlso ctx.InputTheme IsNot Nothing Then
                iconColor = If(Me.Enabled,
                               ctx.InputTheme.Text.Placeholder.WithAlpha(210),
                               ctx.InputTheme.Text.Disabled.WithAlpha(180))
            End If

            MASIconPainter.Draw(
                canvas:=canvas,
                bounds:=iconRect,
                dpi:=PixelSnap.SafeDpi(dpi),
                kind:=MASPathTextBoxPolicy.ResolvePathIconKind(),
                color:=iconColor,
                style:=MASIconStyle.Outline)
        End Sub

        Protected Overrides Sub DrawRightAdornment(canvas As SKCanvas,
                                                   ctx As MASThemeContext,
                                                   rightRect As SKRect,
                                                   dpi As Single)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If rightRect.Width <= 1.0F OrElse rightRect.Height <= 1.0F Then Return

            Dim th As IMASInputTheme = ctx.InputTheme
            If th Is Nothing Then Return

            Dim circleRect As SKRect = InsetRect(rightRect, 4.5F * dpi)
            Dim baseColor As SKColor = th.Text.Placeholder.WithAlpha(110)
            Dim iconColor As SKColor = th.Text.Normal.WithAlpha(220)

            If Not Me.Enabled Then
                baseColor = th.Text.Disabled.WithAlpha(52)
                iconColor = th.Text.Disabled.WithAlpha(145)
            ElseIf _browsePressed Then
                If th.States.Pressed.Fill.Enabled Then
                    baseColor = th.States.Pressed.Fill.Color.WithAlpha(128)
                Else
                    baseColor = th.Text.Normal.WithAlpha(70)
                End If
            ElseIf _browseHover Then
                If th.States.Hover.Fill.Enabled Then
                    baseColor = th.States.Hover.Fill.Color.WithAlpha(98)
                Else
                    baseColor = th.Text.Normal.WithAlpha(42)
                End If
            End If

            Using fill As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = baseColor
            }
                canvas.DrawOval(circleRect, fill)
            End Using

            MASIconPainter.Draw(
                canvas:=canvas,
                bounds:=InsetRect(circleRect, 5.0F * dpi),
                dpi:=PixelSnap.SafeDpi(dpi),
                kind:=MASPathTextBoxPolicy.ResolveBrowseIconKind(),
                color:=iconColor,
                style:=MASIconStyle.Outline)
        End Sub

        Protected Overrides Sub OnMouseMove(ctx As MASThemeContext,
                                            ptPx As SKPoint)
            MyBase.OnMouseMove(ctx, ptPx)

            Dim hitBrowse As Boolean = IsPointInBrowseButton(ctx, ptPx)
            If hitBrowse <> _browseHover Then
                _browseHover = hitBrowse
                InvalidateVisual()
            End If
        End Sub

        Protected Overrides Sub OnMouseLeave(ctx As MASThemeContext)
            MyBase.OnMouseLeave(ctx)
            ResetBrowseInteractionState()
        End Sub

        Protected Overrides Function OnMouseDown(ctx As MASThemeContext,
                                                 ptPx As SKPoint,
                                                 button As Integer) As Boolean
            If button = CInt(MouseButtons.Left) AndAlso
               IsPointInBrowseButton(ctx, ptPx) Then
                _browsePressed = True
                InvalidateVisual()
                Return True
            End If

            Return MyBase.OnMouseDown(ctx, ptPx, button)
        End Function

        Protected Overrides Function OnMouseUp(ctx As MASThemeContext,
                                               ptPx As SKPoint,
                                               button As Integer) As Boolean
            If button = CInt(MouseButtons.Left) AndAlso _browsePressed Then
                Dim activate As Boolean = IsPointInBrowseButton(ctx, ptPx)
                _browsePressed = False
                _browseHover = activate

                If activate Then RaisePathBrowseRequested()

                InvalidateVisual()
                Return True
            End If

            Return MyBase.OnMouseUp(ctx, ptPx, button)
        End Function

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            If Not Me.ReadOnly AndAlso
               MASPathTextBoxPolicy.ShouldRequestBrowseFromKey(keyCode, Control.ModifierKeys) Then
                RaisePathBrowseRequested()
                Return True
            End If

            Return MyBase.OnKeyDown(ctx, keyCode)
        End Function

        Protected Overrides Function FilterIncomingText(value As String) As String
            Return NormalizePathText(value)
        End Function

        Protected Overrides Sub OnEnabledChanged()
            MyBase.OnEnabledChanged()
            ResetBrowseInteractionState()
        End Sub

        Protected Overrides Sub OnVisibleChanged()
            MyBase.OnVisibleChanged()
            ResetBrowseInteractionState()
        End Sub

        Protected Overrides Sub OnDetached()
            MyBase.OnDetached()
            ResetBrowseInteractionState()
        End Sub

        Friend Function CreatePathTextBoxReadinessManifest() As Nexamas.UI.Components.Governance.MASPathTextBoxReadinessManifest
            Return Nexamas.UI.Components.Governance.MASPathTextBoxReadinessManifest.CreateCurrent()
        End Function

#End Region

#Region "Helpers"

        Private Sub NormalizeCurrentText()
            Dim normalized As String = NormalizePathText(Me.Text)

            If String.Equals(Me.Text, normalized, StringComparison.Ordinal) Then
                Return
            End If

            Me.Text = normalized
        End Sub

        Private Function NormalizePathText(value As String) As String
            Return MASPathTextBoxPolicy.NormalizePathText(
                value:=value,
                trimOuterSpacesOnAssign:=_trimOuterSpacesOnAssign,
                normalizeSlashes:=_normalizeSlashes,
                preferredSlash:=_preferredSlash,
                allowWildcards:=_allowWildcards)
        End Function

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

            Dim rr As SKRect = GetBrowseButtonRectPx(ctx)
            If rr.Width <= 1.0F OrElse rr.Height <= 1.0F Then Return False

            Return rr.Contains(ptPx.X, ptPx.Y)
        End Function

        Private Function GetBrowseButtonRectPx(ctx As MASThemeContext) As SKRect
            Dim pixelBounds As SKRect = GetPixelBounds(ctx)
            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim ringM As MASSurfaceInteractionRing.Metrics = MASSurfaceInteractionRing.GetMetrics(dpi)
            Dim rBase As SKRect = MASSurfaceInteractionRing.SnapBaseRect(pixelBounds, ringM)

            Return InputLayoutPrimitives.ComputeInputRightAccessoryRect(
                r:=rBase,
                dpi:=dpi,
                reservedWidthDip:=MASPathTextBoxPolicy.BrowseReservedWidthDip)
        End Function

        Private Sub RaisePathBrowseRequested()
            RaiseEvent PathBrowseRequested(Me, EventArgs.Empty)
        End Sub

        Private Sub ResetBrowseInteractionState()
            Dim changed As Boolean = False

            If _browseHover Then
                _browseHover = False
                changed = True
            End If

            If _browsePressed Then
                _browsePressed = False
                changed = True
            End If

            If changed Then InvalidateVisual()
        End Sub

        Private Shared Function InsetRect(r As SKRect,
                                          amount As Single) As SKRect
            Return New SKRect(
                r.Left + amount,
                r.Top + amount,
                r.Right - amount,
                r.Bottom - amount)
        End Function

#End Region

#Region "Unified MASSize Fluent API"

        ''' <summary>
        ''' Strongly typed entry into the shared MASSize intent API.
        ''' This method keeps fluent chains on the concrete element while delegating all sizing decisions to MASControlBase/MASSize.
        ''' </summary>
        Public Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As MASPathTextBox
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region
    End Class

End Namespace
