Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic

Namespace Nexamas.UI.Theming

    ''' <summary>
    ''' Global registry and current-theme selector for Nexamas UI application themes.
    ''' </summary>
    ''' <remarks>
    ''' ThemeManager owns registration and current theme selection only. It does not know
    ''' about DPI, typography, rendering, controls, layout, or runtime snapshots.
    ''' </remarks>
    Friend NotInheritable Class ThemeManager

        Private Sub New()
        End Sub

#Region "Events"

        Friend NotInheritable Class CurrentThemeChangedEventArgs
            Inherits EventArgs

            Friend Sub New(oldTheme As IMASAppTheme, newTheme As IMASAppTheme)
                Me.OldTheme = oldTheme
                Me.NewTheme = newTheme
            End Sub

            Friend ReadOnly Property OldTheme As IMASAppTheme
            Friend ReadOnly Property NewTheme As IMASAppTheme
        End Class

        Friend Shared Event CurrentChangedCore(newTheme As IMASAppTheme)
        Friend Shared Event CurrentThemeChangedCore(sender As Object, e As CurrentThemeChangedEventArgs)
        Friend Shared Event ThemeRegisteredCore(theme As IMASAppTheme)

        Friend Shared Event CurrentChanged(newTheme As IMASAppTheme)
        Friend Shared Event CurrentThemeChanged(sender As Object, e As CurrentThemeChangedEventArgs)
        Friend Shared Event ThemeRegistered(theme As IMASAppTheme)

#End Region

#Region "State"

        Private Shared ReadOnly _gate As New Object()
        Private Shared ReadOnly _publicEventGate As New Object()
        Private Shared ReadOnly _themes As New List(Of IMASAppTheme)()
        Private Shared ReadOnly _byId As New Dictionary(Of String, IMASAppTheme)(StringComparer.OrdinalIgnoreCase)
        Private Shared ReadOnly _publicCurrentChangedHandlers As New List(Of Action(Of IMASAppTheme))()
        Private Shared ReadOnly _publicCurrentThemeChangedHandlers As New List(Of EventHandler(Of CurrentThemeChangedEventArgs))()
        Private Shared ReadOnly _publicThemeRegisteredHandlers As New List(Of Action(Of IMASAppTheme))()

        Private Const DefaultThemeId As String = "premium-office-mas-signature"

        Private Shared _current As IMASAppTheme = Nothing
        Private Shared _initialized As Boolean = False

#End Region

#Region "Initialization"

        Friend Shared Sub EnsureInitialized()
            EnsureInitializedCore()
        End Sub

        Friend Shared Sub EnsureInitializedCore()
            SyncLock _gate
                If _initialized Then Return
                InitializeCoreLocked()
            End SyncLock
        End Sub

        Private Shared Sub InitializeCoreLocked()
            If _initialized Then Return

            Dim coreThemes As IMASAppTheme() = {
        New MASPremiumOfficeApp(),
        New MASGreenOceanApp(),
        New MASDarkBlueNightSkyApp()
    }

            For Each theme As IMASAppTheme In coreThemes
                Dim id As String = GetValidatedThemeId(theme)

                If _byId.ContainsKey(id) Then
                    Continue For
                End If

                _themes.Add(theme)
                _byId.Add(id, theme)
            Next

            Dim defaultTheme As IMASAppTheme = Nothing
            If Not _byId.TryGetValue(DefaultThemeId, defaultTheme) Then
                Throw New InvalidOperationException($"Default theme '{DefaultThemeId}' is not registered.")
            End If

            _current = defaultTheme
            _initialized = True
        End Sub

#End Region

#Region "Public Access"

        Friend Shared ReadOnly Property Current As IMASAppTheme
            Get
                Return CurrentCore
            End Get
        End Property

        Friend Shared ReadOnly Property CurrentCore As IMASAppTheme
            Get
                EnsureInitializedCore()

                SyncLock _gate
                    If _current Is Nothing Then
                        Throw New InvalidOperationException("No current theme is active.")
                    End If

                    Return _current
                End SyncLock
            End Get
        End Property

        Friend Shared Function HasCurrent() As Boolean
            Return HasCurrentCore()
        End Function

        Friend Shared Function HasCurrentCore() As Boolean
            EnsureInitializedCore()

            SyncLock _gate
                Return _current IsNot Nothing
            End SyncLock
        End Function

        Friend Shared Function GetAllThemes() As IReadOnlyList(Of IMASAppTheme)
            Return GetAllThemesCore()
        End Function

        Friend Shared Function GetAllThemesCore() As IReadOnlyList(Of IMASAppTheme)
            EnsureInitializedCore()

            SyncLock _gate
                Return _themes.ToArray()
            End SyncLock
        End Function

        Friend Shared Function GetById(id As String) As IMASAppTheme
            Return GetByIdCore(id)
        End Function

        Friend Shared Function GetByIdCore(id As String) As IMASAppTheme
            Dim normalizedId As String = NormalizeThemeIdOrThrow(id)

            EnsureInitializedCore()

            SyncLock _gate
                Dim theme As IMASAppTheme = Nothing
                If _byId.TryGetValue(normalizedId, theme) Then
                    Return theme
                End If
            End SyncLock

            Return Nothing
        End Function

#End Region

#Region "Registration"

        Friend Shared Sub Register(theme As IMASAppTheme)
            RegisterCore(theme)
        End Sub

        Friend Shared Sub RegisterCore(theme As IMASAppTheme)
            If theme Is Nothing Then
                Throw New ArgumentNullException(NameOf(theme))
            End If

            EnsureInitializedCore()

            Dim registeredTheme As IMASAppTheme = Nothing
            Dim shouldRaise As Boolean = False

            SyncLock _gate
                Dim id As String = GetValidatedThemeId(theme)

                If _byId.ContainsKey(id) Then
                    Throw New InvalidOperationException($"Theme '{id}' is already registered.")
                End If

                _themes.Add(theme)
                _byId.Add(id, theme)

                registeredTheme = theme
                shouldRaise = True
            End SyncLock

            If shouldRaise Then
                RaiseEvent ThemeRegisteredCore(registeredTheme)
                RaisePublicThemeRegistered(registeredTheme)
                RaiseEvent ThemeRegistered(registeredTheme)
            End If
        End Sub

        Private Shared Sub RegisterCoreLocked(theme As IMASAppTheme)
            If theme Is Nothing Then
                Throw New ArgumentNullException(NameOf(theme))
            End If

            Dim id As String = GetValidatedThemeId(theme)

            If _byId.ContainsKey(id) Then
                Throw New InvalidOperationException($"Theme '{id}' is already registered.")
            End If

            _themes.Add(theme)
            _byId.Add(id, theme)
        End Sub

#End Region

#Region "Current Theme"

        Friend Shared Function TryApplyTheme(id As String) As Boolean
            Return TryApplyThemeCore(id)
        End Function

        Friend Shared Function TryApplyThemeCore(id As String) As Boolean
            If String.IsNullOrWhiteSpace(id) Then
                Return False
            End If

            EnsureInitializedCore()

            Dim normalizedId As String = id.Trim()
            Dim oldTheme As IMASAppTheme = Nothing
            Dim newTheme As IMASAppTheme = Nothing
            Dim changed As Boolean = False

            SyncLock _gate
                If Not _byId.TryGetValue(normalizedId, newTheme) Then
                    Return False
                End If

                oldTheme = _current

                If Object.ReferenceEquals(oldTheme, newTheme) Then
                    Return True
                End If

                _current = newTheme
                changed = True
            End SyncLock

            If changed Then
                Dim args As New CurrentThemeChangedEventArgs(oldTheme, newTheme)
                RaiseEvent CurrentChangedCore(newTheme)
                RaiseEvent CurrentThemeChangedCore(Nothing, args)
                RaisePublicCurrentChanged(newTheme)
                RaisePublicCurrentThemeChanged(Nothing, args)
                RaiseEvent CurrentChanged(newTheme)
                RaiseEvent CurrentThemeChanged(Nothing, args)
            End If

            Return True
        End Function

#End Region

#Region "Public Event Isolation"

        Friend Shared Sub AddPublicCurrentChangedHandler(handler As Action(Of IMASAppTheme))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                If _publicCurrentChangedHandlers.Contains(handler) Then Return
                _publicCurrentChangedHandlers.Add(handler)
            End SyncLock
        End Sub

        Friend Shared Sub RemovePublicCurrentChangedHandler(handler As Action(Of IMASAppTheme))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                _publicCurrentChangedHandlers.Remove(handler)
            End SyncLock
        End Sub

        Friend Shared Sub AddPublicCurrentThemeChangedHandler(handler As EventHandler(Of CurrentThemeChangedEventArgs))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                If _publicCurrentThemeChangedHandlers.Contains(handler) Then Return
                _publicCurrentThemeChangedHandlers.Add(handler)
            End SyncLock
        End Sub

        Friend Shared Sub RemovePublicCurrentThemeChangedHandler(handler As EventHandler(Of CurrentThemeChangedEventArgs))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                _publicCurrentThemeChangedHandlers.Remove(handler)
            End SyncLock
        End Sub

        Friend Shared Sub AddPublicThemeRegisteredHandler(handler As Action(Of IMASAppTheme))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                If _publicThemeRegisteredHandlers.Contains(handler) Then Return
                _publicThemeRegisteredHandlers.Add(handler)
            End SyncLock
        End Sub

        Friend Shared Sub RemovePublicThemeRegisteredHandler(handler As Action(Of IMASAppTheme))
            If handler Is Nothing Then Return

            SyncLock _publicEventGate
                _publicThemeRegisteredHandlers.Remove(handler)
            End SyncLock
        End Sub

        Private Shared Sub RaisePublicCurrentChanged(newTheme As IMASAppTheme)
            Dim handlers As Action(Of IMASAppTheme)() = Nothing

            SyncLock _publicEventGate
                handlers = _publicCurrentChangedHandlers.ToArray()
            End SyncLock

            For Each handler As Action(Of IMASAppTheme) In handlers
                Try
                    handler.Invoke(newTheme)
                Catch ex As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                        ex,
                        "ThemeManager.PublicCurrentChanged")
                End Try
            Next
        End Sub

        Private Shared Sub RaisePublicCurrentThemeChanged(sender As Object, e As CurrentThemeChangedEventArgs)
            Dim handlers As EventHandler(Of CurrentThemeChangedEventArgs)() = Nothing

            SyncLock _publicEventGate
                handlers = _publicCurrentThemeChangedHandlers.ToArray()
            End SyncLock

            For Each handler As EventHandler(Of CurrentThemeChangedEventArgs) In handlers
                Try
                    handler.Invoke(sender, e)
                Catch ex As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                        ex,
                        "ThemeManager.PublicCurrentThemeChanged")
                End Try
            Next
        End Sub

        Private Shared Sub RaisePublicThemeRegistered(theme As IMASAppTheme)
            Dim handlers As Action(Of IMASAppTheme)() = Nothing

            SyncLock _publicEventGate
                handlers = _publicThemeRegisteredHandlers.ToArray()
            End SyncLock

            For Each handler As Action(Of IMASAppTheme) In handlers
                Try
                    handler.Invoke(theme)
                Catch ex As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(
                        ex,
                        "ThemeManager.PublicThemeRegistered")
                End Try
            Next
        End Sub

#End Region

#Region "Helpers"

        Private Shared Function NormalizeThemeIdOrThrow(id As String) As String
            If String.IsNullOrWhiteSpace(id) Then
                Throw New ArgumentException("Theme id cannot be null, empty, or whitespace.", NameOf(id))
            End If

            Return id.Trim()
        End Function

        Private Shared Function GetValidatedThemeId(theme As IMASAppTheme) As String
            If theme Is Nothing Then
                Throw New ArgumentNullException(NameOf(theme))
            End If

            Return NormalizeThemeIdOrThrow(theme.Id)
        End Function

#End Region

    End Class

End Namespace