Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.General
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Official Nexamas UI drawer gateway. It owns request state only and delegates
    ''' docking, lifecycle, outside dismissal, Escape handling, focus, and motion to
    ''' MASFloatRuntime. Visual content is hosted by MASSidePanel.
    ''' </summary>
    Public NotInheritable Class MASDrawer
        Implements IDisposable

#Region "Fields"

        Private ReadOnly _rootHost As MASSkiaRootHost
        Private ReadOnly _panel As MASSidePanel
        Private _title As String = String.Empty
        Private _side As MASDrawerSide = MASDrawerSide.Right
        Private _widthDip As Single = DrawerTokens.DefaultWidthDip
        Private _closeOnOutsidePointer As Boolean = True
        Private _swallowOutsidePointer As Boolean = True
        Private _closeOnEscape As Boolean = True
        Private _ownerKey As String = DrawerTokens.DefaultOwnerKey
        Private _handle As MASFloatHandle
        Private _content As MASFloatControlsContent
        Private _disposed As Boolean

#End Region

#Region "Events"

        Public Event Opened As EventHandler
        Public Event Closed As EventHandler

#End Region

#Region "Constructor"

        Friend Sub New(rootHost As MASSkiaRootHost,
                       Optional title As String = Nothing)
            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            _rootHost = rootHost
            _title = MASDrawerFloatPolicy.NormalizeTitle(title)
            _panel = MASSidePanel.Create(_title)
            AddHandler _panel.CloseRequested, AddressOf OnPanelCloseRequested
        End Sub

#End Region

#Region "Public API"

        Public ReadOnly Property Panel As MASSidePanel
            Get
                Return _panel
            End Get
        End Property

        Public Property Title As String
            Get
                Return _title
            End Get
            Set(value As String)
                Dim normalized As String = MASDrawerFloatPolicy.NormalizeTitle(value)
                If String.Equals(_title, normalized, StringComparison.Ordinal) Then Return
                _title = normalized
                _panel.Title = normalized
                RequestInvalidate()
            End Set
        End Property

        Public Property Side As MASDrawerSide
            Get
                Return _side
            End Get
            Set(value As MASDrawerSide)
                Dim normalized As MASDrawerSide = MASDrawerFloatPolicy.NormalizeSide(value)
                If _side = normalized Then Return
                _side = normalized
                If IsOpen Then Reopen()
            End Set
        End Property

        Public Property Width As Single
            Get
                Return _widthDip
            End Get
            Set(value As Single)
                Dim normalized As Single = MASDrawerFloatPolicy.NormalizeWidth(value)
                If Math.Abs(_widthDip - normalized) < 0.001F Then Return
                _widthDip = normalized
                If IsOpen Then Reopen()
            End Set
        End Property

        Public Property CloseOnOutsidePointer As Boolean
            Get
                Return _closeOnOutsidePointer
            End Get
            Set(value As Boolean)
                If _closeOnOutsidePointer = value Then Return
                _closeOnOutsidePointer = value
                If _content IsNot Nothing Then _content.CloseOnOutsidePointer = value
            End Set
        End Property

        Public Property SwallowOutsidePointer As Boolean
            Get
                Return _swallowOutsidePointer
            End Get
            Set(value As Boolean)
                If _swallowOutsidePointer = value Then Return
                _swallowOutsidePointer = value
                If _content IsNot Nothing Then _content.SwallowOutsidePointer = value
            End Set
        End Property

        Public Property CloseOnEscape As Boolean
            Get
                Return _closeOnEscape
            End Get
            Set(value As Boolean)
                If _closeOnEscape = value Then Return
                _closeOnEscape = value
                If _content IsNot Nothing Then _content.CloseOnEscape = value
            End Set
        End Property

        Public ReadOnly Property IsOpen As Boolean
            Get
                If _disposed OrElse _handle Is Nothing Then Return False

                Try
                    Return _rootHost.FloatRuntimeCore.IsOpen(_handle)
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(masCaughtException1, "MASDrawer.IsOpen")
                    Return False
                End Try
            End Get
        End Property

        Public Function Open() As MASDrawer
            If _disposed Then Throw New ObjectDisposedException(NameOf(MASDrawer))
            OpenCore()
            Return Me
        End Function

        Public Function Close() As MASDrawer
            CloseCore(MASFloatCloseReason.Programmatic)
            Return Me
        End Function

        Public Function Toggle() As MASDrawer
            If IsOpen Then
                Close()
            Else
                Open()
            End If

            Return Me
        End Function

        Public Function Add(control As MASControlBase) As MASDrawer
            _panel.Add(control)
            Return Me
        End Function

        Public Function Add(control As MASControlBase,
                            relativeContentBounds As SKRect) As MASDrawer
            _panel.Add(control, relativeContentBounds)
            Return Me
        End Function

        Public Function Remove(control As MASControlBase) As MASDrawer
            _panel.Remove(control)
            Return Me
        End Function

        Public Function Clear() As MASDrawer
            _panel.Clear()
            Return Me
        End Function

        Public Function WithTitle(value As String) As MASDrawer
            Title = value
            Return Me
        End Function

        Public Function WithSide(value As MASDrawerSide) As MASDrawer
            Side = value
            Return Me
        End Function

        Public Function WithWidth(value As Single) As MASDrawer
            Width = value
            Return Me
        End Function

        Public Function WithDismissPolicy(closeOnOutsidePointer As Boolean,
                                           closeOnEscape As Boolean,
                                           Optional swallowOutsidePointer As Boolean = True) As MASDrawer
            Me.CloseOnOutsidePointer = closeOnOutsidePointer
            Me.CloseOnEscape = closeOnEscape
            Me.SwallowOutsidePointer = swallowOutsidePointer
            Return Me
        End Function

#End Region

#Region "Float lifecycle"

        Private Sub OpenCore()
            If _rootHost Is Nothing Then Return
            If IsOpen Then Return

            EnsureContent()

            Dim availablePx As SKRect = ResolveAvailableBoundsPx()
            If availablePx.Width <= 1.0F OrElse availablePx.Height <= 1.0F Then Return

            Dim dpi As Single = ResolveDpi()
            Dim widthPx As Single = Math.Min(_widthDip * dpi, Math.Max(1.0F, availablePx.Width))
            Dim initialPx As New SKRect(0.0F, 0.0F, widthPx, availablePx.Height)

            Dim request As New MASFloatRequest() With {
                .FloatKey = MASFloatKeys.Drawer,
                .Content = _content,
                .OwnerKey = _ownerKey,
                .RequestedPresentation = MASFloatPresentationProfiles.Drawer(ResolveFloatPlacement()),
                .ShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                .Shield = MASFloatShieldOptions.None,
                .AvailableBoundsPx = availablePx,
                .InitialBoundsPx = initialPx,
                .OnOpened = Sub(handle As MASFloatHandle)
                                If handle IsNot Nothing Then _handle = handle
                                _panel.StartSidePanelRevealMotion()
                                RaiseOpenedSafe()
                            End Sub,
                .OnClosed = Sub(args As MASFloatClosedEventArgs)
                                _handle = Nothing
                                RaiseClosedSafe()
                            End Sub
            }

            Try
                _handle = _rootHost.FloatRuntimeCore.Show(request)
            Catch masCaughtException2 As Exception
                _handle = Nothing
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException2, "MASDrawer.Open")
            End Try

            RequestInvalidate()
        End Sub

        Private Sub CloseCore(reason As MASFloatCloseReason)
            If _handle Is Nothing OrElse _rootHost Is Nothing Then Return

            Dim closing As MASFloatHandle = _handle
            _handle = Nothing

            Try
                _rootHost.FloatRuntimeCore.Close(closing, reason)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException3, "MASDrawer.Close")
            End Try

            RequestInvalidate()
        End Sub

        Private Sub Reopen()
            If Not IsOpen Then Return
            CloseCore(MASFloatCloseReason.Programmatic)
            OpenCore()
        End Sub

        Private Sub EnsureContent()
            _panel.Title = _title
            _panel.WithSize(MASSize.FillWidth)

            _content = New MASFloatControlsContent(
                name:=DrawerTokens.FloatContentName,
                themeHost:=_rootHost.ThemeHostCore,
                invalidate:=AddressOf RequestInvalidate)

            _content.AcceptsPointerInput = True
            _content.AcceptsKeyboardInput = True
            _content.AcceptsTextInput = False
            _content.CloseOnOutsidePointer = _closeOnOutsidePointer
            _content.SwallowOutsidePointer = _swallowOutsidePointer
            _content.CloseOnEscape = _closeOnEscape
            _content.Add(_panel)
        End Sub

#End Region

#Region "Helpers / events / dispose"


        Private Function ResolveFloatPlacement() As MASFloatPlacement
            Return MASDrawerFloatPolicy.ResolveFloatPlacement(_side)
        End Function

        Private Function ResolveAvailableBoundsPx() As SKRect
            Try
                Dim viewport As SKRect = _rootHost.GetSurfaceViewportPx()
                If viewport.Width > 1.0F AndAlso viewport.Height > 1.0F Then Return viewport
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(masCaughtException4, "MASDrawer.AvailableBounds")
            End Try

            Return SKRect.Empty
        End Function

        Private Function ResolveDpi() As Single
            Try
                Dim ctx As MASThemeContext = _rootHost.ThemeHostCore.TryGetContext()
                If ctx IsNot Nothing Then Return PixelSnap.SafeDpi(ctx.Dpi)
            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDiagnosticOnly(masCaughtException5, "MASDrawer.Dpi")
            End Try

            Return 1.0F
        End Function

        Private Sub RequestInvalidate()
            If _rootHost Is Nothing Then Return
            Try
                _rootHost.RequestInvalidate()
            Catch masCaughtException6 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRecoverable(masCaughtException6, "MASDrawer.RequestInvalidate")
            End Try
        End Sub

        Private Sub OnPanelCloseRequested(sender As Object,
                                          e As EventArgs)
            CloseCore(MASFloatCloseReason.Programmatic)
        End Sub

        Private Shared Function NormalizeText(value As String) As String
            Return MASDrawerFloatPolicy.NormalizeTitle(value)
        End Function

        Private Shared Function NormalizeSide(value As MASDrawerSide) As MASDrawerSide
            Return MASDrawerFloatPolicy.NormalizeSide(value)
        End Function

        Private Shared Function NormalizeWidth(value As Single) As Single
            Return MASDrawerFloatPolicy.NormalizeWidth(value)
        End Function

        Private Sub RaiseOpenedSafe()
            Try
                RaiseEvent Opened(Me, EventArgs.Empty)
            Catch masCaughtException7 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException7, "MASDrawer.Opened")
            End Try
        End Sub

        Private Sub RaiseClosedSafe()
            Try
                RaiseEvent Closed(Me, EventArgs.Empty)
            Catch masCaughtException8 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException8, "MASDrawer.Closed")
            End Try
        End Sub

        Friend Function CreateDrawerReadinessManifest() As MASDrawerReadinessManifest
            Return MASDrawerReadinessManifest.CreateCurrent()
        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True

            RemoveHandler _panel.CloseRequested, AddressOf OnPanelCloseRequested
            CloseCore(MASFloatCloseReason.Programmatic)
            _content = Nothing
            _handle = Nothing
        End Sub

#End Region

    End Class

End Namespace
