Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic

Namespace Nexamas.UI.Rendering

    ''' <summary>
    ''' Friend-only runtime switch for broad Nexamas UI surface materials. Runtime selection is
    ''' render-scope owned, not process-global: a RootHost pushes its own material while it paints,
    ''' so two MAS windows can render different Auto surfaces without leaking state into each other.
    ''' Explicit material overrides still bypass this switch and stay owned by their component.
    ''' </summary>
    Friend NotInheritable Class MASSurfaceMaterialRuntimeSwitch
        <ThreadStatic>
        Private Shared _scopedSelections As Stack(Of RuntimeSelection)

        <ThreadStatic>
        Private Shared _ambientSelection As RuntimeSelection

        Private Shared ReadOnly _defaultSelection As New RuntimeSelection(
            MASSurfaceMaterialProfileIds.PlatformDefault,
            MASSurfaceMaterialIds.Auto)

        Private Sub New()
        End Sub

        Friend Shared ReadOnly Property IsActive As Boolean
            Get
                Dim selection As RuntimeSelection = CurrentSelection()
                Return IsProfileActive(selection.ProfileId) OrElse IsMaterialActive(selection.MaterialKey)
            End Get
        End Property

        Friend Shared ReadOnly Property ActiveProfileId As String
            Get
                Return CurrentSelection().ProfileId
            End Get
        End Property

        Friend Shared ReadOnly Property ActiveMaterialKey As String
            Get
                Return CurrentSelection().MaterialKey
            End Get
        End Property

        Friend Shared Function BeginMaterialScope(materialKey As String) As IDisposable
            Return BeginScope(MASSurfaceMaterialProfileIds.PlatformDefault, materialKey)
        End Function

        Friend Shared Function BeginProfileScope(profileId As String) As IDisposable
            Return BeginScope(profileId, MASSurfaceMaterialIds.Auto)
        End Function

        Friend Shared Function BeginScope(profileId As String,
                                          materialKey As String) As IDisposable
            Dim selection As RuntimeSelection = CreateSelection(profileId, materialKey)
            If _scopedSelections Is Nothing Then _scopedSelections = New Stack(Of RuntimeSelection)()
            _scopedSelections.Push(selection)
            Return New ScopeLease(selection)
        End Function

        ''' <summary>
        ''' Clears only the current thread's ambient compatibility override. It intentionally does
        ''' not mutate any process-global state because runtime material selection belongs to the
        ''' painting host/scope that owns the surface being rendered.
        ''' </summary>
        Friend Shared Sub Reset()
            _ambientSelection = Nothing
        End Sub

        ''' <summary>
        ''' Compatibility entry for internal callers that still select a material outside a render
        ''' scope. New host/application code must use BeginMaterialScope or component-owned explicit
        ''' material properties so selection remains isolated.
        ''' </summary>
        Friend Shared Sub SelectProfile(profileId As String)
            _ambientSelection = CreateSelection(profileId, MASSurfaceMaterialIds.Auto)
        End Sub

        ''' <summary>
        ''' Compatibility entry for internal callers that still select a material outside a render
        ''' scope. New host/application code must use BeginMaterialScope or component-owned explicit
        ''' material properties so selection remains isolated.
        ''' </summary>
        Friend Shared Sub SelectMaterial(materialKey As String)
            _ambientSelection = CreateSelection(MASSurfaceMaterialProfileIds.PlatformDefault, materialKey)
        End Sub

        Friend Shared Function ResolveProfileForImplicit(requestedProfileId As String,
                                                         requestedMaterialKey As String,
                                                         hasExplicitMaterialOverride As Boolean) As String
            Dim normalizedProfileId As String = MASSurfaceMaterialSlotProfileRegistry.Canonicalize(requestedProfileId)
            If normalizedProfileId.Length = 0 Then normalizedProfileId = MASSurfaceMaterialProfileIds.PlatformDefault

            If Not IsImplicitSwitchTarget(requestedMaterialKey, hasExplicitMaterialOverride) Then Return normalizedProfileId

            Dim selection As RuntimeSelection = CurrentSelection()
            If IsMaterialActive(selection.MaterialKey) Then Return normalizedProfileId
            If Not IsProfileActive(selection.ProfileId) Then Return normalizedProfileId
            If Not IsSwitchableDefaultProfileId(normalizedProfileId) Then Return normalizedProfileId
            Return selection.ProfileId
        End Function

        Friend Shared Function TryResolveActiveMaterialForImplicit(requestedMaterialKey As String,
                                                                   hasExplicitMaterialOverride As Boolean,
                                                                   ByRef resolvedMaterialKey As String,
                                                                   ByRef resolvedFrameKey As String) As Boolean
            resolvedMaterialKey = String.Empty
            resolvedFrameKey = String.Empty

            If Not IsImplicitSwitchTarget(requestedMaterialKey, hasExplicitMaterialOverride) Then Return False

            Dim selection As RuntimeSelection = CurrentSelection()
            If Not IsMaterialActive(selection.MaterialKey) Then Return False

            resolvedMaterialKey = selection.MaterialKey

            Dim definition As MASSurfaceMaterialDefinition = MASSurfaceMaterialRegistry.ResolveDefinition(resolvedMaterialKey)
            If definition IsNot Nothing Then
                resolvedFrameKey = definition.DefaultFrameKey
            End If
            If String.IsNullOrWhiteSpace(resolvedFrameKey) Then resolvedFrameKey = MASSurfaceFrameIds.PlatformDefault

            Return resolvedMaterialKey.Length > 0
        End Function

        Friend Shared Function TryResolveActiveProfileAssignmentForImplicit(requestedMaterialKey As String,
                                                                            hasExplicitMaterialOverride As Boolean,
                                                                            slot As MASSurfaceSlot,
                                                                            ByRef assignment As MASSurfaceMaterialSlotAssignment,
                                                                            ByRef activeProfileId As String) As Boolean
            assignment = Nothing
            activeProfileId = MASSurfaceMaterialProfileIds.PlatformDefault

            If Not IsImplicitSwitchTarget(requestedMaterialKey, hasExplicitMaterialOverride) Then Return False

            Dim selection As RuntimeSelection = CurrentSelection()
            If IsMaterialActive(selection.MaterialKey) OrElse Not IsProfileActive(selection.ProfileId) Then Return False
            activeProfileId = selection.ProfileId

            Dim profile As MASSurfaceMaterialSlotProfile = MASSurfaceMaterialSlotProfileRegistry.Resolve(activeProfileId)
            If profile Is Nothing Then Return False

            Dim normalizedSlot As MASSurfaceSlot = MASSurfaceSlotCatalog.Normalize(slot)
            If normalizedSlot = MASSurfaceSlot.Unknown Then normalizedSlot = MASSurfaceSlot.Root

            Return profile.TryGetAssignment(normalizedSlot, assignment)
        End Function

        Friend Shared Function ResolveRuntimeMaterialKey(consumerKind As MASSurfaceTreatmentConsumerKind,
                                                         requestedMaterialKey As String,
                                                         hasExplicitMaterialOverride As Boolean,
                                                         Optional slot As MASSurfaceSlot = MASSurfaceSlot.Root) As String
            Dim forcedMaterialKey As String = String.Empty
            Dim forcedFrameKey As String = String.Empty
            If TryResolveActiveMaterialForImplicit(requestedMaterialKey, hasExplicitMaterialOverride, forcedMaterialKey, forcedFrameKey) Then
                Return forcedMaterialKey
            End If

            Dim assignment As MASSurfaceMaterialSlotAssignment = Nothing
            Dim profileId As String = String.Empty
            If TryResolveActiveProfileAssignmentForImplicit(requestedMaterialKey, hasExplicitMaterialOverride, slot, assignment, profileId) AndAlso assignment IsNot Nothing Then
                Return assignment.MaterialKey
            End If

            Return MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForResolution(requestedMaterialKey)
        End Function

        Friend Shared Function IsImplicitSwitchTarget(materialKey As String,
                                                      hasExplicitMaterialOverride As Boolean) As Boolean
            Dim normalized As String = MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForStorage(materialKey)

            If IsSwitchableDefaultMaterialKey(normalized) Then Return True
            If Not hasExplicitMaterialOverride Then Return True

            Return False
        End Function

        Friend Shared Function IsSwitchableDefaultMaterialKey(materialKey As String) As Boolean
            Dim normalized As String = MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForStorage(materialKey)
            Return normalized.Length = 0 OrElse
                   String.Equals(normalized, MASSurfaceMaterialIds.Auto, StringComparison.OrdinalIgnoreCase) OrElse
                   String.Equals(normalized, MASSurfaceMaterialIds.PlatformDefault, StringComparison.OrdinalIgnoreCase)
        End Function

        Friend Shared Function IsSwitchableDefaultProfileId(profileId As String) As Boolean
            Dim normalized As String = MASSurfaceMaterialSlotProfileRegistry.Canonicalize(profileId)
            Return normalized.Length = 0 OrElse
                   String.Equals(normalized, MASSurfaceMaterialProfileIds.Auto, StringComparison.OrdinalIgnoreCase) OrElse
                   String.Equals(normalized, MASSurfaceMaterialProfileIds.PlatformDefault, StringComparison.OrdinalIgnoreCase)
        End Function

        Private Shared Function CurrentSelection() As RuntimeSelection
            If _scopedSelections IsNot Nothing AndAlso _scopedSelections.Count > 0 Then
                Return _scopedSelections.Peek()
            End If

            If _ambientSelection IsNot Nothing Then Return _ambientSelection
            Return _defaultSelection
        End Function

        Private Shared Function CreateSelection(profileId As String,
                                                materialKey As String) As RuntimeSelection
            Dim canonicalMaterialKey As String = MASSurfaceMaterialRegistry.Canonicalize(
                MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForStorage(materialKey))
            Dim canonicalProfileId As String = MASSurfaceMaterialSlotProfileRegistry.Canonicalize(profileId)

            If String.IsNullOrWhiteSpace(canonicalMaterialKey) Then canonicalMaterialKey = MASSurfaceMaterialIds.Auto
            If String.IsNullOrWhiteSpace(canonicalProfileId) Then canonicalProfileId = MASSurfaceMaterialProfileIds.PlatformDefault

            If Not IsSwitchableDefaultMaterialKey(canonicalMaterialKey) AndAlso Not MASSurfaceMaterialRegistry.Contains(canonicalMaterialKey) Then
                canonicalMaterialKey = MASSurfaceMaterialIds.Auto
            End If

            Dim resolvedProfile As MASSurfaceMaterialSlotProfile = Nothing
            If Not IsSwitchableDefaultProfileId(canonicalProfileId) AndAlso
               Not MASSurfaceMaterialSlotProfileRegistry.TryResolve(canonicalProfileId, resolvedProfile) Then
                canonicalProfileId = MASSurfaceMaterialProfileIds.PlatformDefault
            End If

            If Not IsSwitchableDefaultMaterialKey(canonicalMaterialKey) Then
                canonicalProfileId = MASSurfaceMaterialProfileIds.PlatformDefault
            End If

            Return New RuntimeSelection(canonicalProfileId, canonicalMaterialKey)
        End Function

        Private Shared Function IsProfileActive(profileId As String) As Boolean
            Dim normalized As String = MASSurfaceMaterialSlotProfileRegistry.Canonicalize(profileId)
            Return normalized.Length > 0 AndAlso
                   Not String.Equals(normalized, MASSurfaceMaterialProfileIds.PlatformDefault, StringComparison.OrdinalIgnoreCase) AndAlso
                   Not String.Equals(normalized, MASSurfaceMaterialProfileIds.Auto, StringComparison.OrdinalIgnoreCase)
        End Function

        Private Shared Function IsMaterialActive(materialKey As String) As Boolean
            Dim normalized As String = MASSurfaceTreatmentMaterialKeyNormalizer.NormalizeForStorage(materialKey)
            Return normalized.Length > 0 AndAlso
                   Not String.Equals(normalized, MASSurfaceMaterialIds.Auto, StringComparison.OrdinalIgnoreCase) AndAlso
                   Not String.Equals(normalized, MASSurfaceMaterialIds.PlatformDefault, StringComparison.OrdinalIgnoreCase)
        End Function

        Private NotInheritable Class RuntimeSelection
            Friend Sub New(profileId As String, materialKey As String)
                Me.ProfileId = If(profileId, String.Empty)
                Me.MaterialKey = If(materialKey, String.Empty)
            End Sub

            Friend ReadOnly Property ProfileId As String
            Friend ReadOnly Property MaterialKey As String
        End Class

        Private NotInheritable Class ScopeLease
            Implements IDisposable

            Private ReadOnly _selection As RuntimeSelection
            Private _disposed As Boolean

            Friend Sub New(selection As RuntimeSelection)
                _selection = selection
            End Sub

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

                If _scopedSelections Is Nothing OrElse _scopedSelections.Count = 0 Then Return

                If Object.ReferenceEquals(_scopedSelections.Peek(), _selection) Then
                    _scopedSelections.Pop()
                    Return
                End If

                Dim remaining As New Stack(Of RuntimeSelection)()
                While _scopedSelections.Count > 0
                    Dim item As RuntimeSelection = _scopedSelections.Pop()
                    If Object.ReferenceEquals(item, _selection) Then Exit While
                    remaining.Push(item)
                End While

                While remaining.Count > 0
                    _scopedSelections.Push(remaining.Pop())
                End While
            End Sub
        End Class
    End Class

End Namespace
