Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' Beginner-facing controls API for one MASApplicationWindow.
    ''' This class does not create a controls layer; it delegates to the single root-owned ControlsLayer instance.
    ''' It is also the official Application-level creation gateway for direct Skia controls: each Create/Add method
    ''' returns the real MAS control type and then hosts it through the same root-owned ControlsLayer, without duplicate control ownership,
    ''' adapters, shadow registries, or parallel runtimes.
    ''' </summary>
    Partial Public NotInheritable Class MASApplicationWindowControls

        Private ReadOnly _rootHost As MASSkiaRootHost
        Private ReadOnly _shell As MASApplicationWindowShell
        Private ReadOnly _layer As ControlsLayer
        Private ReadOnly _ownerThreadId As Integer

        Private ReadOnly Property RootHost As MASSkiaRootHost
            Get
                _rootHost.ThrowIfDisposedCore()
                Return _rootHost
            End Get
        End Property

        Private ReadOnly Property Layer As ControlsLayer
            Get
                _rootHost.ThrowIfDisposedCore()
                Return _layer
            End Get
        End Property

        Private Sub ThrowIfDisposed()
            _rootHost.ThrowIfDisposedCore()
        End Sub
        Private Sub ExecuteControlsUiThread(operationName As String, action As Action)
            If String.IsNullOrWhiteSpace(operationName) Then operationName = "Operation"
            If action Is Nothing Then Throw New ArgumentNullException(NameOf(action))

            ThrowIfDisposed()

            Dim executed As Boolean = False
            Dim capturedException As Exception = Nothing

            Dim invoked As Boolean = RootHost.InvokeOnUiThreadCore(
                Sub()
                    executed = True
                    capturedException = InvokeControlsUiAction(action)
                End Sub,
                "MASApplicationWindow.Controls." & operationName)

            If Not invoked OrElse Not executed Then
                If CanExecuteControlsInlineBeforeActiveHost() Then
                    executed = True
                    capturedException = InvokeControlsUiAction(action)
                End If
            End If

            If capturedException IsNot Nothing Then
                Throw New InvalidOperationException(
                    "MASApplicationWindow.Controls." & operationName & " failed on the owning UI thread. See InnerException for the original misuse or runtime fault.",
                    capturedException)
            End If

            If Not executed Then
                ThrowIfDisposed()
                Throw New InvalidOperationException(
                    "MASApplicationWindow.Controls." & operationName & " requires an active UI-thread host before controls can be accessed or mutated.")
            End If
        End Sub

        Private Shared Function InvokeControlsUiAction(action As Action) As Exception
            Try
                action.Invoke()
                Return Nothing
            Catch ex As Exception
                Return ex
            End Try
        End Function

        Private Function CanExecuteControlsInlineBeforeActiveHost() As Boolean
            If Environment.CurrentManagedThreadId <> _ownerThreadId Then Return False

            Try
                Dim owner As System.Windows.Forms.Form = _rootHost.OwnerForm
                If owner Is Nothing OrElse owner.IsDisposed Then Return False

                ' CreateWindow callers commonly build controls immediately from the owning Form constructor,
                ' before the native handles exist. That path is still on the owner thread, so it is safe to
                ' execute inline while cross-thread callers remain blocked until the host can marshal.
                Return Not owner.IsHandleCreated
            Catch masCaughtExceptionInlineGate As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                    masCaughtExceptionInlineGate,
                    "MASApplicationWindow.Controls.InlineUiThreadGate")
                Return False
            End Try
        End Function

        Private Function ExecuteControlsUiThread(Of TResult)(operationName As String, func As Func(Of TResult)) As TResult
            If func Is Nothing Then Throw New ArgumentNullException(NameOf(func))

            Dim result As TResult = Nothing

            ExecuteControlsUiThread(
                operationName,
                Sub()
                    result = func.Invoke()
                End Sub)

            Return result
        End Function

        Private _layoutPageSession As MASApplicationControlsLayoutSession
        Private _layoutRegionsSession As MASApplicationControlsRegionLayoutSession
        Private _layoutApplicationSurfaceSession As MASApplicationControlsApplicationSurfaceLayoutSession
        Private ReadOnly _layoutHostedControls As New HashSet(Of MASControlBase)()
        Private ReadOnly _factoryCreatedControls As New HashSet(Of MASControlBase)()
        Private _retainedLayoutBuildAdmissionDepth As Integer

        Friend Sub New(rootHost As MASSkiaRootHost,
                       shell As MASApplicationWindowShell)
            If rootHost Is Nothing Then Throw New ArgumentNullException(NameOf(rootHost))
            If shell Is Nothing Then Throw New ArgumentNullException(NameOf(shell))
            _rootHost = rootHost
            _shell = shell
            _layer = rootHost.ControlsLayerCore
            _ownerThreadId = Environment.CurrentManagedThreadId
            If _layer Is Nothing Then Throw New ArgumentNullException("ControlsLayerCore")
        End Sub

        Public ReadOnly Property Count As Integer
            Get
                Return ExecuteControlsUiThread("Count", Function() Layer.Count)
            End Get
        End Property


        ''' <summary>
        ''' Detaches a control from this window. This is intentionally detach-only: it does not dispose
        ''' the control, so callers can reuse or dispose it explicitly according to their own ownership model.
        ''' Use RemoveAndDispose when the window should remove and dispose the control in one lifecycle step.
        ''' </summary>
        Public Sub Remove(control As MASControlBase)
            ExecuteControlsUiThread(
                "Remove",
                Sub()
                    If control Is Nothing Then Return
                    ReleaseRetainedLayoutForDirectMutationInternal()

                    Dim wasFactoryCreated As Boolean = _factoryCreatedControls.Contains(control)

                    Try
                        Layer.Remove(control)
                    Finally
                        If wasFactoryCreated AndAlso Not ContainsCore(control) Then
                            _factoryCreatedControls.Remove(control)
                        End If
                    End Try
                End Sub)
        End Sub

        ''' <summary>
        ''' Detaches all direct controls from this window. This is intentionally detach-only and does not
        ''' dispose controls. Use ClearAndDisposeCreatedControls to dispose controls created by the Add* factories.
        ''' </summary>
        Public Sub Clear()
            ExecuteControlsUiThread(
                "Clear",
                Sub()
                    ReleaseRetainedLayoutForDirectMutationInternal()
                    Dim factoryHostedSnapshot As MASControlBase() = GetFactoryCreatedHostedSnapshotInternal()

                    Try
                        Layer.Clear()
                    Finally
                        ReleaseFactoryCreatedOwnershipForDetachedControlsInternal(factoryHostedSnapshot)
                    End Try
                End Sub)
        End Sub

        ''' <summary>
        ''' Removes and disposes a single control through the window controls lifecycle boundary.
        ''' This is the explicit destructive counterpart to Remove, which remains detach-only.
        ''' </summary>
        Public Sub RemoveAndDispose(control As MASControlBase)
            ExecuteControlsUiThread(
                "RemoveAndDispose",
                Sub()
                    If control Is Nothing Then Return
                    ReleaseRetainedLayoutForDirectMutationInternal()

                    Try
                        If ContainsCore(control) Then
                            Layer.RemoveAndDispose(control)
                        End If
                    Finally
                        _factoryCreatedControls.Remove(control)
                    End Try
                End Sub)
        End Sub

        ''' <summary>
        ''' Removes and disposes controls that were created and hosted by this facade through Add* factories.
        ''' Manually added controls remain consumer-owned and are not disposed by this method.
        ''' </summary>
        Public Sub ClearAndDisposeCreatedControls()
            ExecuteControlsUiThread(
                "ClearAndDisposeCreatedControls",
                Sub()
                    ReleaseRetainedLayoutForDirectMutationInternal()

                    Dim targets As MASControlBase() = _factoryCreatedControls.ToArray()
                    If targets.Length = 0 Then Return

                    Dim failures As New List(Of Exception)()
                    Layer.BeginUpdateInternal()

                    Try
                        For Each control As MASControlBase In targets
                            If control Is Nothing Then Continue For

                            Try
                                If ContainsCore(control) Then
                                    Layer.RemoveAndDispose(control)
                                End If
                            Catch ex As Exception
                                failures.Add(ex)
                            Finally
                                _factoryCreatedControls.Remove(control)
                            End Try
                        Next
                    Finally
                        Layer.EndUpdateInternal()
                    End Try

                    If failures.Count > 0 Then
                        Throw New AggregateException(
                            "One or more factory-created controls failed during ClearAndDisposeCreatedControls after disposal was attempted for every tracked control.",
                            failures)
                    End If
                End Sub)
        End Sub

        Private Function GetFactoryCreatedHostedSnapshotInternal() As MASControlBase()
            If _factoryCreatedControls.Count = 0 Then Return Array.Empty(Of MASControlBase)()

            Dim hosted As New List(Of MASControlBase)()
            For Each control As MASControlBase In _factoryCreatedControls
                If control Is Nothing Then Continue For
                If ContainsCore(control) Then hosted.Add(control)
            Next

            Return hosted.ToArray()
        End Function

        Private Sub ReleaseFactoryCreatedOwnershipForDetachedControlsInternal(controls As IEnumerable(Of MASControlBase))
            If controls Is Nothing Then Return

            For Each control As MASControlBase In controls
                If control Is Nothing Then Continue For
                If Not ContainsCore(control) Then _factoryCreatedControls.Remove(control)
            Next
        End Sub

        Public Sub BringToFront(control As MASControlBase)
            ExecuteControlsUiThread(
                "BringToFront",
                Sub()
                    If control Is Nothing Then Return
                    Layer.BringToFront(control)
                End Sub)
        End Sub

        ''' <summary>
        ''' Runs a group of control additions, state changes, and direct placements as one visual update.
        ''' This is the preferred performance gateway for external screens that create or place many MAS controls
        ''' without using the Composition layout system.
        ''' </summary>
        Public Sub PerformUpdate(updateAction As Action(Of MASApplicationWindowControls))
            If updateAction Is Nothing Then Throw New ArgumentNullException(NameOf(updateAction))

            ExecuteControlsUiThread(
                "PerformUpdate",
                Sub()
                    Layer.BeginUpdateInternal()

                    Try
                        updateAction.Invoke(Me)
                    Finally
                        Layer.EndUpdateInternal()
                    End Try
                End Sub)
        End Sub
    End Class

End Namespace
