Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports Nexamas.UI.Application
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Scroll
Imports Nexamas.UI.SkiaScroll
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.Rendering

    ''' <summary>
    ''' Reusable FloatRuntime root that hosts arbitrary feature controls inside
    ''' MASPanelShell. It mirrors the FilePicker/FileExplorer pattern: the shell is
    ''' rendered by MASPanelShellDialogHost, while the FloatRuntime owns lifecycle and input.
    ''' Fixed controls remain anchored in the content area; scrollable controls are clipped
    ''' to a dedicated body viewport driven by the official ScrollFactory/SkiaScrollAdapter.
    ''' </summary>
    Friend NotInheritable Class MASPanelShellFloatHost
        Inherits MASControlBase
        Implements IMASPanelShellHostedContent
        Implements IMASPanelShellScrollableHostedContent
        Implements IMASIntrinsicSizeContract
        Implements IMASSurfaceLayoutContract
        Implements IMASLayoutParticipant
        Implements IDisposable

        Private Enum ContentZone
            Fixed
            Scrollable
        End Enum

        Private ReadOnly _contentBounds As New Dictionary(Of MASControlBase, SKRect)()
        Private ReadOnly _contentZones As New Dictionary(Of MASControlBase, ContentZone)()
        Private ReadOnly _scrollOverlay As MASPanelShellScrollOverlay

        Private _shellOptions As MASPanelShellDialogOptions
        Private _lastPanelShellResult As MASPanelShellResult = MASPanelShellResult.Empty
        Private _closeHover As Boolean
        Private _closePressed As Boolean
        Private _scroll As SkiaScrollAdapter
        Private _scrollableAreaRelative As Nullable(Of SKRect)
        Private _scrollableAreaGroupBoxOptions As MASApplicationPanelShellContentGroupBox
        Private _scrollableAreaGroupBox As MASGroupBox
        Private _scrollableAreaGroupBoxDpi As Single = 1.0F
        Private _lastContentLogical As SKRect = SKRect.Empty
        Private _lastScrollViewportLogical As SKRect = SKRect.Empty
        Private _lastScrollViewportPx As SKRect = SKRect.Empty
        Private _scrollContentHeightLogical As Single
        Private _isScrollbarHover As Boolean
        Private _disposed As Boolean

        Friend Sub New()
            MyBase.New()

            _shellOptions = MASPanelShellDialogOptions.SelectionDialog("MAS")

            _scrollOverlay = New MASPanelShellScrollOverlay(Me) With {
                .Name = "MASPanelShellFloatScrollOverlay"
            }

            ChildLayerInternal.Add(_scrollOverlay)

        End Sub

        Friend Property ShellOptions As MASPanelShellDialogOptions
            Get
                Return MASPanelShellDialogOptions.SafeClone(_shellOptions)
            End Get
            Set(value As MASPanelShellDialogOptions)
                _shellOptions = MASPanelShellDialogOptions.SafeClone(value)
                InvalidateVisual()
            End Set
        End Property

        Public Property PanelShellDragEnabled As Boolean = True _
            Implements IMASPanelShellHostedContent.PanelShellDragEnabled

        Public Property PanelShellCloseEnabled As Boolean = True _
            Implements IMASPanelShellHostedContent.PanelShellCloseEnabled

        Friend Property VisualPadPx As Single = 0.0F

        Friend Property TopVisualPadPx As Single = 0.0F

        Friend Property ScrollbarEdgeInsetPx As Single = 6.0F

        Friend ReadOnly Property LastPanelShellResult As MASPanelShellResult _
            Implements IMASPanelShellHostedContent.LastPanelShellResult
            Get
                Return _lastPanelShellResult
            End Get
        End Property

        Friend Sub AddFixedContent(child As MASControlBase,
                                   relativeContentBoundsLogical As SKRect)

            AddContentCore(child, relativeContentBoundsLogical, ContentZone.Fixed)

        End Sub

        Friend Sub AddScrollableContent(child As MASControlBase,
                                        relativeScrollableBoundsLogical As SKRect)

            AddContentCore(child, relativeScrollableBoundsLogical, ContentZone.Scrollable)

        End Sub

        Friend Sub ConfigureScrollableAreaGroupBox(options As MASApplicationPanelShellContentGroupBox,
                                                    dpi As Single)

            _scrollableAreaGroupBoxOptions = options
            _scrollableAreaGroupBoxDpi = SafeDpiValue(dpi)

        End Sub

        Friend Function SetScrollableArea(relativeContentBoundsLogical As SKRect) As SKRect

            Dim effectiveBoundsLogical As SKRect =
                ResolveEffectiveScrollableArea(relativeContentBoundsLogical)

            _scrollableAreaRelative = effectiveBoundsLogical
            InvalidateVisual()

            Return effectiveBoundsLogical

        End Function

        Friend Sub RemoveContent(child As MASControlBase)

            If child Is Nothing Then Return
            If Object.ReferenceEquals(child, _scrollOverlay) Then Return

            If HasChildLayerInternal() Then
                ChildLayerInternal.Remove(child)
            End If

            _contentBounds.Remove(child)
            _contentZones.Remove(child)

            InvalidateVisual()

        End Sub

        Friend Sub ClearContent()

            If HasChildLayerInternal() Then
                For Each child As MASControlBase In New List(Of MASControlBase)(_contentBounds.Keys)
                    If child IsNot Nothing Then
                        ChildLayerInternal.Remove(child)
                    End If
                Next
            End If

            _contentBounds.Clear()
            _contentZones.Clear()
            _scrollableAreaRelative = Nothing
            _scrollableAreaGroupBox = Nothing

            ResetScrollRuntime()
            EnsureScrollOverlayLast()

            InvalidateVisual()

        End Sub

        Friend Sub SetPanelShellCloseButtonInteraction(isHover As Boolean,
                                                       isPressed As Boolean) _
            Implements IMASPanelShellHostedContent.SetPanelShellCloseButtonInteraction

            If _closeHover = isHover AndAlso _closePressed = isPressed Then Return

            _closeHover = isHover
            _closePressed = isPressed

            InvalidateVisual()

        End Sub

        Public Function RequestPanelShellClose() As Boolean _
            Implements IMASPanelShellHostedContent.RequestPanelShellClose

            Return False

        End Function


#Region "MASSize / MASLayout Contract"

        Friend Function GetPreferredLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetPreferredLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).DesiredSize
        End Function

        Friend Function GetMinLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetMinLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).MinSize
        End Function

        Friend Function MeasureIntrinsicSize(context As MASSizeContext, intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Return MeasureSurfaceLayout(context, ResolveAvailableSize(context), intent)
        End Function

        Friend Function MeasureSurfaceLayout(context As MASSizeContext,
                                             availableSize As SKSize,
                                             intent As MASSize) As MASSizeResult Implements IMASSurfaceLayoutContract.MeasureSurfaceLayout
            Dim safeContext As MASSizeContext = EnsureSizeContext(context, availableSize)
            Dim safeAvailable As SKSize = ResolveAvailableSize(safeContext, availableSize)
            Dim characteristics As New MASSurfaceLayoutCharacteristics(hasHeader:=True)

            Return MASSurfaceLayoutSizeResolver.Resolve(
                MASSurfaceLayoutKind.PanelShell,
                safeContext,
                safeAvailable,
                intent,
                characteristics)
        End Function

        Private Shared Function EnsureSizeContext(context As MASSizeContext,
                                                  availableSize As SKSize) As MASSizeContext
            If context IsNot Nothing Then Return context
            Return New MASSizeContext(ResolveAvailableSize(Nothing, availableSize))
        End Function

        Private Shared Function ResolveAvailableSize(context As MASSizeContext) As SKSize
            Return ResolveAvailableSize(context, SKSize.Empty)
        End Function

        Private Shared Function ResolveAvailableSize(context As MASSizeContext,
                                                     availableSize As SKSize) As SKSize
            Dim width As Single = availableSize.Width
            Dim height As Single = availableSize.Height

            If (Single.IsNaN(width) OrElse width <= 0.0F OrElse Single.IsNegativeInfinity(width)) AndAlso context IsNot Nothing Then width = context.AvailableSize.Width
            If (Single.IsNaN(height) OrElse height <= 0.0F OrElse Single.IsNegativeInfinity(height)) AndAlso context IsNot Nothing Then height = context.AvailableSize.Height

            If Single.IsNaN(width) OrElse width <= 0.0F OrElse Single.IsNegativeInfinity(width) Then width = Single.PositiveInfinity
            If Single.IsNaN(height) OrElse height <= 0.0F OrElse Single.IsNegativeInfinity(height) Then height = Single.PositiveInfinity

            Return New SKSize(width, height)
        End Function

        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return New MASSizeContext(New SKSize(Single.PositiveInfinity, Single.PositiveInfinity))
            Return context.ToSizeContext()
        End Function

#End Region

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 2.0F OrElse pixelBounds.Height <= 2.0F Then Return

            EnsureScroll(ctx)

            Dim shellBoundsPx As SKRect =
                ResolveShellBoundsPx(pixelBounds)

            _lastPanelShellResult =
                MASPanelShellDialogHost.Draw(
                    canvas:=canvas,
                    ctx:=ctx,
                    dpi:=ctx.Dpi,
                    boundsPx:=shellBoundsPx,
                    options:=MASPanelShellDialogOptions.SafeClone(_shellOptions),
                    closeHover:=_closeHover,
                    closePressed:=_closePressed)

            LayoutContentChildren(ctx)

        End Sub

        Protected Overrides Function HitTest(ctx As MASThemeContext,
                                             ptPx As SKPoint) As Boolean

            Dim result As MASPanelShellResult = _lastPanelShellResult

            If result IsNot Nothing AndAlso Not result.BaseRectPx.IsEmpty Then
                Return result.BaseRectPx.Contains(ptPx.X, ptPx.Y)
            End If

            Return MyBase.HitTest(ctx, ptPx)

        End Function

        Friend Function TryHandlePanelShellScrollPointerMove(ctx As MASThemeContext,
                                                             ptPx As SKPoint) As Boolean _
            Implements IMASPanelShellScrollableHostedContent.TryHandlePanelShellScrollPointerMove

            If _scroll Is Nothing Then Return False
            If Not IsScrollRequired() Then Return False

            Dim handled As Boolean = _scroll.MouseMove(ptPx)

            Dim hover As Boolean =
                _lastScrollViewportPx.Contains(ptPx.X, ptPx.Y)

            If hover <> _isScrollbarHover Then
                _isScrollbarHover = hover
                InvalidateVisual()
            End If

            Return handled

        End Function

        Friend Function TryHandlePanelShellScrollPointerDown(ctx As MASThemeContext,
                                                             ptPx As SKPoint,
                                                             button As MouseButtons) As Boolean _
            Implements IMASPanelShellScrollableHostedContent.TryHandlePanelShellScrollPointerDown

            If button <> MouseButtons.Left Then Return False
            If _scroll Is Nothing Then Return False
            If Not IsScrollRequired() Then Return False
            If Not _lastScrollViewportPx.Contains(ptPx.X, ptPx.Y) Then Return False

            Return _scroll.MouseDown(
                pt:=ptPx,
                rowStepPx:=ScrollStepPx(ctx),
                pageFactor:=0.92F)

        End Function

        Friend Function TryHandlePanelShellScrollPointerUp(ctx As MASThemeContext,
                                                           ptPx As SKPoint,
                                                           button As MouseButtons) As Boolean _
            Implements IMASPanelShellScrollableHostedContent.TryHandlePanelShellScrollPointerUp

            If _scroll Is Nothing Then Return False

            Return _scroll.MouseUp()

        End Function

        Friend Function TryHandlePanelShellScrollWheel(ctx As MASThemeContext,
                                                       ptPx As SKPoint,
                                                       delta As Integer) As Boolean _
            Implements IMASPanelShellScrollableHostedContent.TryHandlePanelShellScrollWheel

            If _scroll Is Nothing Then Return False
            If Not IsScrollRequired() Then Return False
            If Not _lastScrollViewportPx.Contains(ptPx.X, ptPx.Y) Then Return False

            Return _scroll.MouseWheel(
                delta:=delta,
                stepPx:=ScrollStepPx(ctx))

        End Function

        Public Sub CancelPanelShellScrollInteraction() _
            Implements IMASPanelShellScrollableHostedContent.CancelPanelShellScrollInteraction

            If _scroll IsNot Nothing Then
                _scroll.CancelInteraction()
            End If

        End Sub

        Friend Sub DrawScrollOverlay(canvas As SKCanvas,
                                     ctx As MASThemeContext)

            If _scroll Is Nothing Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If Not IsScrollRequired() Then Return

            _scroll.Draw(canvas, _isScrollbarHover)

        End Sub

        Protected Overrides Sub OnDetached()

            MyBase.OnDetached()
            CancelPanelShellScrollInteraction()

        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose

            If _disposed Then Return

            _disposed = True
            ResetScrollRuntime()

        End Sub

        Private Function ResolveEffectiveScrollableArea(relativeContentBoundsLogical As SKRect) As SKRect

            If Not ShouldFrameScrollableArea() Then Return relativeContentBoundsLogical
            If Not IsUsableBounds(relativeContentBoundsLogical) Then Return relativeContentBoundsLogical

            EnsureScrollableAreaGroupBox(relativeContentBoundsLogical)

            Dim innerBoundsLogical As SKRect =
                _scrollableAreaGroupBoxOptions.ResolveInnerContentBoundsLogical(
                    groupBoxBoundsLogical:=relativeContentBoundsLogical,
                    dpi:=_scrollableAreaGroupBoxDpi)

            If Not IsUsableBounds(innerBoundsLogical) Then Return relativeContentBoundsLogical

            Return innerBoundsLogical

        End Function

        Private Function ShouldFrameScrollableArea() As Boolean

            If _scrollableAreaGroupBoxOptions Is Nothing Then Return False
            If Not _scrollableAreaGroupBoxOptions.Enabled Then Return False

            Return _scrollableAreaGroupBoxOptions.Placement =
                   MASApplicationPanelShellContentGroupBox.PlacementKind.ScrollableArea

        End Function

        Private Sub EnsureScrollableAreaGroupBox(relativeBoundsLogical As SKRect)

            If _scrollableAreaGroupBoxOptions Is Nothing Then Return

            If _scrollableAreaGroupBox Is Nothing Then
                _scrollableAreaGroupBox = _scrollableAreaGroupBoxOptions.CreateControl(_scrollableAreaGroupBoxDpi)
                _scrollableAreaGroupBox.Name = If(Name, "MASPanelShell") & "_ScrollableAreaGroupBox"
            End If

            AddContentCore(
                child:=_scrollableAreaGroupBox,
                relativeBoundsLogical:=relativeBoundsLogical,
                zone:=ContentZone.Fixed)

        End Sub

        Private Sub AddContentCore(child As MASControlBase,
                                   relativeBoundsLogical As SKRect,
                                   zone As ContentZone)

            If child Is Nothing Then Throw New ArgumentNullException(NameOf(child))
            If Object.ReferenceEquals(child, _scrollOverlay) Then Return

            _contentBounds(child) = relativeBoundsLogical
            _contentZones(child) = zone

            If HasChildLayerInternal() Then
                ChildLayerInternal.Remove(child)
            End If

            ChildLayerInternal.Add(child)
            EnsureScrollOverlayLast()

            InvalidateVisual()

        End Sub

        Private Sub EnsureScrollOverlayLast()

            If Not HasChildLayerInternal() Then Return
            If _scrollOverlay Is Nothing Then Return

            ChildLayerInternal.Remove(_scrollOverlay)
            ChildLayerInternal.Add(_scrollOverlay)

        End Sub

        Private Function ResolveShellBoundsPx(pixelBounds As SKRect) As SKRect

            Dim pad As Single = Math.Max(0.0F, VisualPadPx)
            Dim topPad As Single = Math.Max(0.0F, TopVisualPadPx)

            Dim result As New SKRect(
                pixelBounds.Left + pad,
                pixelBounds.Top + topPad,
                pixelBounds.Right - pad,
                pixelBounds.Bottom - pad)

            If result.Width <= 1.0F OrElse result.Height <= 1.0F Then
                Return pixelBounds
            End If

            Return result

        End Function

        Private Sub LayoutContentChildren(ctx As MASThemeContext)

            If ctx Is Nothing Then Return

            Dim plan As New PanelShellFloatLayoutPlan()

            If Not TryBuildPanelShellFloatLayoutPlan(ctx, plan) Then
                ClearLastPanelShellFloatLayoutPlan()
                Return
            End If

            _lastContentLogical = plan.ContentBoundsLogical
            _lastScrollViewportLogical = plan.ScrollViewportLogical
            _lastScrollViewportPx = plan.ScrollHostPx
            _scrollContentHeightLogical = plan.ScrollContentHeightLogical

            If _scroll IsNot Nothing Then
                _scroll.Update(
                    hostRect:=plan.ScrollHostPx,
                    dpi:=plan.Dpi,
                    contentHeight:=plan.ScrollContentHeightLogical * plan.Dpi)
            End If

            For Each pair As KeyValuePair(Of MASControlBase, SKRect) In _contentBounds

                Dim child As MASControlBase = pair.Key
                If child Is Nothing Then Continue For

                Dim zone As ContentZone = ResolveContentZone(child)

                If zone = ContentZone.Fixed Then
                    SetChildBoundsFromPlan(
                        child:=child,
                        originLogical:=plan.ContentBoundsLogical,
                        relativeBoundsLogical:=pair.Value,
                        offsetLogical:=0.0F,
                        clipLogical:=Nothing)
                Else
                    SetChildBoundsFromPlan(
                        child:=child,
                        originLogical:=plan.ScrollViewportLogical,
                        relativeBoundsLogical:=pair.Value,
                        offsetLogical:=plan.ScrollOffsetLogical,
                        clipLogical:=New System.Nullable(Of SKRect)(plan.ScrollViewportLogical))
                End If

            Next

            _scrollOverlay.SetLayoutPlacementInternal(plan.ScrollViewportLogical, New System.Nullable(Of SKRect)(plan.ScrollViewportLogical))

        End Sub

        Private Function TryBuildPanelShellFloatLayoutPlan(ctx As MASThemeContext,
                                                           ByRef plan As PanelShellFloatLayoutPlan) As Boolean

            plan = New PanelShellFloatLayoutPlan()

            If ctx Is Nothing Then Return False

            Dim contentLogical As SKRect = ResolveCurrentContentBoundsLogical(ctx)
            If Not IsUsableBounds(contentLogical) Then Return False

            Dim scrollViewportLogical As SKRect = ResolveScrollViewportLogical(contentLogical)
            If Not IsUsableBounds(scrollViewportLogical) Then Return False

            Dim dpi As Single = SafeDpi(ctx)
            Dim scrollContentHeightLogical As Single = CalculateScrollableExtentLogical()
            Dim requiresScroll As Boolean = ShouldRequireScroll(scrollContentHeightLogical, scrollViewportLogical)
            Dim scrollHostLogical As SKRect = ResolveScrollHostLogical(scrollViewportLogical, requiresScroll, dpi)

            If Not IsUsableBounds(scrollHostLogical) Then Return False

            Dim offsetLogical As Single = 0.0F

            If _scroll IsNot Nothing Then
                offsetLogical = _scroll.Offset / dpi
            End If

            plan = New PanelShellFloatLayoutPlan(
                contentBoundsLogical:=contentLogical,
                scrollViewportLogical:=scrollViewportLogical,
                scrollHostLogical:=scrollHostLogical,
                scrollHostPx:=ToPx(scrollHostLogical, dpi),
                scrollContentHeightLogical:=scrollContentHeightLogical,
                scrollOffsetLogical:=offsetLogical,
                dpi:=dpi)

            Return plan.IsValid

        End Function

        Private Sub ClearLastPanelShellFloatLayoutPlan()

            _lastContentLogical = SKRect.Empty
            _lastScrollViewportLogical = SKRect.Empty
            _lastScrollViewportPx = SKRect.Empty
            _scrollContentHeightLogical = 0.0F

        End Sub

        Private Function ResolveContentZone(child As MASControlBase) As ContentZone

            If child Is Nothing Then Return ContentZone.Scrollable

            If _contentZones.ContainsKey(child) Then
                Return _contentZones(child)
            End If

            Return ContentZone.Scrollable

        End Function

        Private Function ResolveScrollHostLogical(scrollViewportLogical As SKRect,
                                                  requiresScroll As Boolean,
                                                  dpi As Single) As SKRect

            Dim scrollHostLogical As SKRect = scrollViewportLogical

            If requiresScroll Then
                Dim edgeInsetLogical As Single = Math.Max(0.0F, ScrollbarEdgeInsetPx) / dpi

                scrollHostLogical.Right =
                    Math.Max(
                        scrollHostLogical.Left,
                        scrollHostLogical.Right - edgeInsetLogical)
            End If

            Return scrollHostLogical

        End Function

        Private Shared Function ShouldRequireScroll(contentHeightLogical As Single,
                                               viewportLogical As SKRect) As Boolean

            If viewportLogical.IsEmpty Then Return False

            Return contentHeightLogical > viewportLogical.Height + 0.5F

        End Function

        Private Sub SetChildBoundsFromPlan(child As MASControlBase,
                                               originLogical As SKRect,
                                               relativeBoundsLogical As SKRect,
                                               offsetLogical As Single,
                                               clipLogical As Nullable(Of SKRect))

            If child Is Nothing Then Return

            Dim childBoundsLogical As New SKRect(
                originLogical.Left + relativeBoundsLogical.Left,
                originLogical.Top + relativeBoundsLogical.Top - offsetLogical,
                originLogical.Left + relativeBoundsLogical.Right,
                originLogical.Top + relativeBoundsLogical.Bottom - offsetLogical)

            child.SetLayoutPlacementInternal(childBoundsLogical, clipLogical)

        End Sub

        Private Shared Function IsUsableBounds(bounds As SKRect) As Boolean

            Return Not bounds.IsEmpty AndAlso
                   bounds.Width > 0.5F AndAlso
                   bounds.Height > 0.5F

        End Function

        Private Function ResolveScrollViewportLogical(contentLogical As SKRect) As SKRect

            If contentLogical.IsEmpty Then Return contentLogical

            If Not _scrollableAreaRelative.HasValue Then
                Return contentLogical
            End If

            Dim relative As SKRect = _scrollableAreaRelative.Value

            Dim left As Single =
                contentLogical.Left + Clamp(relative.Left, 0.0F, contentLogical.Width)

            Dim top As Single =
                contentLogical.Top + Clamp(relative.Top, 0.0F, contentLogical.Height)

            Dim right As Single =
                contentLogical.Left + Clamp(relative.Right, 0.0F, contentLogical.Width)

            Dim bottom As Single =
                contentLogical.Top + Clamp(relative.Bottom, 0.0F, contentLogical.Height)

            If right <= left OrElse bottom <= top Then
                Return contentLogical
            End If

            Return New SKRect(left, top, right, bottom)

        End Function

        Private Function CalculateScrollableExtentLogical() As Single

            Dim bottom As Single = 0.0F

            For Each pair As KeyValuePair(Of MASControlBase, SKRect) In _contentBounds

                Dim zone As ContentZone = ContentZone.Scrollable

                If _contentZones.ContainsKey(pair.Key) Then
                    zone = _contentZones(pair.Key)
                End If

                If zone <> ContentZone.Scrollable Then Continue For

                If pair.Value.Bottom > bottom Then
                    bottom = pair.Value.Bottom
                End If

            Next

            Return Math.Max(0.0F, bottom)

        End Function

        Private Function ResolveCurrentContentBoundsLogical(ctx As MASThemeContext) As SKRect

            Dim result As MASPanelShellResult = _lastPanelShellResult

            If result Is Nothing OrElse result.ContentRectPx.IsEmpty Then
                Return BoundsLogical
            End If

            Dim dpi As Single = SafeDpi(ctx)

            Return New SKRect(
                result.ContentRectPx.Left / dpi,
                result.ContentRectPx.Top / dpi,
                result.ContentRectPx.Right / dpi,
                result.ContentRectPx.Bottom / dpi)

        End Function

        Private Sub EnsureScroll(ctx As MASThemeContext)

            If ctx Is Nothing Then Return

            If _scroll Is Nothing Then
                _scroll = ScrollFactory.Create(ctx, AddressOf InvalidateVisual)
            Else
                _scroll.UpdateThemeContext(ctx)
            End If

        End Sub

        Private Sub ResetScrollRuntime()

            If _scroll Is Nothing Then Return

            Try
                _scroll.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(
                    masCaughtException1,
                    "MASPanelShellFloatHost.ResetScrollRuntime")
            End Try

            _scroll = Nothing
            _isScrollbarHover = False

        End Sub

        Private Function IsScrollRequired() As Boolean

            Return ShouldRequireScroll(_scrollContentHeightLogical, _lastScrollViewportLogical)

        End Function

        Private Function ScrollStepPx(ctx As MASThemeContext) As Single

            Return 42.0F * SafeDpi(ctx)

        End Function

        Private Shared Function SafeDpi(ctx As MASThemeContext) As Single

            Dim dpi As Single = If(ctx Is Nothing, 1.0F, ctx.Dpi)
            Return SafeDpiValue(dpi)

        End Function

        Private Shared Function SafeDpiValue(value As Single) As Single

            If value <= 0.0F OrElse
               Single.IsNaN(value) OrElse
               Single.IsInfinity(value) Then

                Return 1.0F
            End If

            Return value

        End Function

        Private Shared Function ToPx(logical As SKRect,
                                     dpi As Single) As SKRect

            Return New SKRect(
                logical.Left * dpi,
                logical.Top * dpi,
                logical.Right * dpi,
                logical.Bottom * dpi)

        End Function

        Private Shared Function Clamp(value As Single,
                                      minValue As Single,
                                      maxValue As Single) As Single

            Return Math.Max(minValue, Math.Min(maxValue, value))

        End Function

        Private Structure PanelShellFloatLayoutPlan

            Friend ReadOnly ContentBoundsLogical As SKRect
            Friend ReadOnly ScrollViewportLogical As SKRect
            Friend ReadOnly ScrollHostLogical As SKRect
            Friend ReadOnly ScrollHostPx As SKRect
            Friend ReadOnly ScrollContentHeightLogical As Single
            Friend ReadOnly ScrollOffsetLogical As Single
            Friend ReadOnly Dpi As Single

            Friend Sub New(contentBoundsLogical As SKRect,
                           scrollViewportLogical As SKRect,
                           scrollHostLogical As SKRect,
                           scrollHostPx As SKRect,
                           scrollContentHeightLogical As Single,
                           scrollOffsetLogical As Single,
                           dpi As Single)

                Me.ContentBoundsLogical = contentBoundsLogical
                Me.ScrollViewportLogical = scrollViewportLogical
                Me.ScrollHostLogical = scrollHostLogical
                Me.ScrollHostPx = scrollHostPx
                Me.ScrollContentHeightLogical = scrollContentHeightLogical
                Me.ScrollOffsetLogical = scrollOffsetLogical
                Me.Dpi = dpi

            End Sub

            Friend ReadOnly Property IsValid As Boolean
                Get
                    Return Not ContentBoundsLogical.IsEmpty AndAlso
                           ContentBoundsLogical.Width > 0.5F AndAlso
                           ContentBoundsLogical.Height > 0.5F AndAlso
                           Not ScrollViewportLogical.IsEmpty AndAlso
                           ScrollViewportLogical.Width > 0.5F AndAlso
                           ScrollViewportLogical.Height > 0.5F AndAlso
                           Not ScrollHostPx.IsEmpty AndAlso
                           ScrollHostPx.Width > 0.5F AndAlso
                           ScrollHostPx.Height > 0.5F AndAlso
                           Dpi > 0.0F AndAlso
                           Not Single.IsNaN(Dpi) AndAlso
                           Not Single.IsInfinity(Dpi)
                End Get
            End Property

        End Structure

        Friend NotInheritable Class MASPanelShellScrollOverlay
            Inherits MASControlBase

            Private ReadOnly _owner As MASPanelShellFloatHost

            Friend Sub New(owner As MASPanelShellFloatHost)
                _owner = owner
            End Sub

            Protected Overrides Sub Render(canvas As SKCanvas,
                                           ctx As MASThemeContext,
                                           pixelBounds As SKRect)

                If _owner Is Nothing Then Return

                _owner.DrawScrollOverlay(canvas, ctx)

            End Sub

            Protected Overrides Function HitTest(ctx As MASThemeContext,
                                                 ptPx As SKPoint) As Boolean

                Return False

            End Function

            Protected Overrides Function WantsKeyboardFocus() As Boolean

                Return False

            End Function

            Protected Overrides Function WantsPointerFocus() As Boolean

                Return False

            End Function


#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>
        Friend Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As MASPanelShellScrollOverlay
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

        Friend Shadows Function WithDefaultSize() As MASPanelShellScrollOverlay
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.[Default])
            Return Me
        End Function

        Friend Shadows Function WithCompactSize() As MASPanelShellScrollOverlay
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Compact)
            Return Me
        End Function

        Friend Shadows Function WithLargeSize() As MASPanelShellScrollOverlay
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Large)
            Return Me
        End Function

        Friend Shadows Function WithFillWidthSize() As MASPanelShellScrollOverlay
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.FillWidth)
            Return Me
        End Function

        Friend Shadows Function WithHugContentSize() As MASPanelShellScrollOverlay
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.HugContent)
            Return Me
        End Function

#End Region
        End Class


#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>
        Friend Shadows Function WithSize(sizeIntent As Nexamas.UI.Layout.MASSize) As MASPanelShellFloatHost
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

        Friend Shadows Function WithDefaultSize() As MASPanelShellFloatHost
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.[Default])
            Return Me
        End Function

        Friend Shadows Function WithCompactSize() As MASPanelShellFloatHost
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Compact)
            Return Me
        End Function

        Friend Shadows Function WithLargeSize() As MASPanelShellFloatHost
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.Large)
            Return Me
        End Function

        Friend Shadows Function WithFillWidthSize() As MASPanelShellFloatHost
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.FillWidth)
            Return Me
        End Function

        Friend Shadows Function WithHugContentSize() As MASPanelShellFloatHost
            MyBase.SetSize(Nexamas.UI.Layout.MASSize.HugContent)
            Return Me
        End Function

#End Region
    End Class

End Namespace
