Option Strict On
Option Explicit On

Imports System
Imports System.Globalization
Imports System.Text
Imports System.Windows.Forms
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.TextInput
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Public NotInheritable Class MASNumericTextBox
        Inherits MASInputBoxSkiaBase

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

#Region "Fields"

        Private _allowNegative As Boolean = True
        Private _allowDecimal As Boolean = False
        Private _decimalSeparator As Char = MASNumericTextBoxPolicy.ResolveDecimalSeparatorFromCulture(CultureInfo.CurrentCulture)

#End Region

#Region "Ctor / Factory"

        Public Sub New()
            _decimalSeparator = MASNumericTextBoxPolicy.ResolveDecimalSeparatorFromCulture(CultureInfo.CurrentCulture)
        End Sub

        Public Sub New(value As Integer)
            SetIntegerValue(value)
        End Sub

        Public Sub New(value As Double)
            _allowDecimal = True
            SetDoubleValue(value)
        End Sub

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

        Public Shared Function Create() As MASNumericTextBox
            Return New MASNumericTextBox()
        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 MASNumericTextBox

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

        Public Function WithPlaceholder(value As String) As MASNumericTextBox
            Me.Placeholder = MASNumericTextBoxPolicy.NormalizePlaceholder(value)
            Return Me
        End Function

        Public Function WithMaxLength(value As Integer) As MASNumericTextBox
            Me.MaxLength = MASNumericTextBoxPolicy.NormalizeMaxLength(value)
            Return Me
        End Function

        Public Function WithIntegerValue(value As Integer) As MASNumericTextBox
            SetIntegerValue(value)
            Return Me
        End Function

        Public Function WithDoubleValue(value As Double) As MASNumericTextBox
            SetDoubleValue(value)
            Return Me
        End Function

        Public Function AllowDecimalNumbers() As MASNumericTextBox
            Me.AllowDecimal = True
            Return Me
        End Function

        Public Function AllowOnlyInteger() As MASNumericTextBox
            Me.AllowDecimal = False
            Return Me
        End Function

        Public Function AllowNegativeNumbers() As MASNumericTextBox
            Me.AllowNegative = True
            Return Me
        End Function

        Public Function DisallowNegativeNumbers() As MASNumericTextBox
            Me.AllowNegative = False
            Return Me
        End Function

        Public Function WithDecimalSeparator(value As Char) As MASNumericTextBox
            Me.DecimalSeparator = value
            Return Me
        End Function

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

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

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

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

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

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

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

            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 OnValueChanged(handler As Action(Of Double?)) As MASNumericTextBox

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

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

            Return Me
        End Function

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

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

        Public Function ClearValue() As MASNumericTextBox
            Me.Clear()
            Return Me
        End Function

#End Region

#Region "Numeric API"

        Public Property AllowNegative As Boolean
            Get
                Return _allowNegative
            End Get
            Set(value As Boolean)

                If _allowNegative = value Then Return

                _allowNegative = MASNumericTextBoxPolicy.NormalizeAllowNegative(value)

                NormalizeCurrentText()
            End Set
        End Property

        Public Property AllowDecimal As Boolean
            Get
                Return _allowDecimal
            End Get
            Set(value As Boolean)

                If _allowDecimal = value Then Return

                _allowDecimal = MASNumericTextBoxPolicy.NormalizeAllowDecimal(value)

                NormalizeCurrentText()
            End Set
        End Property

        Public Property DecimalSeparator As Char
            Get
                Return _decimalSeparator
            End Get
            Set(value As Char)

                Dim normalized As Char = MASNumericTextBoxPolicy.ResolveDecimalSeparator(value, CultureInfo.CurrentCulture)
                If _decimalSeparator = normalized Then Return

                _decimalSeparator = normalized

                NormalizeCurrentText()
            End Set
        End Property

        Public ReadOnly Property IntegerValue As Integer?
            Get

                If String.IsNullOrWhiteSpace(Me.Text) Then
                    Return Nothing
                End If

                If MASNumericTextBoxPolicy.IsIncompleteNumber(Me.Text, _decimalSeparator) Then
                    Return Nothing
                End If

                Dim s As String = NormalizeForParsing(Me.Text)

                Dim value As Integer

                If Integer.TryParse(
                    s,
                    NumberStyles.Integer,
                    CultureInfo.InvariantCulture,
                    value) Then

                    Return value
                End If

                Return Nothing
            End Get
        End Property

        Public ReadOnly Property DoubleValue As Double?
            Get

                If String.IsNullOrWhiteSpace(Me.Text) Then
                    Return Nothing
                End If

                If MASNumericTextBoxPolicy.IsIncompleteNumber(Me.Text, _decimalSeparator) Then
                    Return Nothing
                End If

                Dim s As String = NormalizeForParsing(Me.Text)

                Dim value As Double

                If Double.TryParse(
                    s,
                    NumberStyles.Float,
                    CultureInfo.InvariantCulture,
                    value) Then

                    Return value
                End If

                Return Nothing
            End Get
        End Property

        Public Sub SetIntegerValue(value As Integer)

            Me.Text = MASNumericTextBoxPolicy.FormatIntegerValue(value, CultureInfo.CurrentCulture)
        End Sub

        Public Sub SetDoubleValue(value As Double)

            Me.Text = MASNumericTextBoxPolicy.FormatDoubleValue(value, _decimalSeparator, CultureInfo.CurrentCulture)
        End Sub

#End Region

#Region "Overrides"

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

        Protected Overrides ReadOnly Property RightReservedWidthDip As Single
            Get
                Return 0.0F
            End Get
        End Property

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

        Protected Overrides Function OnKeyDown(ctx As MASThemeContext,
                                               keyCode As Keys) As Boolean
            Dim delta As Double
            If Not Me.ReadOnly AndAlso MASNumericTextBoxPolicy.TryResolveKeyboardStep(keyCode, delta) Then
                ApplyKeyboardStep(delta)
                Return True
            End If

            Return MyBase.OnKeyDown(ctx, keyCode)
        End Function

        Protected Overrides Function FilterIncomingText(value As String) As String
            Return MASNumericTextBoxPolicy.NormalizeNumericText(
                value:=value,
                allowNegative:=_allowNegative,
                allowDecimal:=_allowDecimal,
                decimalSeparator:=_decimalSeparator)
        End Function

        Friend Function CreateNumericTextBoxReadinessManifest() As Nexamas.UI.Components.Governance.MASNumericTextBoxReadinessManifest
            Return Nexamas.UI.Components.Governance.MASNumericTextBoxReadinessManifest.CreateCurrent()
        End Function

#End Region

#Region "Helpers"

        Private Sub NormalizeCurrentText()

            Dim normalized As String =
                FilterIncomingText(Me.Text)

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

            Me.Text = normalized
        End Sub

        Private Sub ApplyKeyboardStep(delta As Double)
            If delta = 0.0R Then Return

            Dim current As Double = 0.0R
            Dim parsed As Double? = Me.DoubleValue
            If parsed.HasValue Then current = parsed.Value

            Dim nextValue As Double = current + delta

            If Not _allowNegative AndAlso nextValue < 0.0R Then
                nextValue = 0.0R
            End If

            If Not _allowDecimal Then
                SetIntegerValue(CInt(Math.Round(nextValue, MidpointRounding.AwayFromZero)))
            Else
                SetDoubleValue(nextValue)
            End If

            SelectAll()
        End Sub

        Private Function NormalizeForParsing(value As String) As String
            Return MASNumericTextBoxPolicy.NormalizeForParsing(value, _decimalSeparator)
        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 MASNumericTextBox
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function






#End Region
    End Class

End Namespace
