Option Strict On
Option Explicit On

Imports System
Imports System.Globalization
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.ListPopup
Imports Nexamas.UI.Controls
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Public Nexamas UI time input control. The visible control is an input-like
    ''' popup trigger; the stepper clock surface is MAS-owned internal float content
    ''' so TimePicker does not expose a second public clock or scheduler API surface.
    ''' </summary>
    Partial Public NotInheritable Class MASTimePicker
        Inherits MASControlBase
        Implements IMASControlRuntimeServicesConsumer
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract
        Implements IMASLayoutSemanticBoundsProvider

#Region "Fields"

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

        Private ReadOnly _shadowPainter As New MASShadowPainter()
        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _popupService As DropDownService

        Private _selectedTime As Nullable(Of TimeSpan) = Nothing
        Private _minimumTime As Nullable(Of TimeSpan) = Nothing
        Private _maximumTime As Nullable(Of TimeSpan) = Nothing
        Private _minuteStep As Integer = TimePickerTokens.DefaultMinuteStep
        Private _placeholder As String = TimePickerTokens.Placeholder

        Private _hover As Boolean
        Private _pressed As Boolean
        Private _isClockOpen As Boolean
        Private _lastPixelBounds As SKRect

        Private _floatRuntime As MASFloatRuntime
        Private _runtimeServices As MASControlRuntimeServices

#End Region

#Region "Events"

        ''' <summary>
        ''' Raised when the effective selected time changes through the official TimePicker gateway.
        ''' Handler exceptions are contained at the Nexamas UI boundary.
        ''' </summary>
        Public Event SelectedTimeChanged As EventHandler

#End Region

#Region "Constructor"

        Public Sub New()
            MyBase.New()

            _popupService =
                New DropDownService(
                    hasOwnedFloat:=
                        Function(handle As MASFloatHandle) As Boolean
                            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()
                            If runtime Is Nothing OrElse handle Is Nothing Then Return False

                            Try
                                Return runtime.IsOpen(handle)
                            Catch masCaughtException1 As Exception
                                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException1, "MASTimePicker.IsClockOpen")
                                Return False
                            End Try
                        End Function,
                    closeOwnedFloat:=
                        Function(handle As MASFloatHandle) As Boolean
                            Dim runtime As MASFloatRuntime = ResolveFloatRuntime()
                            If runtime Is Nothing OrElse handle Is Nothing Then Return False

                            Try
                                Return runtime.Close(handle, MASFloatCloseReason.Programmatic)
                            Catch masCaughtException2 As Exception
                                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException2, "MASTimePicker.CloseClockFloat")
                                Return False
                            End Try
                        End Function,
                    showFloat:=
                        Function(ownerKey As String,
                                 content As IMASFloatContent,
                                 boundsPx As SKRect) As MASFloatHandle

                            Return ShowClockFloat(ownerKey, content, boundsPx)
                        End Function)
        End Sub

#End Region

#Region "Public API"

        Public Shared Function Create() As MASTimePicker
            Return New MASTimePicker()
        End Function

        Friend Shared Function [Default]() As MASTimePicker
            Return Create()
        End Function

        Friend Property FloatRuntime As MASFloatRuntime
            Get
                Return _floatRuntime
            End Get
            Set(value As MASFloatRuntime)
                If Object.ReferenceEquals(_floatRuntime, value) Then Return
                CloseClockSafe()
                _floatRuntime = value
                InvalidateVisual()
            End Set
        End Property

        Friend Property SurfaceSizeProvider As Func(Of SKSizeI) = Nothing

        Public Property SelectedTime As Nullable(Of TimeSpan)
            Get
                Return _selectedTime
            End Get
            Set(value As Nullable(Of TimeSpan))
                SetSelectedTimeInternal(value, True)
            End Set
        End Property

        Public Property MinimumTime As Nullable(Of TimeSpan)
            Get
                Return _minimumTime
            End Get
            Set(value As Nullable(Of TimeSpan))
                Dim normalized As Nullable(Of TimeSpan) = NormalizeNullableTime(value, _minuteStep)
                If NullableTimesEqual(_minimumTime, normalized) Then Return

                _minimumTime = normalized
                If _maximumTime.HasValue AndAlso _minimumTime.HasValue AndAlso _minimumTime.Value > _maximumTime.Value Then
                    _maximumTime = _minimumTime
                End If

                SetSelectedTimeInternal(_selectedTime, False)
                CloseClockIfOpenForPolicyChange()
                InvalidateVisual()
            End Set
        End Property

        Public Property MaximumTime As Nullable(Of TimeSpan)
            Get
                Return _maximumTime
            End Get
            Set(value As Nullable(Of TimeSpan))
                Dim normalized As Nullable(Of TimeSpan) = NormalizeNullableTime(value, _minuteStep)
                If NullableTimesEqual(_maximumTime, normalized) Then Return

                _maximumTime = normalized
                If _minimumTime.HasValue AndAlso _maximumTime.HasValue AndAlso _maximumTime.Value < _minimumTime.Value Then
                    _minimumTime = _maximumTime
                End If

                SetSelectedTimeInternal(_selectedTime, False)
                CloseClockIfOpenForPolicyChange()
                InvalidateVisual()
            End Set
        End Property

        Public Property MinuteStep As Integer
            Get
                Return _minuteStep
            End Get
            Set(value As Integer)
                Dim normalized As Integer = NormalizeMinuteStep(value)
                If _minuteStep = normalized Then Return

                _minuteStep = normalized
                _minimumTime = NormalizeNullableTime(_minimumTime, _minuteStep)
                _maximumTime = NormalizeNullableTime(_maximumTime, _minuteStep)
                If _minimumTime.HasValue AndAlso _maximumTime.HasValue AndAlso _minimumTime.Value > _maximumTime.Value Then
                    _maximumTime = _minimumTime
                End If

                SetSelectedTimeInternal(_selectedTime, False)
                CloseClockIfOpenForPolicyChange()
                InvalidateVisual()
            End Set
        End Property

        Public Property Placeholder As String
            Get
                Return _placeholder
            End Get
            Set(value As String)
                Dim normalized As String = If(value, String.Empty).Trim()
                If normalized.Length = 0 Then normalized = TimePickerTokens.Placeholder
                If String.Equals(_placeholder, normalized, StringComparison.Ordinal) Then Return

                _placeholder = normalized
                InvalidateLayoutSlotInternal()
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property DisplayText As String
            Get
                If Not _selectedTime.HasValue Then Return String.Empty
                Return FormatTime(_selectedTime.Value)
            End Get
        End Property

        Public Function WithSelectedTime(value As TimeSpan) As MASTimePicker
            SelectedTime = value
            Return Me
        End Function

        Public Function ClearTime() As MASTimePicker
            SelectedTime = Nothing
            Return Me
        End Function

        Public Function WithTimeRange(minimumTime As Nullable(Of TimeSpan),
                                      maximumTime As Nullable(Of TimeSpan)) As MASTimePicker
            _minimumTime = NormalizeNullableTime(minimumTime, _minuteStep)
            _maximumTime = NormalizeNullableTime(maximumTime, _minuteStep)

            If _minimumTime.HasValue AndAlso _maximumTime.HasValue AndAlso _minimumTime.Value > _maximumTime.Value Then
                Dim swap As TimeSpan = _minimumTime.Value
                _minimumTime = _maximumTime
                _maximumTime = swap
            End If

            SetSelectedTimeInternal(_selectedTime, False)
            CloseClockIfOpenForPolicyChange()
            InvalidateVisual()
            Return Me
        End Function

        Public Function WithMinuteStep(value As Integer) As MASTimePicker
            MinuteStep = value
            Return Me
        End Function

        Public Function WithPlaceholder(value As String) As MASTimePicker
            Placeholder = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As MASTimePicker
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

        Friend Function SetBounds(bounds As SKRect) As MASTimePicker
            SetLocalLayoutBoundsInternal(bounds)
            Return Me
        End Function

        Friend Function WithBounds(bounds As SKRect) As MASTimePicker
            Return SetBounds(bounds)
        End Function

        Friend Function WithBounds(x As Single,
                                   y As Single,
                                   width As Single,
                                   height As Single) As MASTimePicker
            SetLocalLayoutBoundsInternal(New SKRect(
                x,
                y,
                x + Math.Max(0.0F, width),
                y + Math.Max(0.0F, height)))
            Return Me
        End Function

        Friend Function WithFloatRuntime(runtime As MASFloatRuntime) As MASTimePicker
            FloatRuntime = runtime
            Return Me
        End Function

        Friend Function WithSurfaceSizeProvider(provider As Func(Of SKSizeI)) As MASTimePicker
            SurfaceSizeProvider = provider
            Return Me
        End Function

#End Region

#Region "Runtime Services"

        Friend Sub BindControlRuntimeServices(services As MASControlRuntimeServices) Implements IMASControlRuntimeServicesConsumer.BindControlRuntimeServices
            If Object.ReferenceEquals(_runtimeServices, services) Then Return

            CloseClockSafe()
            _runtimeServices = services
            InvalidateVisual()
        End Sub

#End Region

    End Class

End Namespace
