Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Windows.Forms
Imports Nexamas.UI.Components
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Theming

Namespace Nexamas.UI.CommandActions

     ''' <summary>
     ''' Unified internal registry that owns command definitions and binds them to button, menu, shortcut, toolbar,
     ''' and future CommandBar / CommandPalette surfaces from one route.
     ''' </summary>
     ''' <remarks>
     ''' The registry does not render UI, create forms, scan controls, record telemetry, or replace existing controls.
     ''' Existing surfaces opt in by binding to this registry, then all invocation flows through TryExecute.
     ''' </remarks>
    Friend NotInheritable Class MASCommandRegistry

        Friend Const OfficialSystemName As String = "Nexamas UI Command / Action System Foundation"

        Private ReadOnly _commands As New Dictionary(Of String, MASCommandDefinition)(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _commandOrder As New List(Of String)()
        Private ReadOnly _bindings As New List(Of MASCommandBindingRecord)()
        Private ReadOnly _surfaceLeases As New List(Of MASCommandSurfaceBindingLease)()
        Private ReadOnly _lifecycleRecords As New List(Of MASCommandBindingLifecycleRecord)()

        Private Sub New()
        End Sub

        Friend Shared Function Create() As MASCommandRegistry
            Return New MASCommandRegistry()
        End Function

        Friend ReadOnly Property CommandCount As Integer
            Get
                Return _commands.Count
            End Get
        End Property

        Friend ReadOnly Property BindingCount As Integer
            Get
                Return _bindings.Count
            End Get
        End Property

        Friend Function Register(commandId As String,
                                 text As String,
                                 execute As Action(Of MASCommandExecutionContext),
                                 Optional canExecute As Func(Of MASCommandExecutionContext, Boolean) = Nothing,
                                 Optional shortcut As MASCommandShortcutGesture = Nothing,
                                 Optional category As String = "",
                                 Optional description As String = "") As MASCommandRegistry
            Dim definition As New MASCommandDefinition(commandId, text, execute, canExecute, shortcut, category, description)
            Dim id As String = definition.CommandId

            If Not _commands.ContainsKey(id) Then
                _commandOrder.Add(id)
            End If

            _commands(id) = definition

            If definition.HasShortcut Then
                RememberBinding(id, MASCommandBindingSurfaceKind.Shortcut, definition.Shortcut.DisplayText)
                RememberLifecycle(id, MASCommandBindingSurfaceKind.Shortcut, definition.Shortcut.DisplayText, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            End If

            Return Me
        End Function

        Friend Function HasCommand(commandId As String) As Boolean
            Return _commands.ContainsKey(NormalizeCommandId(commandId))
        End Function

        Friend Function CanExecute(commandId As String,
                                   Optional sourceKind As MASCommandBindingSurfaceKind = MASCommandBindingSurfaceKind.Programmatic,
                                   Optional sourceName As String = "",
                                   Optional parameter As Object = Nothing) As Boolean
            Dim definition As MASCommandDefinition = Nothing
            Dim id As String = NormalizeCommandId(commandId)
            If Not _commands.TryGetValue(id, definition) Then Return False

            Return definition.CanExecute(New MASCommandExecutionContext(id, sourceKind, sourceName, parameter))
        End Function

        Friend Function TryGetCommand(commandId As String,
                                      ByRef definition As MASCommandDefinition) As Boolean
            Return _commands.TryGetValue(NormalizeCommandId(commandId), definition)
        End Function

        Friend Function BindButton(button As MASButton,
                                   commandId As String,
                                   Optional sourceName As String = "",
                                   Optional parameter As Object = Nothing) As MASCommandRegistry
            If button Is Nothing Then Return Me

            Dim definition As MASCommandDefinition = RequireDefinition(commandId)
            Dim id As String = definition.CommandId
            Dim name As String = ResolveSourceName(sourceName, button.Name)

            If String.IsNullOrWhiteSpace(button.Text) Then
                button.Text = definition.Text
            End If

            If String.IsNullOrWhiteSpace(button.Tooltip) Then
                button.Tooltip = definition.Description
            End If

            ReleaseOwnedBinding(button, MASCommandBindingSurfaceKind.Button)

            Dim previousCommand As Action(Of MASButton) = button.Command
            Dim registryReference As New WeakReference(Me)
            Dim assignedCommand As Action(Of MASButton) =
                Sub(owner As MASButton)
                    Dim registry As MASCommandRegistry = TryCast(registryReference.Target, MASCommandRegistry)
                    If registry Is Nothing Then Return

                    Dim ignored As MASCommandInvocationResult = registry.TryExecute(id, MASCommandBindingSurfaceKind.Button, name, If(parameter, owner))
                End Sub

            button.Command = assignedCommand

            RememberBinding(id, MASCommandBindingSurfaceKind.Button, name)
            RememberSurfaceLease(New MASCommandSurfaceBindingLease(
                id,
                MASCommandBindingSurfaceKind.Button,
                name,
                button,
                Sub(target As Object)
                    Dim targetButton As MASButton = TryCast(target, MASButton)
                    If targetButton IsNot Nothing AndAlso Object.ReferenceEquals(targetButton.Command, assignedCommand) Then
                        targetButton.Command = previousCommand
                    End If
                End Sub,
                weakInvocationRoute:=True))
            Return Me
        End Function

        Friend Function BindMenuAction(builder As MASDropDownMenuBuilder,
                                       commandId As String,
                                       Optional sourceName As String = "",
                                       Optional intent As MASDropDownMenuItemIntent = MASDropDownMenuItemIntent.Default,
                                       Optional parameter As Object = Nothing) As MASCommandRegistry
            If builder Is Nothing Then Return Me

            Dim definition As MASCommandDefinition = RequireDefinition(commandId)
            Dim id As String = definition.CommandId
            Dim name As String = ResolveSourceName(sourceName, "DropDownMenu")
            Dim context As New MASCommandExecutionContext(id, MASCommandBindingSurfaceKind.Menu, name, parameter)
            Dim shortcutText As String = If(definition.HasShortcut, definition.Shortcut.DisplayText, String.Empty)

            Dim registryReference As New WeakReference(Me)

            builder.AddAction(
                text:=definition.Text,
                commandId:=id,
                handler:=Sub()
                             Dim registry As MASCommandRegistry = TryCast(registryReference.Target, MASCommandRegistry)
                             If registry Is Nothing Then Return

                             Dim ignored As MASCommandInvocationResult = registry.TryExecute(id, MASCommandBindingSurfaceKind.Menu, name, parameter)
                         End Sub,
                enabled:=definition.CanExecute(context),
                shortcutText:=shortcutText,
                intent:=intent)

            RememberBinding(id, MASCommandBindingSurfaceKind.Menu, name)
            RememberLifecycle(id, MASCommandBindingSurfaceKind.Menu, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            Return Me
        End Function

        Friend Function CreateMenuNode(commandId As String,
                                       Optional sourceName As String = "",
                                       Optional intent As MASDropDownMenuItemIntent = MASDropDownMenuItemIntent.Default,
                                       Optional parameter As Object = Nothing) As MASDropDownMenuNode
            Dim definition As MASCommandDefinition = RequireDefinition(commandId)
            Dim id As String = definition.CommandId
            Dim name As String = ResolveSourceName(sourceName, "DropDownMenuNode")
            Dim context As New MASCommandExecutionContext(id, MASCommandBindingSurfaceKind.Menu, name, parameter)
            Dim shortcutText As String = If(definition.HasShortcut, definition.Shortcut.DisplayText, String.Empty)

            RememberBinding(id, MASCommandBindingSurfaceKind.Menu, name)
            RememberLifecycle(id, MASCommandBindingSurfaceKind.Menu, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            Return MASDropDownMenuNode.Action(definition.Text, id, False, definition.CanExecute(context), shortcutText, Nothing, intent)
        End Function

        Friend Function BindShortcut(commandId As String,
                                     shortcut As MASCommandShortcutGesture,
                                     Optional sourceName As String = "") As MASCommandRegistry
            Dim definition As MASCommandDefinition = RequireDefinition(commandId)
            Dim gesture As MASCommandShortcutGesture = If(shortcut, MASCommandShortcutGesture.Empty)
            _commands(definition.CommandId) = definition.WithShortcut(gesture)

            If Not gesture.IsEmpty Then
                Dim name As String = ResolveSourceName(sourceName, gesture.DisplayText)
                RememberBinding(definition.CommandId, MASCommandBindingSurfaceKind.Shortcut, name)
                RememberLifecycle(definition.CommandId, MASCommandBindingSurfaceKind.Shortcut, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            End If

            Return Me
        End Function

        Friend Function BindToolbar(toolbar As MASToolbar,
                                    ParamArray commandIds() As String) As MASCommandRegistry
            Return BindToolbar(toolbar, String.Empty, commandIds)
        End Function

        Friend Function BindToolbar(toolbar As MASToolbar,
                                    sourceName As String,
                                    ParamArray commandIds() As String) As MASCommandRegistry
            If toolbar Is Nothing Then Return Me

            Dim name As String = ResolveSourceName(sourceName, toolbar.Name)
            If String.IsNullOrWhiteSpace(name) Then name = "Toolbar"

            If Not HasActiveSurfaceLease(toolbar, MASCommandBindingSurfaceKind.Toolbar) Then
                Dim registryReference As New WeakReference(Me)
                Dim handler As EventHandler(Of MASToolbarButtonClickedEventArgs) =
                    Sub(sender As Object, e As MASToolbarButtonClickedEventArgs)
                        If e Is Nothing Then Return

                        Dim registry As MASCommandRegistry = TryCast(registryReference.Target, MASCommandRegistry)
                        If registry Is Nothing Then Return

                        Dim ignored As MASCommandInvocationResult = registry.TryExecute(e.Id, MASCommandBindingSurfaceKind.Toolbar, name, e.Control)
                    End Sub

                AddHandler toolbar.ButtonClicked, handler
                RememberSurfaceLease(New MASCommandSurfaceBindingLease(
                    String.Empty,
                    MASCommandBindingSurfaceKind.Toolbar,
                    name,
                    toolbar,
                    Sub(target As Object)
                        Dim targetToolbar As MASToolbar = TryCast(target, MASToolbar)
                        If targetToolbar IsNot Nothing Then
                            RemoveHandler targetToolbar.ButtonClicked, handler
                        End If
                    End Sub,
                    weakInvocationRoute:=True))
            End If

            Dim ids As String() = If(commandIds, New String() {})
            If ids.Length = 0 Then
                RememberBinding(String.Empty, MASCommandBindingSurfaceKind.Toolbar, name)
                RememberLifecycle(String.Empty, MASCommandBindingSurfaceKind.Toolbar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=True, weakInvocationRoute:=True)
            Else
                For Each commandId As String In ids
                    Dim definition As MASCommandDefinition = RequireDefinition(commandId)
                    RememberBinding(definition.CommandId, MASCommandBindingSurfaceKind.Toolbar, name)
                    RememberLifecycle(definition.CommandId, MASCommandBindingSurfaceKind.Toolbar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=True, weakInvocationRoute:=True)
                Next
            End If

            Return Me
        End Function

        Friend Function BindCommandBar(toolbar As MASToolbar,
                                         ParamArray commandIds() As String) As MASCommandRegistry
            Return BindCommandBar(toolbar, String.Empty, commandIds)
        End Function

        Friend Function BindCommandBar(toolbar As MASToolbar,
                                         sourceName As String,
                                         ParamArray commandIds() As String) As MASCommandRegistry
            If toolbar Is Nothing Then Return Me

            toolbar.ApplyCommandBarDefaultsInternal()

            Dim name As String = ResolveSourceName(sourceName, toolbar.Name)
            If String.IsNullOrWhiteSpace(name) Then name = "CommandBar"

            If Not HasActiveSurfaceLease(toolbar, MASCommandBindingSurfaceKind.CommandBar) Then
                Dim registryReference As New WeakReference(Me)
                Dim handler As EventHandler(Of MASToolbarButtonClickedEventArgs) =
                    Sub(sender As Object, e As MASToolbarButtonClickedEventArgs)
                        If e Is Nothing Then Return

                        Dim registry As MASCommandRegistry = TryCast(registryReference.Target, MASCommandRegistry)
                        If registry Is Nothing Then Return

                        Dim ignored As MASCommandInvocationResult = registry.TryExecute(e.Id, MASCommandBindingSurfaceKind.CommandBar, name, e.Control)
                    End Sub

                AddHandler toolbar.ButtonClicked, handler
                RememberSurfaceLease(New MASCommandSurfaceBindingLease(
                    String.Empty,
                    MASCommandBindingSurfaceKind.CommandBar,
                    name,
                    toolbar,
                    Sub(target As Object)
                        Dim targetToolbar As MASToolbar = TryCast(target, MASToolbar)
                        If targetToolbar IsNot Nothing Then
                            RemoveHandler targetToolbar.ButtonClicked, handler
                        End If
                    End Sub,
                    weakInvocationRoute:=True))
            End If

            Dim ids As String() = If(commandIds, New String() {})
            If ids.Length = 0 Then
                RememberBinding(String.Empty, MASCommandBindingSurfaceKind.CommandBar, name)
                RememberLifecycle(String.Empty, MASCommandBindingSurfaceKind.CommandBar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=True, weakInvocationRoute:=True)
            Else
                For Each commandId As String In ids
                    Dim definition As MASCommandDefinition = RequireDefinition(commandId)
                    toolbar.EnsureCommandButtonInternal(definition.CommandId, definition.Text, definition.Description, action:=Nothing, intent:=MASButtonIntent.Default)
                    RememberBinding(definition.CommandId, MASCommandBindingSurfaceKind.CommandBar, name)
                    RememberLifecycle(definition.CommandId, MASCommandBindingSurfaceKind.CommandBar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=True, weakInvocationRoute:=True)
                Next
            End If

            Return Me
        End Function

        Friend Function BindCommandPalette(palette As MASCommandPalette,
                                                 ParamArray commandIds() As String) As MASCommandRegistry
            Return BindCommandPalette(palette, String.Empty, commandIds)
        End Function

        Friend Function BindCommandPalette(palette As MASCommandPalette,
                                                 sourceName As String,
                                                 ParamArray commandIds() As String) As MASCommandRegistry
            If palette Is Nothing Then Return Me

            Dim name As String = ResolveSourceName(sourceName, palette.Name)
            If String.IsNullOrWhiteSpace(name) Then name = "CommandPalette"

            Dim ids As String() = If(commandIds, New String() {})
            If ids.Length = 0 Then
                RememberBinding(String.Empty, MASCommandBindingSurfaceKind.CommandPalette, name)
                RememberLifecycle(String.Empty, MASCommandBindingSurfaceKind.CommandPalette, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            Else
                For Each commandId As String In ids
                    Dim definition As MASCommandDefinition = RequireDefinition(commandId)
                    RememberBinding(definition.CommandId, MASCommandBindingSurfaceKind.CommandPalette, name)
                    RememberLifecycle(definition.CommandId, MASCommandBindingSurfaceKind.CommandPalette, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
                Next
            End If

            palette.AttachCommandRegistryInternal(Me, name, ids)
            Return Me
        End Function


        Friend Function BindMenuBar(menuBar As MASMenuBar,
                                    menuText As String,
                                    ParamArray commandIds() As String) As MASCommandRegistry
            Return BindMenuBar(menuBar, menuText, String.Empty, commandIds)
        End Function

        Friend Function BindMenuBar(menuBar As MASMenuBar,
                                    menuText As String,
                                    sourceName As String,
                                    ParamArray commandIds() As String) As MASCommandRegistry
            If menuBar Is Nothing Then Return Me

            Dim normalizedMenuText As String = If(menuText, String.Empty).Trim()
            If normalizedMenuText.Length = 0 Then normalizedMenuText = "Commands"

            Dim name As String = ResolveSourceName(sourceName, menuBar.Name)
            If String.IsNullOrWhiteSpace(name) Then name = "MenuBar"

            Dim registryReference As New WeakReference(Me)
            Dim ids As String() = If(commandIds, New String() {})

            If ids.Length = 0 Then
                RememberBinding(String.Empty, MASCommandBindingSurfaceKind.MenuBar, name)
                RememberLifecycle(String.Empty, MASCommandBindingSurfaceKind.MenuBar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
                Return Me
            End If

            menuBar.AddMenu(normalizedMenuText,
                Sub(builder As MASDropDownMenuBuilder)
                    If builder Is Nothing Then Return

                    For Each commandId As String In ids
                        Dim definition As MASCommandDefinition = RequireDefinition(commandId)
                        Dim id As String = definition.CommandId
                        Dim context As New MASCommandExecutionContext(id, MASCommandBindingSurfaceKind.MenuBar, name, Nothing)
                        Dim shortcutText As String = If(definition.HasShortcut, definition.Shortcut.DisplayText, String.Empty)

                        builder.AddAction(
                            text:=definition.Text,
                            commandId:=id,
                            handler:=Sub()
                                         Dim registry As MASCommandRegistry = TryCast(registryReference.Target, MASCommandRegistry)
                                         If registry Is Nothing Then Return

                                         Dim ignored As MASCommandInvocationResult = registry.TryExecute(id, MASCommandBindingSurfaceKind.MenuBar, name, Nothing)
                                     End Sub,
                            enabled:=definition.CanExecute(context),
                            shortcutText:=shortcutText)
                    Next
                End Sub)

            For Each commandId As String In ids
                Dim definition As MASCommandDefinition = RequireDefinition(commandId)
                RememberBinding(definition.CommandId, MASCommandBindingSurfaceKind.MenuBar, name)
                RememberLifecycle(definition.CommandId, MASCommandBindingSurfaceKind.MenuBar, name, MASCommandBindingLifecycleStatus.EvidenceOnly, canRelease:=False, weakInvocationRoute:=True)
            Next

            Return Me
        End Function

        Friend Function ReleaseButton(button As MASButton) As MASCommandRegistry
            If button Is Nothing Then Return Me

            ReleaseOwnedBinding(button, MASCommandBindingSurfaceKind.Button)
            Return Me
        End Function

        Friend Function ReleaseToolbar(toolbar As MASToolbar) As MASCommandRegistry
            If toolbar Is Nothing Then Return Me

            ReleaseOwnedBinding(toolbar, MASCommandBindingSurfaceKind.Toolbar)
            Return Me
        End Function

        Friend Function ReleaseCommandBar(toolbar As MASToolbar) As MASCommandRegistry
            If toolbar Is Nothing Then Return Me

            ReleaseOwnedBinding(toolbar, MASCommandBindingSurfaceKind.CommandBar)
            Return Me
        End Function

        Friend Sub ReleaseAllBindings()
            For Each lease As MASCommandSurfaceBindingLease In _surfaceLeases.ToArray()
                lease.Release()
            Next
        End Sub

        Friend Function ReleaseRegisteredCommand(commandId As String) As Boolean
            Dim id As String = NormalizeCommandId(commandId)
            If id.Length = 0 Then Return False

            Dim removed As Boolean = _commands.Remove(id)
            _commandOrder.RemoveAll(Function(existing) String.Equals(existing, id, StringComparison.OrdinalIgnoreCase))
            _bindings.RemoveAll(Function(binding) String.Equals(binding.CommandId, id, StringComparison.OrdinalIgnoreCase))
            _lifecycleRecords.RemoveAll(Function(record) String.Equals(record.CommandId, id, StringComparison.OrdinalIgnoreCase))
            Return removed
        End Function

        Friend Function TryHandleShortcut(keyData As Keys,
                                          Optional sourceName As String = "",
                                          Optional parameter As Object = Nothing) As MASCommandInvocationResult
            For Each id As String In _commandOrder
                Dim definition As MASCommandDefinition = _commands(id)
                If definition.HasShortcut AndAlso definition.Shortcut.Matches(keyData) Then
                    Return TryExecute(id, MASCommandBindingSurfaceKind.Shortcut, ResolveSourceName(sourceName, definition.Shortcut.DisplayText), parameter)
                End If
            Next

            Return MASCommandInvocationResult.NoShortcutMatch()
        End Function

        Friend Function TryExecute(commandId As String,
                                   Optional sourceKind As MASCommandBindingSurfaceKind = MASCommandBindingSurfaceKind.Programmatic,
                                   Optional sourceName As String = "",
                                   Optional parameter As Object = Nothing) As MASCommandInvocationResult
            Dim id As String = NormalizeCommandId(commandId)
            Dim definition As MASCommandDefinition = Nothing

            If Not _commands.TryGetValue(id, definition) Then
                Return MASCommandInvocationResult.Missing(id, sourceKind)
            End If

            Dim context As New MASCommandExecutionContext(id, sourceKind, sourceName, parameter)
            If Not definition.CanExecute(context) Then
                Return MASCommandInvocationResult.Disabled(id, sourceKind)
            End If

            Try
                definition.Execute(context)
                Return MASCommandInvocationResult.Success(id, sourceKind)
            Catch ex As Exception
                Return MASCommandInvocationResult.Failed(id, sourceKind, ex.Message)
            End Try
        End Function

        Friend Function CreateSnapshot() As MASCommandRegistrySnapshot
            Dim commandEntries As MASCommandCatalogEntry() = _commandOrder.
                Where(Function(id) _commands.ContainsKey(id)).
                Select(Function(id) _commands(id).ToCatalogEntry()).
                ToArray()

            Return New MASCommandRegistrySnapshot(commandEntries, _bindings.ToArray(), OfficialSystemName)
        End Function

        Friend Function CreateLifecycleSnapshot() As MASCommandRegistryLifecycleSnapshot
            Dim records As MASCommandBindingLifecycleRecord() = _lifecycleRecords.
                Concat(_surfaceLeases.Select(Function(surfaceLease) surfaceLease.ToRecord())).
                ToArray()

            Return New MASCommandRegistryLifecycleSnapshot(records)
        End Function

        Friend Shared Function NormalizeCommandId(value As String) As String
            Return If(value, String.Empty).Trim()
        End Function

        Private Function RequireDefinition(commandId As String) As MASCommandDefinition
            Dim definition As MASCommandDefinition = Nothing
            Dim id As String = NormalizeCommandId(commandId)
            If _commands.TryGetValue(id, definition) Then Return definition

            Throw New InvalidOperationException("Command is not registered: " & id)
        End Function

        Private Sub RememberBinding(commandId As String,
                                    surfaceKind As MASCommandBindingSurfaceKind,
                                    sourceName As String)
            Dim id As String = NormalizeCommandId(commandId)
            Dim source As String = If(sourceName, String.Empty)

            If _bindings.Any(Function(binding) String.Equals(binding.CommandId, id, StringComparison.OrdinalIgnoreCase) AndAlso
                                               binding.SurfaceKind = surfaceKind AndAlso
                                               String.Equals(binding.SourceName, source, StringComparison.Ordinal)) Then
                Return
            End If

            _bindings.Add(New MASCommandBindingRecord(id, surfaceKind, source))
        End Sub

        Private Sub RememberSurfaceLease(lease As MASCommandSurfaceBindingLease)
            If lease IsNot Nothing Then
                _surfaceLeases.Add(lease)
            End If
        End Sub

        Private Sub RememberLifecycle(commandId As String,
                                      surfaceKind As MASCommandBindingSurfaceKind,
                                      sourceName As String,
                                      status As MASCommandBindingLifecycleStatus,
                                      canRelease As Boolean,
                                      weakInvocationRoute As Boolean)
            Dim id As String = NormalizeCommandId(commandId)
            Dim source As String = If(sourceName, String.Empty)

            If _lifecycleRecords.Any(Function(record) String.Equals(record.CommandId, id, StringComparison.OrdinalIgnoreCase) AndAlso
                                                     record.SurfaceKind = surfaceKind AndAlso
                                                     String.Equals(record.SourceName, source, StringComparison.Ordinal) AndAlso
                                                     record.Status = status AndAlso
                                                     record.CanRelease = canRelease AndAlso
                                                     record.WeakInvocationRoute = weakInvocationRoute) Then
                Return
            End If

            _lifecycleRecords.Add(New MASCommandBindingLifecycleRecord(id, surfaceKind, source, status, canRelease, weakInvocationRoute))
        End Sub

        Private Function HasActiveSurfaceLease(target As Object,
                                               surfaceKind As MASCommandBindingSurfaceKind) As Boolean
            Return _surfaceLeases.Any(Function(surfaceLease) surfaceLease.IsActiveForTarget(target, surfaceKind))
        End Function

        Private Sub ReleaseOwnedBinding(target As Object,
                                        surfaceKind As MASCommandBindingSurfaceKind)
            If target Is Nothing Then Return

            For Each lease As MASCommandSurfaceBindingLease In _surfaceLeases.ToArray()
                If lease.IsActiveForTarget(target, surfaceKind) Then
                    lease.Release()
                End If
            Next
        End Sub

        Private Shared Function ResolveSourceName(preferred As String,
                                                  fallback As String) As String
            If Not String.IsNullOrWhiteSpace(preferred) Then Return preferred.Trim()
            Return If(fallback, String.Empty).Trim()
        End Function

    End Class

End Namespace
