Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.Application


    ''' <summary>
    ''' Retained Application layout session for explicit logical regions.
    ''' Region math remains an advanced placement primitive, while every region still resolves through MASLayout.
    ''' </summary>
    Friend NotInheritable Class MASApplicationControlsRegionLayoutSession
        Private ReadOnly _rootHost As MASSkiaRootHost
        Private ReadOnly _controls As MASApplicationWindowControls
        Private ReadOnly _buildAction As Action(Of MASApplicationLayoutRegionsBuilder)
        Private ReadOnly _fixedSizeProfile As MASSizeProfile
        Private ReadOnly _engine As New MASLayoutEngine()
        Private _disposed As Boolean
        Private _isApplying As Boolean

        Friend Sub New(rootHost As MASSkiaRootHost,
                       controls As MASApplicationWindowControls,
                       buildAction As Action(Of MASApplicationLayoutRegionsBuilder),
                       fixedSizeProfile As MASSizeProfile)
            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            If controls Is Nothing Then Throw New ArgumentNullException(NameOf(controls))
            If buildAction Is Nothing Then Throw New ArgumentNullException(NameOf(buildAction))

            _rootHost = rootHost
            _controls = controls
            _buildAction = buildAction
            _fixedSizeProfile = fixedSizeProfile

            AddHandler _rootHost.SurfaceMeasured, AddressOf RootHost_SurfaceMeasured
            AddHandler _rootHost.SizeLayoutRefreshRequested, AddressOf RootHost_SizeLayoutRefreshRequested
        End Sub

        Friend Function ApplyNow() As Boolean
            Return ApplyCore(throwOnError:=True, refreshReason:="InitialApply")
        End Function

        Friend Sub Dispose()
            If _disposed Then Return
            _disposed = True
            RemoveHandler _rootHost.SurfaceMeasured, AddressOf RootHost_SurfaceMeasured
            RemoveHandler _rootHost.SizeLayoutRefreshRequested, AddressOf RootHost_SizeLayoutRefreshRequested
        End Sub

        Private Sub RootHost_SurfaceMeasured(sizePx As SKSizeI)
            If _disposed Then Return
            ApplyCore(throwOnError:=False, refreshReason:="SurfaceMeasured")
        End Sub

        Private Sub RootHost_SizeLayoutRefreshRequested(reason As String)
            If _disposed Then Return
            ApplyCore(throwOnError:=False, refreshReason:=NormalizeRefreshReason(reason))
        End Sub

        Private Shared Function NormalizeRefreshReason(reason As String) As String
            If String.IsNullOrWhiteSpace(reason) Then Return "SizeLayoutRefreshRequested"
            Return reason.Trim()
        End Function

        Private Function ResolveSizeProfile() As MASSizeProfile
            If _fixedSizeProfile IsNot Nothing Then Return MASSizeProfile.Normalize(_fixedSizeProfile)
            Return MASRuntimeSizeProfileService.Current
        End Function

        Private Function ApplyCore(throwOnError As Boolean, refreshReason As String) As Boolean
            If _disposed OrElse _isApplying OrElse _rootHost.IsDisposed Then Return False

            _isApplying = True

            Try
                _rootHost.EnsureThemeContextForComposition()

                Dim viewportPx As SKRect = _rootHost.GetSurfaceViewportPx()
                Dim boundsLogical As SKRect = _rootHost.PxToLogical(viewportPx)
                If boundsLogical.IsEmpty OrElse boundsLogical.Width <= 1.0F OrElse boundsLogical.Height <= 1.0F Then Return False

                Dim regions As New MASApplicationLayoutRegionsBuilder()
                _controls.BeginRetainedLayoutBuildAdmissionInternal()
                Try
                    _buildAction.Invoke(regions)
                Finally
                    _controls.EndRetainedLayoutBuildAdmissionInternal()
                End Try

                Dim hostedControls As New List(Of MASControlBase)()
                regions.CollectControls(hostedControls)

                Dim diagnostics As New MASSizeLayoutDiagnosticBag()
                Dim themeContext As MASThemeContext = _rootHost.ThemeHostCore.TryGetContext()
                Dim sizeProfile As MASSizeProfile = ResolveSizeProfile()
                Dim integration As MASSizeLayoutIntegrationContext = MASSizeLayoutIntegrationGateway.Create(themeContext, sizeProfile, _rootHost.GetDpi())
                Dim mergedSlots As New Dictionary(Of MASControlBase, MASLayoutSlot)()

                For Each entry As MASApplicationLayoutRegionEntry In regions.Entries
                    If entry Is Nothing Then Continue For
                    If entry.BoundsLogical.IsEmpty OrElse entry.BoundsLogical.Width <= 0.0F OrElse entry.BoundsLogical.Height <= 0.0F Then Continue For

                    Dim regionContext As New MASLayoutContext(entry.BoundsLogical, integration, diagnostics)
                    Dim regionPlan As MASLayoutPlan = _engine.Arrange(entry.BuildNode(sizeProfile), regionContext)
                    If MASApplicationWindowControls.ShouldBlockMissingApplicationRegionLayoutPlan(
                        regionPlan,
                        diagnostics,
                        "MASLayout.RegionPlanMissing",
                        "An explicit Application layout region did not produce a MASLayout plan.") Then
                        Continue For
                    End If

                    For Each pair As KeyValuePair(Of MASControlBase, MASLayoutSlot) In regionPlan.Slots
                        If pair.Key Is Nothing OrElse pair.Value Is Nothing Then Continue For
                        If mergedSlots.ContainsKey(pair.Key) Then
                            diagnostics.AddError("MASLayout.RegionDuplicateControl", "The same control was assigned to more than one explicit layout region.", "Type=" & pair.Key.GetType().Name)
                            Continue For
                        End If

                        mergedSlots(pair.Key) = pair.Value
                    Next
                Next

                If diagnostics.HasErrors Then
                    _controls.RecordLayoutRefreshDiagnosticsFailureInternal("LayoutRegions", refreshReason, "ArrangeRegions", diagnostics)
                    Return False
                End If

                Dim mergedPlan As New MASLayoutPlan(New SKSize(boundsLogical.Width, boundsLogical.Height), mergedSlots)
                Dim geometryChanged As Boolean = _controls.TryApplySizeLayoutPlanInternal(mergedPlan, diagnostics)
                If diagnostics.HasErrors Then
                    _controls.RecordLayoutRefreshDiagnosticsFailureInternal("LayoutRegions", refreshReason, "ApplyRegions", diagnostics)
                    Return False
                End If
                Dim ownershipChanged As Boolean = _controls.ReconcileLayoutHostedControlsInternal(hostedControls)
                If geometryChanged OrElse ownershipChanged Then _rootHost.RequestInvalidate()
                _controls.ClearLayoutRefreshFailureInternal("LayoutRegions")
                Return True
            Catch ex As Exception
                _controls.RecordLayoutRefreshExceptionFailureInternal("LayoutRegions", refreshReason, "BuildOrApply", ex)

                If throwOnError Then
                    Throw New InvalidOperationException("MASApplicationWindow.Controls.LayoutRegions failed while building or applying the official MASLayout region plan.", ex)
                End If

                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(ex, "Application.Controls.LayoutRegions.ResizeApply")
                Return False
            Finally
                _isApplying = False
            End Try
        End Function
    End Class

End Namespace
