Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Application
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Host
Imports Nexamas.UI.Rendering
Imports SkiaSharp

Namespace Nexamas.UI.FloatRuntime

    ''' <summary>
    ''' Builds FloatRuntime requests for the reusable MASPanelShell surface.
    ''' This builder is the official generic PanelShell-backed float gateway.
    ''' It owns section layout for Toolbar, Content, and Footer so caller code does not
    ''' manually position common shell actions or scrollbar gutters.
    ''' </summary>
    Friend NotInheritable Class MASPanelShellFloatRequestBuilder

        Private Sub New()
        End Sub

        Friend Shared Function CreateRequestCore(rootHost As MASSkiaRootHost,
                                                 ownerKey As String,
                                                 options As MASApplicationPanelShellOptions,
                                                 Optional floatKey As String = MASFloatKeys.Panel,
                                                 Optional showPolicy As MASFloatShowPolicy = MASFloatShowPolicy.CloseSameOwnerThenShow,
                                                 Optional shield As MASFloatShieldOptions = Nothing,
                                                 Optional onOpened As Action(Of MASFloatHandle) = Nothing,
                                                 Optional onClosed As Action(Of MASFloatClosedEventArgs) = Nothing) As MASFloatRequest

            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            If String.IsNullOrWhiteSpace(ownerKey) Then Throw New ArgumentException("PanelShell float requires a non-empty ownerKey.", NameOf(ownerKey))
            If options Is Nothing Then Throw New ArgumentNullException(NameOf(options))

            Dim availablePx As SKRect =
                If(IsUsableBounds(options.AvailableBoundsPx),
                   options.AvailableBoundsPx,
                   rootHost.GetSurfaceViewportPx())

            If Not IsUsableBounds(availablePx) Then
                Throw New InvalidOperationException("PanelShell float requires usable available bounds.")
            End If

            Dim layout As PanelShellLayout = ResolveLayout(options, availablePx)

            If Not layout.IsValid Then
                Throw New InvalidOperationException("PanelShell float layout could not be resolved.")
            End If

            Dim content As New MASFloatControlsContent(
                name:=If(options.Name, "MASPanelShellFloatContent"),
                themeHost:=rootHost.ThemeHostCore,
                invalidate:=AddressOf rootHost.RequestInvalidate)

            content.AcceptsPointerInput = True
            content.AcceptsKeyboardInput = True
            content.AcceptsTextInput = options.AcceptsTextInput
            content.CloseOnEscape = options.CloseOnEscape
            content.CloseOnOutsidePointer = options.CloseOnOutsidePointer
            content.SwallowOutsidePointer = options.SwallowOutsidePointer

            Dim host As MASPanelShellFloatHost =
                CreateHost(
                    rootHost:=rootHost,
                    options:=options,
                    boundsPx:=layout.FloatBoundsPx)

            Dim fullContentBoundsPx As SKRect =
                ResolveFullContentAreaPx(
                    rootHost:=rootHost,
                    host:=host,
                    options:=options)

            If Not IsUsableBounds(fullContentBoundsPx) Then
                Throw New InvalidOperationException("PanelShell content area could not be resolved.")
            End If

            Dim sectionLayout As PanelShellSectionsLayout =
                ResolveSectionsLayout(options, fullContentBoundsPx)

            If Not sectionLayout.IsValid Then
                Throw New InvalidOperationException("PanelShell section layout could not be resolved.")
            End If

            host.ScrollbarEdgeInsetPx = Math.Max(0.0F, options.Content.ScrollbarEdgeInsetPx)

            Dim callerContentAreaPx As SKRect =
                ResolveCallerContentAreaPx(
                    rootHost:=rootHost,
                    host:=host,
                    options:=options,
                    bodyAreaPx:=sectionLayout.BodyAreaPx,
                    fullContentAreaPx:=fullContentBoundsPx)

            If Not IsUsableBounds(callerContentAreaPx) Then
                Throw New InvalidOperationException("PanelShell content body could not be resolved.")
            End If

            Dim bodyRelativeLogical As SKRect =
                ToRelativeLogical(
                    rootHost:=rootHost,
                    originPx:=fullContentBoundsPx,
                    boundsPx:=callerContentAreaPx)

            host.SetScrollableArea(bodyRelativeLogical)

            If ShouldBuildScrollableAreaContentGroupBox(options) Then
                host.ConfigureScrollableAreaGroupBox(
                    options:=options.Content.GroupBox,
                    dpi:=ResolveDpi(rootHost))
            End If

            If options.ContentBuilder IsNot Nothing Then
                options.ContentBuilder.Invoke(
                    New MASPanelShellContentHost(host, bodyRelativeLogical),
                    ToRelativeSizeLogical(rootHost, callerContentAreaPx))
            End If

            If sectionLayout.HasToolbar Then
                AddCommandBarButtons(
                    rootHost:=rootHost,
                    host:=host,
                    options:=options,
                    content:=content,
                    commandBar:=options.Toolbar,
                    areaPx:=sectionLayout.ToolbarAreaPx,
                    fullContentAreaPx:=fullContentBoundsPx,
                    namePrefix:="MASPanelShellToolbarButton_",
                    notifyCompletedWhenNonClosing:=False)
            End If

            If sectionLayout.HasFooter Then
                AddCommandBarButtons(
                    rootHost:=rootHost,
                    host:=host,
                    options:=options,
                    content:=content,
                    commandBar:=options.Footer,
                    areaPx:=sectionLayout.FooterAreaPx,
                    fullContentAreaPx:=fullContentBoundsPx,
                    namePrefix:="MASPanelShellFooterButton_",
                    notifyCompletedWhenNonClosing:=True)
            End If

            content.Add(host)

            Return New MASFloatRequest() With {
                .FloatKey = If(floatKey, MASFloatKeys.Panel),
                .OwnerKey = ownerKey.Trim(),
                .Content = content,
                .ShowPolicy = showPolicy,
                .Shield = If(shield, MASFloatShieldOptions.None),
                .AvailableBoundsPx = availablePx,
                .InitialBoundsPx = layout.FloatBoundsPx,
                .OnOpened = onOpened,
                .OnClosed = Sub(args As MASFloatClosedEventArgs)
                                InvokeCompleted(options, args)

                                If onClosed IsNot Nothing Then
                                    onClosed.Invoke(args)
                                End If
                            End Sub
            }

        End Function

        Private Shared Function CreateHost(rootHost As MASSkiaRootHost,
                                           options As MASApplicationPanelShellOptions,
                                           boundsPx As SKRect) As MASPanelShellFloatHost

            Dim host As New MASPanelShellFloatHost()

            host.Name = If(options.Name, "MASPanelShellFloatHost")
            host.SetLocalLayoutBoundsInternal(rootHost.PxToLogical(boundsPx))
            host.ShellOptions = MASPanelShellDialogOptions.SafeClone(options.ShellOptions)
            host.VisualPadPx = Math.Max(0.0F, options.VisualPadPx)
            host.TopVisualPadPx = Math.Max(0.0F, options.TopVisualPadPx)
            host.ScrollbarEdgeInsetPx = Math.Max(0.0F, options.Content.ScrollbarEdgeInsetPx)
            host.PanelShellDragEnabled = host.ShellOptions.AllowHeaderDrag
            host.PanelShellCloseEnabled = host.ShellOptions.ShowCloseButton

            Return host

        End Function

        Private Shared Function ResolveFullContentAreaPx(rootHost As MASSkiaRootHost,
                                                         host As MASPanelShellFloatHost,
                                                         options As MASApplicationPanelShellOptions) As SKRect

            If rootHost Is Nothing OrElse host Is Nothing OrElse options Is Nothing Then Return SKRect.Empty

            Dim shellOptions As MASPanelShellDialogOptions =
                MASPanelShellDialogOptions.SafeClone(options.ShellOptions)

            Dim hostBoundsPx As SKRect = rootHost.LogicalToPx(host.BoundsLogical)

            Dim shellBoundsPx As New SKRect(
                hostBoundsPx.Left + Math.Max(0.0F, options.VisualPadPx),
                hostBoundsPx.Top + Math.Max(0.0F, options.TopVisualPadPx),
                hostBoundsPx.Right - Math.Max(0.0F, options.VisualPadPx),
                hostBoundsPx.Bottom - Math.Max(0.0F, options.VisualPadPx))

            If shellBoundsPx.Width <= 1.0F OrElse shellBoundsPx.Height <= 1.0F Then
                shellBoundsPx = hostBoundsPx
            End If

            Dim ctx As Nexamas.UI.Theming.MASThemeContext =
                rootHost.ThemeHostCore.TryGetContext()

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

            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then
                dpi = 1.0F
            End If

            Dim measured As MASPanelShellResult =
                MASPanelShellDialogHost.Measure(
                    dpi:=dpi,
                    boundsPx:=shellBoundsPx,
                    options:=shellOptions)

            If measured Is Nothing OrElse measured.ContentRectPx.IsEmpty Then
                Return SKRect.Empty
            End If

            Return measured.ContentRectPx

        End Function

        Private Shared Function ResolveSectionsLayout(options As MASApplicationPanelShellOptions,
                                                      fullContentAreaPx As SKRect) As PanelShellSectionsLayout

            Dim result As New PanelShellSectionsLayout()

            If options Is Nothing OrElse Not IsUsableBounds(fullContentAreaPx) Then Return result

            result.FullContentAreaPx = fullContentAreaPx

            Dim left As Single = fullContentAreaPx.Left
            Dim right As Single = fullContentAreaPx.Right
            Dim top As Single = fullContentAreaPx.Top
            Dim bottom As Single = fullContentAreaPx.Bottom

            If ShouldBuildToolbar(options) Then
                Dim toolbarHeight As Single = SafePositive(options.Toolbar.HeightPx, 0.0F)

                If toolbarHeight > 0.0F AndAlso top + toolbarHeight <= bottom Then
                    result.ToolbarAreaPx = New SKRect(left, top, right, top + toolbarHeight)
                    result.HasToolbar = True
                    top = result.ToolbarAreaPx.Bottom + Math.Max(0.0F, options.Toolbar.ContentGapPx)
                End If
            End If

            If ShouldBuildFooter(options) Then
                Dim footerHeight As Single = SafePositive(options.Footer.HeightPx, 0.0F)

                If footerHeight > 0.0F AndAlso bottom - footerHeight >= top Then
                    result.FooterAreaPx = New SKRect(left, bottom - footerHeight, right, bottom)
                    result.HasFooter = True
                    bottom = result.FooterAreaPx.Top - Math.Max(0.0F, options.Footer.ContentGapPx)
                End If
            End If

            If bottom <= top Then Return result

            Dim bodyWithoutPadding As New SKRect(left, top, right, bottom)
            result.BodyAreaPx = options.Content.ApplyPadding(bodyWithoutPadding)

            Return result

        End Function

        Private Shared Function ResolveCallerContentAreaPx(rootHost As MASSkiaRootHost,
                                                           host As MASPanelShellFloatHost,
                                                           options As MASApplicationPanelShellOptions,
                                                           bodyAreaPx As SKRect,
                                                           fullContentAreaPx As SKRect) As SKRect

            If rootHost Is Nothing OrElse host Is Nothing OrElse options Is Nothing Then Return SKRect.Empty
            If Not IsUsableBounds(bodyAreaPx) OrElse Not IsUsableBounds(fullContentAreaPx) Then Return SKRect.Empty

            If Not ShouldBuildContentBodyGroupBox(options) Then Return bodyAreaPx

            Dim groupBoxOptions As MASApplicationPanelShellContentGroupBox = options.Content.GroupBox
            Dim dpi As Single = ResolveDpi(rootHost)

            Dim groupBoxAreaPx As SKRect = groupBoxOptions.ApplyOuterInset(bodyAreaPx)
            If Not IsUsableBounds(groupBoxAreaPx) Then Return bodyAreaPx

            Dim innerContentAreaPx As SKRect =
                groupBoxOptions.ResolveInnerContentBoundsPx(
                    groupBoxBoundsPx:=groupBoxAreaPx,
                    dpi:=dpi)

            If Not IsUsableBounds(innerContentAreaPx) Then Return bodyAreaPx

            Dim groupBox As MASGroupBox = groupBoxOptions.CreateControl(dpi)
            groupBox.Name = If(options.Name, "MASPanelShell") & "_ContentGroupBox"

            host.AddFixedContent(
                child:=groupBox,
                relativeContentBoundsLogical:=ToRelativeLogical(
                    rootHost:=rootHost,
                    originPx:=fullContentAreaPx,
                    boundsPx:=groupBoxAreaPx))

            Return innerContentAreaPx

        End Function

        Private Shared Function ShouldBuildContentBodyGroupBox(options As MASApplicationPanelShellOptions) As Boolean

            If Not ShouldBuildContentGroupBox(options) Then Return False

            Return options.Content.GroupBox.Placement =
                   MASApplicationPanelShellContentGroupBox.PlacementKind.ContentBody

        End Function

        Private Shared Function ShouldBuildScrollableAreaContentGroupBox(options As MASApplicationPanelShellOptions) As Boolean

            If Not ShouldBuildContentGroupBox(options) Then Return False

            Return options.Content.GroupBox.Placement =
                   MASApplicationPanelShellContentGroupBox.PlacementKind.ScrollableArea

        End Function

        Private Shared Function ShouldBuildContentGroupBox(options As MASApplicationPanelShellOptions) As Boolean

            If options Is Nothing OrElse options.Content Is Nothing Then Return False
            If options.Content.GroupBox Is Nothing Then Return False
            Return options.Content.GroupBox.Enabled

        End Function

        Private Shared Sub AddCommandBarButtons(rootHost As MASSkiaRootHost,
                                                host As MASPanelShellFloatHost,
                                                options As MASApplicationPanelShellOptions,
                                                content As MASFloatControlsContent,
                                                commandBar As MASApplicationPanelShellCommandBar,
                                                areaPx As SKRect,
                                                fullContentAreaPx As SKRect,
                                                namePrefix As String,
                                                notifyCompletedWhenNonClosing As Boolean)

            If rootHost Is Nothing OrElse host Is Nothing OrElse options Is Nothing OrElse content Is Nothing Then Return
            If commandBar Is Nothing OrElse Not IsUsableBounds(areaPx) Then Return

            Dim visibleActions As List(Of MASApplicationPanelAction) =
                CollectVisibleActions(commandBar.Actions)

            If visibleActions.Count = 0 Then Return

            Dim buttonHeightPx As Single =
                Math.Min(
                    areaPx.Height,
                    Math.Max(24.0F, SafePositive(commandBar.ButtonHeightPx, areaPx.Height)))

            Dim buttonWidthPx As Single = Math.Max(48.0F, SafePositive(commandBar.ButtonWidthPx, 96.0F))
            Dim gapPx As Single = Math.Max(0.0F, commandBar.ButtonGapPx)

            Dim totalWidthPx As Single =
                (buttonWidthPx * CSng(visibleActions.Count)) +
                (gapPx * CSng(Math.Max(0, visibleActions.Count - 1)))

            Dim x As Single =
                ResolveCommandBarX(
                    areaPx:=areaPx,
                    alignment:=commandBar.Alignment,
                    edgeInsetPx:=Math.Max(0.0F, commandBar.EdgeInsetPx),
                    totalWidthPx:=totalWidthPx)

            Dim y As Single = areaPx.Top + ((areaPx.Height - buttonHeightPx) * 0.5F)

            For i As Integer = 0 To visibleActions.Count - 1

                Dim actionItem As MASApplicationPanelAction = visibleActions(i)

                Dim buttonBoundsPx As New SKRect(
                    x,
                    y,
                    x + buttonWidthPx,
                    y + buttonHeightPx)

                Dim button As MASButton =
                    CreateActionButton(
                        actionItem:=actionItem,
                        options:=options,
                        content:=content,
                        namePrefix:=namePrefix,
                        notifyCompletedWhenNonClosing:=notifyCompletedWhenNonClosing)

                host.AddFixedContent(
                    child:=button,
                    relativeContentBoundsLogical:=ToRelativeLogical(
                        rootHost:=rootHost,
                        originPx:=fullContentAreaPx,
                        boundsPx:=buttonBoundsPx))

                x += buttonWidthPx + gapPx

            Next

        End Sub

        Private Shared Function CreateActionButton(actionItem As MASApplicationPanelAction,
                                                   options As MASApplicationPanelShellOptions,
                                                   content As MASFloatControlsContent,
                                                   namePrefix As String,
                                                   notifyCompletedWhenNonClosing As Boolean) As MASButton

            If actionItem Is Nothing Then Throw New ArgumentNullException(NameOf(actionItem))
            If options Is Nothing Then Throw New ArgumentNullException(NameOf(options))
            If content Is Nothing Then Throw New ArgumentNullException(NameOf(content))

            Dim button As New MASButton() With {
                .Name = If(namePrefix, "MASPanelShellButton_") & If(actionItem.CommandId, String.Empty),
                .Text = If(actionItem.Text, String.Empty),
                .Intent = actionItem.Intent,
                .Personality = actionItem.Personality,
                .Enabled = actionItem.Enabled,
                .Visible = actionItem.Visible,
                .IsDefaultButton = actionItem.IsDefaultButton
            }

            AddHandler button.Click,
                Sub(sender As Object, e As EventArgs)

                    If actionItem Is Nothing Then Return
                    If Not actionItem.Enabled Then Return

                    Dim result As MASApplicationPanelResult =
                        MASApplicationPanelResult.FromCommand(
                            commandId:=actionItem.CommandId,
                            userState:=options.UserState)

                    If actionItem.Callback IsNot Nothing Then
                        actionItem.Callback.Invoke(result)
                    End If

                    Dim session As IMASFloatSession = content.Session

                    If actionItem.CloseOnClick Then
                        If session IsNot Nothing Then
                            session.Accept(result)
                        ElseIf options.Completed IsNot Nothing Then
                            options.Completed.Invoke(result)
                        End If

                        Return
                    End If

                    If notifyCompletedWhenNonClosing AndAlso options.Completed IsNot Nothing Then
                        options.Completed.Invoke(result)
                    End If

                End Sub

            Return button

        End Function

        Private Shared Function ShouldBuildToolbar(options As MASApplicationPanelShellOptions) As Boolean

            If options Is Nothing Then Return False
            Return ShouldBuildCommandBar(options.Toolbar)

        End Function

        Private Shared Function ShouldBuildFooter(options As MASApplicationPanelShellOptions) As Boolean

            If options Is Nothing Then Return False
            Return ShouldBuildCommandBar(options.Footer)

        End Function

        Private Shared Function ShouldBuildCommandBar(commandBar As MASApplicationPanelShellCommandBar) As Boolean

            If commandBar Is Nothing OrElse Not commandBar.Visible Then Return False
            Return CollectVisibleActions(commandBar.Actions).Count > 0

        End Function

        Private Shared Function CollectVisibleActions(actions As IList(Of MASApplicationPanelAction)) As List(Of MASApplicationPanelAction)

            Dim result As New List(Of MASApplicationPanelAction)()

            If actions Is Nothing Then Return result

            For Each actionItem As MASApplicationPanelAction In actions
                If actionItem IsNot Nothing AndAlso actionItem.Visible Then
                    result.Add(actionItem)
                End If
            Next

            Return result

        End Function

        Private Shared Function ResolveLayout(options As MASApplicationPanelShellOptions,
                                              availablePx As SKRect) As PanelShellLayout

            Dim result As New PanelShellLayout()

            If options Is Nothing OrElse Not IsUsableBounds(availablePx) Then Return result

            Dim targetWidthPx As Single =
                availablePx.Width * NormalizeRatio(options.WidthRatio, 0.72F)

            Dim targetHeightPx As Single =
                availablePx.Height * NormalizeRatio(options.HeightRatio, 0.72F)

            targetWidthPx =
                Clamp(
                    value:=targetWidthPx,
                    minValue:=Math.Max(1.0F, options.MinWidthPx),
                    maxValue:=Math.Max(options.MinWidthPx, options.MaxWidthPx))

            targetHeightPx =
                Clamp(
                    value:=targetHeightPx,
                    minValue:=Math.Max(1.0F, options.MinHeightPx),
                    maxValue:=Math.Max(options.MinHeightPx, options.MaxHeightPx))

            targetWidthPx = Math.Min(targetWidthPx, availablePx.Width)
            targetHeightPx = Math.Min(targetHeightPx, availablePx.Height)

            Dim left As Single =
                availablePx.Left + ((availablePx.Width - targetWidthPx) * 0.5F)

            Dim top As Single =
                availablePx.Top + ((availablePx.Height - targetHeightPx) * 0.5F)

            result.FloatBoundsPx =
                New SKRect(
                    left,
                    top,
                    left + targetWidthPx,
                    top + targetHeightPx)

            Return result

        End Function

        Private Shared Function ResolveCommandBarX(areaPx As SKRect,
                                                   alignment As MASApplicationPanelShellAlignment,
                                                   edgeInsetPx As Single,
                                                   totalWidthPx As Single) As Single

            Dim x As Single

            Select Case alignment
                Case MASApplicationPanelShellAlignment.Center
                    x = areaPx.Left + ((areaPx.Width - totalWidthPx) * 0.5F)

                Case MASApplicationPanelShellAlignment.[End]
                    x = areaPx.Right - edgeInsetPx - totalWidthPx

                Case Else
                    x = areaPx.Left + edgeInsetPx
            End Select

            If x < areaPx.Left Then x = areaPx.Left
            If x + totalWidthPx > areaPx.Right Then
                x = Math.Max(areaPx.Left, areaPx.Right - totalWidthPx)
            End If

            Return x

        End Function

        Private Shared Function ToRelativeSizeLogical(rootHost As MASSkiaRootHost,
                                                      boundsPx As SKRect) As SKRect

            If rootHost Is Nothing Then Return SKRect.Empty

            Dim dpi As Single = ResolveDpi(rootHost)

            Return New SKRect(
                0.0F,
                0.0F,
                boundsPx.Width / dpi,
                boundsPx.Height / dpi)

        End Function

        Private Shared Function ToRelativeLogical(rootHost As MASSkiaRootHost,
                                                  originPx As SKRect,
                                                  boundsPx As SKRect) As SKRect

            If rootHost Is Nothing Then Return SKRect.Empty

            Dim absoluteLogical As SKRect = rootHost.PxToLogical(boundsPx)
            Dim originLogical As SKRect = rootHost.PxToLogical(originPx)

            Return New SKRect(
                absoluteLogical.Left - originLogical.Left,
                absoluteLogical.Top - originLogical.Top,
                absoluteLogical.Right - originLogical.Left,
                absoluteLogical.Bottom - originLogical.Top)

        End Function

        Private Shared Function ResolveDpi(rootHost As MASSkiaRootHost) As Single

            If rootHost Is Nothing Then Return 1.0F

            Dim ctx As Nexamas.UI.Theming.MASThemeContext =
                rootHost.ThemeHostCore.TryGetContext()

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

            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then
                Return 1.0F
            End If

            Return dpi

        End Function

        Private Shared Function IsUsableBounds(bounds As SKRect) As Boolean

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

        End Function

        Private Shared Function NormalizeRatio(value As Single,
                                               fallback As Single) As Single

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

                Return fallback
            End If

            Return Math.Max(0.05F, Math.Min(1.0F, value))

        End Function

        Private Shared Function SafePositive(value As Single,
                                             fallback As Single) As Single

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

            Return value

        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 Shared Sub InvokeCompleted(options As MASApplicationPanelShellOptions,
                                           args As MASFloatClosedEventArgs)

            If options Is Nothing OrElse options.Completed Is Nothing Then Return

            Dim panelResult As MASApplicationPanelResult = Nothing

            If args IsNot Nothing AndAlso
               args.Result IsNot Nothing AndAlso
               args.Result.Value IsNot Nothing Then

                panelResult = TryCast(args.Result.Value, MASApplicationPanelResult)
            End If

            If panelResult Is Nothing Then
                panelResult = MASApplicationPanelResult.Cancel(options.UserState)
            End If

            options.Completed.Invoke(panelResult)

        End Sub

        Private Structure PanelShellLayout

            Friend FloatBoundsPx As SKRect

            Friend ReadOnly Property IsValid As Boolean
                Get
                    Return Not FloatBoundsPx.IsEmpty AndAlso
                           FloatBoundsPx.Width > 1.0F AndAlso
                           FloatBoundsPx.Height > 1.0F
                End Get
            End Property

        End Structure

        Private Structure PanelShellSectionsLayout

            Friend FullContentAreaPx As SKRect
            Friend ToolbarAreaPx As SKRect
            Friend BodyAreaPx As SKRect
            Friend FooterAreaPx As SKRect
            Friend HasToolbar As Boolean
            Friend HasFooter As Boolean

            Friend ReadOnly Property IsValid As Boolean
                Get
                    Return Not BodyAreaPx.IsEmpty AndAlso
                           BodyAreaPx.Width > 1.0F AndAlso
                           BodyAreaPx.Height > 1.0F
                End Get
            End Property

        End Structure

    End Class

End Namespace
