using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using AIRuntime.Runtime.Agents;
using AIRuntime.Runtime.Agents.BuiltIn;
using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Providers;
using AIRuntime.Runtime.Providers.NousPortal;
using AIRuntime.Runtime.Providers.Nvidia;
using AIRuntime.Runtime.Providers.Ollama;
using AIRuntime.Runtime.Providers.OpenAI;
using AIRuntime.Runtime.Providers.OpenRouter;

using UnityEditor;


namespace AIRuntime.Editor
{
    /// <summary>
    /// Editor hosts in which a capability plugin can be offered.
    /// </summary>
    [Flags]
    public enum AIRuntimeEditorPluginHost
    {
        None = 0,
        ChatConsole = 1,
        Kanban = 2,
        All = ChatConsole | Kanban
    }


    /// <summary>
    /// Provider-neutral values supplied to an editor provider plugin.
    /// Plugin-specific settings can be added without changing callers by
    /// implementing a provider plugin that owns its own configuration source.
    /// </summary>
    public sealed class AIRuntimeEditorProviderSettings
    {
        public string ApiKey { get; set; }

        public string Model { get; set; }

        public bool SupportsImageInput { get; set; }

        public string BaseUrl { get; set; }

        public string HttpReferer { get; set; }

        public string ApplicationTitle { get; set; }
    }


    public interface IAIRuntimeEditorProviderPlugin
    {
        string Id { get; }

        string DisplayName { get; }

        bool RequiresApiKey { get; }

        string DefaultBaseUrl { get; }

        string DefaultModel { get; }

        IReadOnlyList<string> Aliases { get; }

        IProvider CreateProvider(
            AIRuntimeEditorProviderSettings settings);
    }


    /// <summary>
    /// Optional provider-owned editor configuration contract. Providers that
    /// do not implement it keep the conservative model/base-URL defaults.
    /// This keeps provider-specific validation and controls out of host views.
    /// </summary>
    public interface IAIRuntimeEditorProviderConfigurationPlugin
    {
        bool RequiresModel { get; }

        bool RequiresBaseUrl { get; }

        float MaximumAgentTemperature { get; }

        string ConfigurationWarning { get; }

        string AgentOptionsWarning { get; }

        void DrawAdditionalSettings(
            AIRuntimeEditorProviderSettings settings);

        void ValidateSettings(
            AIRuntimeEditorProviderSettings settings);
    }


    public interface IAIRuntimeEditorCapabilityPlugin
    {
        string Name { get; }

        string Label { get; }

        CapabilityPermission Permission { get; }

        AIRuntimeEditorPluginHost Hosts { get; }

        ICapability CreateCapability();
    }


    public sealed class AIRuntimeEditorCapabilityCreationContext
    {
        public AIRuntimeEditorCapabilityCreationContext(
            AIRuntimeEditorPluginHost host,
            IServiceProvider services)
        {
            Host = host;
            Services = services;
        }


        public AIRuntimeEditorPluginHost Host { get; }

        public IServiceProvider Services { get; }


        public T GetService<T>()
            where T : class
        {
            return Services?.GetService(
                       typeof(T)) as T;
        }
    }


    /// <summary>
    /// Optional factory for capabilities that consume run-scoped host
    /// services. The ordinary parameterless factory remains available for
    /// catalog inspection and hosts that do not provide that service.
    /// </summary>
    public interface IAIRuntimeEditorScopedCapabilityPlugin
    {
        ICapability CreateCapability(
            AIRuntimeEditorCapabilityCreationContext context);
    }


    public interface IAIRuntimeEditorAgentPlugin
    {
        string Id { get; }

        string DisplayName { get; }

        AgentDefinition CreateDefinition();
    }


    /// <summary>
    /// Open composition root for editor providers, capabilities, and agent
    /// definitions. Built-ins register through the same public contracts used
    /// by external packages.
    /// </summary>
    public static class AIRuntimeEditorPluginCatalog
    {
        private sealed class ProviderPlugin :
            IAIRuntimeEditorProviderPlugin,
            IAIRuntimeEditorProviderConfigurationPlugin
        {
            private readonly Func<AIRuntimeEditorProviderSettings, IProvider> _factory;
            private readonly Action<AIRuntimeEditorProviderSettings> _settingsDrawer;
            private readonly Action<AIRuntimeEditorProviderSettings> _validator;


            public ProviderPlugin(
                string id,
                string displayName,
                bool requiresApiKey,
                string defaultBaseUrl,
                string defaultModel,
                Func<AIRuntimeEditorProviderSettings, IProvider> factory,
                IEnumerable<string> aliases = null,
                bool requiresModel = true,
                bool requiresBaseUrl = true,
                float maximumAgentTemperature = 2f,
                string configurationWarning = "",
                string agentOptionsWarning = "",
                Action<AIRuntimeEditorProviderSettings> settingsDrawer = null,
                Action<AIRuntimeEditorProviderSettings> validator = null)
            {
                Id = id;
                DisplayName = displayName;
                RequiresApiKey = requiresApiKey;
                DefaultBaseUrl = defaultBaseUrl;
                DefaultModel = defaultModel;
                _factory = factory;
                Aliases = Array.AsReadOnly(
                    (aliases ??
                     Array.Empty<string>())
                    .ToArray());
                RequiresModel = requiresModel;
                RequiresBaseUrl = requiresBaseUrl;
                MaximumAgentTemperature = maximumAgentTemperature;
                ConfigurationWarning = configurationWarning ?? string.Empty;
                AgentOptionsWarning = agentOptionsWarning ?? string.Empty;
                _settingsDrawer = settingsDrawer;
                _validator = validator;
            }


            public string Id { get; }

            public string DisplayName { get; }

            public bool RequiresApiKey { get; }

            public string DefaultBaseUrl { get; }

            public string DefaultModel { get; }

            public IReadOnlyList<string> Aliases { get; }

            public bool RequiresModel { get; }

            public bool RequiresBaseUrl { get; }

            public float MaximumAgentTemperature { get; }

            public string ConfigurationWarning { get; }

            public string AgentOptionsWarning { get; }


            public void DrawAdditionalSettings(
                AIRuntimeEditorProviderSettings settings)
            {
                _settingsDrawer?.Invoke(settings);
            }


            public void ValidateSettings(
                AIRuntimeEditorProviderSettings settings)
            {
                _validator?.Invoke(settings);
            }


            public IProvider CreateProvider(
                AIRuntimeEditorProviderSettings settings)
            {
                IProvider provider = _factory(
                    settings ??
                    throw new ArgumentNullException(
                        nameof(settings)));
                if (provider is OpenAIProvider openAI)
                    openAI.SupportsImageInput = settings.SupportsImageInput;
                if (provider is OllamaProvider ollama)
                    ollama.SupportsImageInput = settings.SupportsImageInput;
                return provider;
            }
        }


        private sealed class CapabilityPlugin :
            IAIRuntimeEditorCapabilityPlugin,
            IAIRuntimeEditorScopedCapabilityPlugin
        {
            private readonly Func<ICapability> _factory;
            private readonly Func<AIRuntimeEditorCapabilityCreationContext, ICapability> _scopedFactory;


            public CapabilityPlugin(
                string name,
                string label,
                CapabilityPermission permission,
                AIRuntimeEditorPluginHost hosts,
                Func<ICapability> factory,
                Func<AIRuntimeEditorCapabilityCreationContext, ICapability> scopedFactory = null)
            {
                Name = name;
                Label = label;
                Permission = permission;
                Hosts = hosts;
                _factory = factory;
                _scopedFactory = scopedFactory;
            }


            public string Name { get; }

            public string Label { get; }

            public CapabilityPermission Permission { get; }

            public AIRuntimeEditorPluginHost Hosts { get; }


            public ICapability CreateCapability()
            {
                return _factory();
            }


            public ICapability CreateCapability(
                AIRuntimeEditorCapabilityCreationContext context)
            {
                return _scopedFactory != null
                    ? _scopedFactory(context)
                    : _factory();
            }
        }


        private sealed class AgentPlugin :
            IAIRuntimeEditorAgentPlugin
        {
            private readonly Func<AgentDefinition> _factory;


            public AgentPlugin(
                string id,
                string displayName,
                Func<AgentDefinition> factory)
            {
                Id = id;
                DisplayName = displayName;
                _factory = factory;
            }


            public string Id { get; }

            public string DisplayName { get; }


            public AgentDefinition CreateDefinition()
            {
                return _factory();
            }
        }


        private sealed class RegisteredProviderPlugin :
            IAIRuntimeEditorProviderPlugin,
            IAIRuntimeEditorProviderConfigurationPlugin
        {
            private readonly IAIRuntimeEditorProviderPlugin _inner;
            private readonly IAIRuntimeEditorProviderConfigurationPlugin _configuration;


            public RegisteredProviderPlugin(
                string id,
                IReadOnlyList<string> aliases,
                IAIRuntimeEditorProviderPlugin inner)
            {
                _inner = inner;
                _configuration =
                    inner as IAIRuntimeEditorProviderConfigurationPlugin;
                Id = id;
                DisplayName =
                    string.IsNullOrWhiteSpace(inner.DisplayName)
                        ? id
                        : inner.DisplayName.Trim();
                RequiresApiKey = inner.RequiresApiKey;
                DefaultBaseUrl = inner.DefaultBaseUrl ?? string.Empty;
                DefaultModel = inner.DefaultModel ?? string.Empty;
                Aliases = Array.AsReadOnly(
                    aliases?.ToArray() ??
                    Array.Empty<string>());
                RequiresModel =
                    _configuration?.RequiresModel ??
                    true;
                RequiresBaseUrl =
                    _configuration?.RequiresBaseUrl ??
                    true;
                MaximumAgentTemperature =
                    _configuration?.MaximumAgentTemperature > 0
                        ? _configuration.MaximumAgentTemperature
                        : 2f;
                ConfigurationWarning =
                    _configuration?.ConfigurationWarning ??
                    string.Empty;
                AgentOptionsWarning =
                    _configuration?.AgentOptionsWarning ??
                    string.Empty;
            }


            public string Id { get; }
            public string DisplayName { get; }
            public bool RequiresApiKey { get; }
            public string DefaultBaseUrl { get; }
            public string DefaultModel { get; }
            public IReadOnlyList<string> Aliases { get; }
            public bool RequiresModel { get; }
            public bool RequiresBaseUrl { get; }
            public float MaximumAgentTemperature { get; }
            public string ConfigurationWarning { get; }
            public string AgentOptionsWarning { get; }


            public IProvider CreateProvider(
                AIRuntimeEditorProviderSettings settings)
            {
                return _inner.CreateProvider(settings);
            }


            public void DrawAdditionalSettings(
                AIRuntimeEditorProviderSettings settings)
            {
                _configuration?.DrawAdditionalSettings(settings);
            }


            public void ValidateSettings(
                AIRuntimeEditorProviderSettings settings)
            {
                _configuration?.ValidateSettings(settings);
            }
        }


        private sealed class RegisteredCapabilityPlugin :
            IAIRuntimeEditorCapabilityPlugin,
            IAIRuntimeEditorScopedCapabilityPlugin
        {
            private readonly IAIRuntimeEditorCapabilityPlugin _inner;


            public RegisteredCapabilityPlugin(
                string name,
                IAIRuntimeEditorCapabilityPlugin inner)
            {
                _inner = inner;
                Name = name;
                Label =
                    string.IsNullOrWhiteSpace(inner.Label)
                        ? name
                        : inner.Label.Trim();
                Permission = inner.Permission;
                Hosts = inner.Hosts;
            }


            public string Name { get; }
            public string Label { get; }
            public CapabilityPermission Permission { get; }
            public AIRuntimeEditorPluginHost Hosts { get; }


            public ICapability CreateCapability()
            {
                return _inner.CreateCapability();
            }


            public ICapability CreateCapability(
                AIRuntimeEditorCapabilityCreationContext context)
            {
                return _inner is IAIRuntimeEditorScopedCapabilityPlugin scoped
                    ? scoped.CreateCapability(context)
                    : _inner.CreateCapability();
            }
        }


        private sealed class RegisteredAgentPlugin :
            IAIRuntimeEditorAgentPlugin
        {
            private readonly IAIRuntimeEditorAgentPlugin _inner;


            public RegisteredAgentPlugin(
                string id,
                IAIRuntimeEditorAgentPlugin inner)
            {
                _inner = inner;
                Id = id;
                DisplayName =
                    string.IsNullOrWhiteSpace(inner.DisplayName)
                        ? id
                        : inner.DisplayName.Trim();
            }


            public string Id { get; }
            public string DisplayName { get; }


            public AgentDefinition CreateDefinition()
            {
                return _inner.CreateDefinition();
            }
        }


        private static readonly object Gate =
            new object();

        private static readonly Dictionary<string, IAIRuntimeEditorProviderPlugin> Providers =
            new Dictionary<string, IAIRuntimeEditorProviderPlugin>(
                StringComparer.OrdinalIgnoreCase);

        private static readonly Dictionary<string, string> ProviderAliases =
            new Dictionary<string, string>(
                StringComparer.OrdinalIgnoreCase);

        private static readonly Dictionary<string, IAIRuntimeEditorCapabilityPlugin> Capabilities =
            new Dictionary<string, IAIRuntimeEditorCapabilityPlugin>(
                StringComparer.Ordinal);

        private static readonly Dictionary<string, IAIRuntimeEditorAgentPlugin> Agents =
            new Dictionary<string, IAIRuntimeEditorAgentPlugin>(
                StringComparer.OrdinalIgnoreCase);

        private static readonly Dictionary<string, string> AgentDefinitionIds =
            new Dictionary<string, string>(
                StringComparer.Ordinal);

        private static readonly Dictionary<string, string> AgentDefinitionIdByPluginId =
            new Dictionary<string, string>(
                StringComparer.OrdinalIgnoreCase);


        static AIRuntimeEditorPluginCatalog()
        {
            RegisterBuiltInProviders();
            RegisterBuiltInCapabilities();
            RegisterBuiltInAgents();
        }


        public static IDisposable RegisterProvider(
            IAIRuntimeEditorProviderPlugin plugin)
        {
            string pluginId =
                ValidatePluginId(
                plugin?.Id,
                nameof(plugin));

            string[] aliases =
                (plugin.Aliases ?? Array.Empty<string>())
                .Where(alias => !string.IsNullOrWhiteSpace(alias))
                .Select(NormalizeProviderAlias)
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();

            RegisteredProviderPlugin registeredPlugin =
                new RegisteredProviderPlugin(
                    pluginId,
                    aliases,
                    plugin);

            lock (Gate)
            {
                if (Providers.ContainsKey(pluginId) ||
                    ProviderAliases.ContainsKey(pluginId))
                {
                    throw new InvalidOperationException(
                        $"An editor provider plugin with id or alias '{pluginId}' is already registered.");
                }

                foreach (string alias in aliases)
                {
                    if (string.Equals(
                            alias,
                            pluginId,
                            StringComparison.OrdinalIgnoreCase))
                    {
                        throw new InvalidOperationException(
                            $"Editor provider alias '{alias}' duplicates its provider id.");
                    }

                    if (Providers.ContainsKey(alias) ||
                        ProviderAliases.ContainsKey(alias))
                    {
                        throw new InvalidOperationException(
                            $"Editor provider alias '{alias}' is already registered.");
                    }
                }

                Providers.Add(
                    pluginId,
                    registeredPlugin);

                foreach (string alias in aliases)
                {
                    ProviderAliases.Add(
                        alias,
                        pluginId);
                }
            }

            return new ProviderRegistration(
                pluginId,
                registeredPlugin);
        }


        private static void UnregisterProvider(
            string id,
            IAIRuntimeEditorProviderPlugin plugin)
        {
            lock (Gate)
            {
                if (!TryResolveProviderIdLocked(
                        id,
                        out string resolvedId) ||
                    !Providers.TryGetValue(
                        resolvedId,
                        out IAIRuntimeEditorProviderPlugin current) ||
                    !ReferenceEquals(
                        current,
                        plugin))
                {
                    return;
                }

                Providers.Remove(
                    resolvedId);

                string[] aliases =
                    ProviderAliases
                        .Where(pair => string.Equals(
                            pair.Value,
                            resolvedId,
                            StringComparison.OrdinalIgnoreCase))
                        .Select(pair => pair.Key)
                        .ToArray();

                foreach (string alias in aliases)
                {
                    ProviderAliases.Remove(alias);
                }

            }
        }


        public static IReadOnlyList<IAIRuntimeEditorProviderPlugin>
            GetProviderPlugins()
        {
            lock (Gate)
            {
                return Providers.Values
                    .OrderBy(plugin => plugin.DisplayName, StringComparer.OrdinalIgnoreCase)
                    .ThenBy(plugin => plugin.Id, StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }
        }


        public static bool TryGetProviderPlugin(
            string idOrAlias,
            out IAIRuntimeEditorProviderPlugin plugin)
        {
            lock (Gate)
            {
                if (TryResolveProviderIdLocked(
                        idOrAlias,
                        out string id))
                {
                    plugin = Providers[id];
                    return true;
                }

                plugin = null;
                return false;
            }
        }


        public static IAIRuntimeEditorProviderPlugin GetProviderPlugin(
            string idOrAlias)
        {
            if (TryGetProviderPlugin(
                    idOrAlias,
                    out IAIRuntimeEditorProviderPlugin plugin))
            {
                return plugin;
            }

            throw new InvalidOperationException(
                $"Unknown editor provider plugin '{idOrAlias}'.");
        }


        public static string NormalizeProviderId(
            string idOrAlias)
        {
            return GetProviderPlugin(
                    idOrAlias)
                .Id;
        }


        public static IProvider CreateProvider(
            string idOrAlias,
            AIRuntimeEditorProviderSettings settings)
        {
            IAIRuntimeEditorProviderPlugin plugin =
                GetProviderPlugin(
                    idOrAlias);

            return plugin.CreateProvider(
                       settings ??
                       throw new ArgumentNullException(
                           nameof(settings))) ??
                   throw new InvalidOperationException(
                       $"Editor provider plugin '{plugin.Id}' returned null.");
        }


        public static void ValidateProviderSettings(
            string idOrAlias,
            AIRuntimeEditorProviderSettings settings)
        {
            IAIRuntimeEditorProviderPlugin plugin =
                GetProviderPlugin(
                    idOrAlias);

            AIRuntimeEditorProviderSettings values =
                settings ??
                throw new ArgumentNullException(
                    nameof(settings));

            IAIRuntimeEditorProviderConfigurationPlugin configuration =
                plugin as IAIRuntimeEditorProviderConfigurationPlugin;

            if ((configuration?.RequiresModel ?? true) &&
                string.IsNullOrWhiteSpace(values.Model))
            {
                throw new InvalidOperationException(
                    $"Provider plugin '{plugin.Id}' requires a model name.");
            }

            if ((configuration?.RequiresBaseUrl ?? true) &&
                string.IsNullOrWhiteSpace(values.BaseUrl))
            {
                throw new InvalidOperationException(
                    $"Provider plugin '{plugin.Id}' requires a base URL.");
            }

            if (plugin.RequiresApiKey &&
                string.IsNullOrWhiteSpace(values.ApiKey))
            {
                throw new InvalidOperationException(
                    $"Provider plugin '{plugin.Id}' requires an API key.");
            }

            configuration?.ValidateSettings(
                values);
        }


        public static IDisposable RegisterCapability(
            IAIRuntimeEditorCapabilityPlugin plugin)
        {
            string pluginName =
                ValidatePluginId(
                plugin?.Name,
                nameof(plugin));

            AIRuntimeEditorPluginHost invalidHosts =
                plugin.Hosts &
                ~AIRuntimeEditorPluginHost.All;

            if (plugin.Hosts == AIRuntimeEditorPluginHost.None ||
                invalidHosts != AIRuntimeEditorPluginHost.None)
            {
                throw new ArgumentException(
                    $"Editor capability plugin '{pluginName}' must declare at least one supported host and cannot contain unknown host flags.",
                    nameof(plugin));
            }

            ICapability capability =
                plugin.CreateCapability() ??
                throw new InvalidOperationException(
                    $"Editor capability plugin '{pluginName}' returned null.");

            if (!string.Equals(
                    capability.Name,
                    pluginName,
                    StringComparison.Ordinal))
            {
                throw new InvalidOperationException(
                    $"Editor capability plugin id '{pluginName}' does not match capability name '{capability.Name}'.");
            }

            RegisteredCapabilityPlugin registeredPlugin =
                new RegisteredCapabilityPlugin(
                    pluginName,
                    plugin);

            lock (Gate)
            {
                if (Capabilities.ContainsKey(pluginName))
                {
                    throw new InvalidOperationException(
                        $"Editor capability plugin '{pluginName}' is already registered.");
                }

                Capabilities.Add(
                    pluginName,
                    registeredPlugin);
            }

            return new CapabilityRegistration(
                pluginName,
                registeredPlugin);
        }


        private static void UnregisterCapability(
            string name,
            IAIRuntimeEditorCapabilityPlugin plugin)
        {
            lock (Gate)
            {
                if (!string.IsNullOrWhiteSpace(name) &&
                    Capabilities.TryGetValue(
                        name,
                        out IAIRuntimeEditorCapabilityPlugin current) &&
                    ReferenceEquals(
                        current,
                        plugin))
                {
                    Capabilities.Remove(name);
                }
            }
        }


        public static IReadOnlyList<IAIRuntimeEditorCapabilityPlugin>
            GetCapabilityPlugins(
                AIRuntimeEditorPluginHost host = AIRuntimeEditorPluginHost.All)
        {
            lock (Gate)
            {
                return Capabilities.Values
                    .Where(plugin =>
                        host == AIRuntimeEditorPluginHost.All ||
                        (plugin.Hosts & host) != 0)
                    .OrderBy(plugin => plugin.Label, StringComparer.OrdinalIgnoreCase)
                    .ThenBy(plugin => plugin.Name, StringComparer.Ordinal)
                    .ToArray();
            }
        }


        public static ICapability CreateCapability(
            string name,
            AIRuntimeEditorPluginHost host,
            IServiceProvider services = null)
        {
            if (host != AIRuntimeEditorPluginHost.ChatConsole &&
                host != AIRuntimeEditorPluginHost.Kanban)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(host),
                    host,
                    "A concrete editor capability host is required.");
            }

            IAIRuntimeEditorCapabilityPlugin plugin;

            lock (Gate)
            {
                if (string.IsNullOrWhiteSpace(name) ||
                    !Capabilities.TryGetValue(
                        name.Trim(),
                        out plugin) ||
                    (plugin.Hosts & host) == 0)
                {
                    throw new InvalidOperationException(
                        $"Unknown editor capability plugin '{name}' for host '{host}'.");
                }
            }

            ICapability capability =
                plugin is IAIRuntimeEditorScopedCapabilityPlugin scoped
                    ? scoped.CreateCapability(
                        new AIRuntimeEditorCapabilityCreationContext(
                            host,
                            services))
                    : plugin.CreateCapability();

            return capability ??
                   throw new InvalidOperationException(
                       $"Editor capability plugin '{name}' returned null.");
        }


        public static IDisposable RegisterAgent(
            IAIRuntimeEditorAgentPlugin plugin)
        {
            string pluginId =
                ValidatePluginId(
                plugin?.Id,
                nameof(plugin));

            AgentDefinition definition =
                plugin.CreateDefinition() ??
                throw new InvalidOperationException(
                    $"Editor agent plugin '{pluginId}' returned null.");

            string definitionId =
                ValidatePluginId(
                    definition.Id,
                    nameof(plugin));

            RegisteredAgentPlugin registeredPlugin =
                new RegisteredAgentPlugin(
                    pluginId,
                    plugin);

            lock (Gate)
            {
                if (Agents.ContainsKey(pluginId))
                {
                    throw new InvalidOperationException(
                        $"Editor agent plugin '{pluginId}' is already registered.");
                }

                if (AgentDefinitionIds.TryGetValue(
                        definitionId,
                        out string existingPluginId))
                {
                    throw new InvalidOperationException(
                        $"Editor agent plugin '{pluginId}' creates agent id '{definitionId}', which is already provided by '{existingPluginId}'.");
                }

                Agents.Add(
                    pluginId,
                    registeredPlugin);

                AgentDefinitionIds.Add(
                    definitionId,
                    pluginId);

                AgentDefinitionIdByPluginId.Add(
                    pluginId,
                    definitionId);
            }

            return new AgentRegistration(
                pluginId,
                definitionId,
                registeredPlugin);
        }


        private static void UnregisterAgent(
            string id,
            string definitionId,
            IAIRuntimeEditorAgentPlugin plugin)
        {
            lock (Gate)
            {
                if (!string.IsNullOrWhiteSpace(id) &&
                    Agents.TryGetValue(
                        id,
                        out IAIRuntimeEditorAgentPlugin current) &&
                    ReferenceEquals(
                        current,
                        plugin))
                {
                    Agents.Remove(id);
                    AgentDefinitionIds.Remove(definitionId);
                    AgentDefinitionIdByPluginId.Remove(id);
                }
            }
        }


        public static IReadOnlyList<IAIRuntimeEditorAgentPlugin>
            GetAgentPlugins()
        {
            lock (Gate)
            {
                return Agents.Values
                    .OrderBy(plugin =>
                        string.Equals(plugin.Id, "Custom", StringComparison.OrdinalIgnoreCase)
                            ? 0
                            : 1)
                    .ThenBy(plugin => plugin.DisplayName, StringComparer.OrdinalIgnoreCase)
                    .ThenBy(plugin => plugin.Id, StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }
        }


        public static bool TryGetAgentPlugin(
            string id,
            out IAIRuntimeEditorAgentPlugin plugin)
        {
            lock (Gate)
            {
                return Agents.TryGetValue(
                    id?.Trim() ?? string.Empty,
                    out plugin);
            }
        }


        public static AgentDefinition CreateAgentDefinition(
            string id)
        {
            if (!TryGetAgentPlugin(
                    id,
                    out IAIRuntimeEditorAgentPlugin plugin))
            {
                throw new InvalidOperationException(
                    $"Unknown editor agent plugin '{id}'.");
            }

            AgentDefinition definition =
                plugin.CreateDefinition() ??
                throw new InvalidOperationException(
                    $"Editor agent plugin '{id}' returned null.");

            string expectedDefinitionId;

            lock (Gate)
            {
                if (!AgentDefinitionIdByPluginId.TryGetValue(
                        plugin.Id,
                        out expectedDefinitionId))
                {
                    throw new InvalidOperationException(
                        $"Editor agent plugin '{id}' is no longer registered.");
                }
            }

            if (!string.Equals(
                    definition.Id,
                    expectedDefinitionId,
                    StringComparison.Ordinal))
            {
                throw new InvalidOperationException(
                    $"Editor agent plugin '{id}' changed its agent id from '{expectedDefinitionId}' to '{definition.Id}'.");
            }

            return definition;
        }


        private static bool TryResolveProviderIdLocked(
            string idOrAlias,
            out string id)
        {
            string value =
                idOrAlias?.Trim() ??
                string.Empty;

            if (Providers.ContainsKey(value))
            {
                id = value;
                return true;
            }

            return ProviderAliases.TryGetValue(
                value,
                out id);
        }


        private static string ValidatePluginId(
            string id,
            string parameterName)
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentException(
                    "Plugin id cannot be empty.",
                    parameterName);
            }

            string normalized =
                id.Trim();

            if (!string.Equals(
                    id,
                    normalized,
                    StringComparison.Ordinal))
            {
                throw new ArgumentException(
                    "Plugin ids cannot contain leading or trailing whitespace.",
                    parameterName);
            }

            return normalized;
        }


        private static string NormalizeProviderAlias(
            string alias)
        {
            string normalized =
                alias.Trim();

            if (!string.Equals(
                    alias,
                    normalized,
                    StringComparison.Ordinal))
            {
                throw new ArgumentException(
                    "Provider aliases cannot contain leading or trailing whitespace.",
                    nameof(alias));
            }

            return normalized;
        }


        private sealed class ProviderRegistration :
            IDisposable
        {
            private readonly string _id;
            private IAIRuntimeEditorProviderPlugin _plugin;


            public ProviderRegistration(
                string id,
                IAIRuntimeEditorProviderPlugin plugin)
            {
                _id = id;
                _plugin = plugin;
            }


            public void Dispose()
            {
                IAIRuntimeEditorProviderPlugin plugin =
                    Interlocked.Exchange(
                        ref _plugin,
                        null);

                if (plugin != null)
                {
                    UnregisterProvider(
                        _id,
                        plugin);
                }
            }
        }


        private sealed class CapabilityRegistration :
            IDisposable
        {
            private readonly string _name;
            private IAIRuntimeEditorCapabilityPlugin _plugin;


            public CapabilityRegistration(
                string name,
                IAIRuntimeEditorCapabilityPlugin plugin)
            {
                _name = name;
                _plugin = plugin;
            }


            public void Dispose()
            {
                IAIRuntimeEditorCapabilityPlugin plugin =
                    Interlocked.Exchange(
                        ref _plugin,
                        null);

                if (plugin != null)
                {
                    UnregisterCapability(
                        _name,
                        plugin);
                }
            }
        }


        private sealed class AgentRegistration :
            IDisposable
        {
            private readonly string _id;
            private readonly string _definitionId;
            private IAIRuntimeEditorAgentPlugin _plugin;


            public AgentRegistration(
                string id,
                string definitionId,
                IAIRuntimeEditorAgentPlugin plugin)
            {
                _id = id;
                _definitionId = definitionId;
                _plugin = plugin;
            }


            public void Dispose()
            {
                IAIRuntimeEditorAgentPlugin plugin =
                    Interlocked.Exchange(
                        ref _plugin,
                        null);

                if (plugin != null)
                {
                    UnregisterAgent(
                        _id,
                        _definitionId,
                        plugin);
                }
            }
        }


        private static void RegisterBuiltInProviders()
        {
            RegisterProvider(
                new ProviderPlugin(
                    "Ollama",
                    "Ollama",
                    false,
                    "http://localhost:11434/v1",
                    "qwen2.5:7b-instruct",
                    settings => new OllamaProvider(
                        new OllamaClient(
                            settings.BaseUrl,
                            settings.Model)),
                    configurationWarning:
                        "The Ollama provider currently logs raw request and response bodies to the Unity Console.",
                    agentOptionsWarning:
                        "The current Ollama provider does not implement provider request options, so temperature and maximum tokens are retained in the agent profile but are not sent to Ollama."));

            RegisterProvider(
                new ProviderPlugin(
                    "OpenAI",
                    "OpenAI",
                    true,
                    OpenAIClient.DefaultBaseUrl,
                    OpenAIClient.DefaultModel,
                    settings => new OpenAIProvider(
                        new OpenAIClient(
                            settings.ApiKey,
                            settings.Model,
                            settings.BaseUrl))));

            RegisterProvider(
                new ProviderPlugin(
                    "NousPortal",
                    "Nous Portal",
                    true,
                    NousPortalProvider.DefaultBaseUrl,
                    string.Empty,
                    settings => new NousPortalProvider(
                        settings.ApiKey,
                        settings.Model,
                        baseUrl: settings.BaseUrl),
                    aliases: new[]
                    {
                        "Nous Portal"
                    }));

            RegisterProvider(
                new ProviderPlugin(
                    "OpenRouter",
                    "OpenRouter",
                    true,
                    OpenRouterProvider.DefaultBaseUrl,
                    string.Empty,
                    settings => new OpenRouterProvider(
                        settings.ApiKey,
                        settings.Model,
                        settings.HttpReferer,
                        settings.ApplicationTitle,
                        baseUrl: settings.BaseUrl),
                    aliases: new[]
                    {
                        "Open Router"
                    },
                    settingsDrawer: settings =>
                    {
                        settings.HttpReferer =
                            EditorGUILayout.TextField(
                                "HTTP referer",
                                settings.HttpReferer);

                        settings.ApplicationTitle =
                            EditorGUILayout.TextField(
                                "Application title",
                                settings.ApplicationTitle);
                    }));

            RegisterProvider(
                new ProviderPlugin(
                    AIRuntimeEditorProviderNames.NvidiaDeveloper,
                    "NVIDIA Developer",
                    true,
                    NvidiaDeveloperProvider.DefaultBaseUrl,
                    string.Empty,
                    settings => new NvidiaDeveloperProvider(
                        settings.ApiKey,
                        settings.Model,
                        baseUrl: settings.BaseUrl),
                    aliases: new[]
                    {
                        "NVIDIA",
                        "Nvidia",
                        "NVIDIA Developer"
                    },
                    maximumAgentTemperature: 1f));
        }


        private static void RegisterBuiltInCapabilities()
        {
            RegisterCapabilityPlugin("read_file", "Read file", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new ReadFileCapability());
            RegisterCapabilityPlugin("file_exists", "File exists", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new FileExistsCapability());
            RegisterCapabilityPlugin("get_file_info", "Get file information", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetFileInfoCapability());
            RegisterCapabilityPlugin("list_directory", "List directory", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new ListDirectoryCapability());
            RegisterCapabilityPlugin("list_directory_tree", "List directory tree", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new ListDirectoryTreeCapability());
            RegisterCapabilityPlugin("get_editor_selection", "Get Unity Editor selection", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetEditorSelectionCapability());
            RegisterCapabilityPlugin("create_editor_selection", "Create Unity Editor selection", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateEditorSelectionCapability());
            RegisterCapabilityPlugin("inspect_editor_component", "Inspect Unity component", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectEditorComponentCapability());
            RegisterCapabilityPlugin("get_serialized_properties", "Get serialized properties", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetSerializedPropertiesCapability());
            RegisterCapabilityPlugin("set_serialized_property", "Set serialized property", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new SetSerializedPropertyCapability());
            RegisterCapabilityPlugin("create_material", "Create Unity material", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateMaterialCapability());
            RegisterCapabilityPlugin("inspect_game_object", "Inspect GameObject", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectGameObjectCapability());
            RegisterCapabilityPlugin("get_hierarchy_tree", "Get hierarchy tree", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetHierarchyTreeCapability());
            RegisterCapabilityPlugin("find_game_objects", "Find GameObjects", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new FindGameObjectsCapability());
            RegisterCapabilityPlugin("get_console_logs", "Get Unity console logs", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetConsoleLogsCapability());
            RegisterCapabilityPlugin("get_compilation_errors", "Get compilation errors", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetCompilationErrorsCapability());
            RegisterCapabilityPlugin("run_edit_mode_tests", "Run EditMode tests", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new RunEditModeTestsCapability());
            RegisterCapabilityPlugin("run_play_mode_tests", "Run PlayMode tests", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new RunPlayModeTestsCapability());
            RegisterScopedCapabilityPlugin("inspect_gameplay_readiness", "Inspect gameplay readiness", CapabilityPermission.Write,
                AIRuntimeEditorPluginHost.All, () => new InspectGameplayReadinessCapability(),
                context => new InspectGameplayReadinessCapability(context.GetService<IKanbanTaskExecutionServices>(), context.GetService<IKanbanRunIdentity>()));
            RegisterCapabilityPlugin("inspect_scene", "Inspect Unity scene", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectSceneCapability());
            RegisterCapabilityPlugin("create_editor_scene", "Create Unity scene", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateEditorSceneCapability());
            RegisterCapabilityPlugin("create_game_object", "Create Unity GameObject", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateGameObjectCapability());
            RegisterCapabilityPlugin("save_editor_scene", "Save Unity scene", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new SaveEditorSceneCapability());
            RegisterCapabilityPlugin("create_prefab", "Create or update Unity prefab", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreatePrefabCapability());
            RegisterCapabilityPlugin("instantiate_prefab", "Instantiate Unity prefab", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new InstantiatePrefabCapability());
            RegisterCapabilityPlugin("inspect_prefab", "Inspect Unity prefab", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectPrefabCapability());
            RegisterCapabilityPlugin("update_game_object", "Update Unity GameObject", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new UpdateGameObjectCapability());
            RegisterCapabilityPlugin("duplicate_game_object", "Duplicate Unity GameObject", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new DuplicateGameObjectCapability());
            RegisterCapabilityPlugin("destroy_game_object", "Destroy Unity GameObject", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new DestroyGameObjectCapability());
            RegisterCapabilityPlugin("configure_project_settings", "Configure Unity project settings", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new ConfigureProjectSettingsCapability());
            RegisterCapabilityPlugin("search_workspace", "Search workspace text", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new SearchWorkspaceCapability());
            RegisterCapabilityPlugin("replace_text", "Replace exact file text", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new ReplaceTextCapability());
            RegisterCapabilityPlugin("list_capabilities", "List installed capabilities", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new ListCapabilitiesCapability());
            RegisterCapabilityPlugin("request_capability", "Request missing capability", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new RequestCapabilityCapability());
            RegisterCapabilityPlugin("open_editor_scene", "Open Unity scene", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new OpenEditorSceneCapability());
            RegisterCapabilityPlugin("close_editor_scene", "Close Unity scene", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new CloseEditorSceneCapability());
            RegisterCapabilityPlugin("apply_prefab_overrides", "Apply prefab overrides", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new ApplyPrefabOverridesCapability());
            RegisterCapabilityPlugin("revert_or_unpack_prefab", "Revert or unpack prefab", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new RevertOrUnpackPrefabCapability());
            RegisterScopedCapabilityPlugin(
                "control_play_mode",
                "Control Unity Play Mode",
                CapabilityPermission.Write,
                AIRuntimeEditorPluginHost.All,
                () => new ControlPlayModeCapability(),
                context => new ControlPlayModeCapability(
                    context.GetService<IKanbanRunIdentity>()));
            RegisterCapabilityPlugin("wait_for_editor_frames", "Wait for Editor frames", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new WaitForEditorFramesCapability());
            RegisterCapabilityPlugin("capture_camera_view", "Capture Camera view", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CaptureCameraViewCapability());
            RegisterCapabilityPlugin("capture_game_view", "Capture Game view", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CaptureGameViewCapability());
            RegisterCapabilityPlugin("build_player", "Build Unity player", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new BuildPlayerCapability());
            RegisterCapabilityPlugin("create_scriptable_object", "Create ScriptableObject asset", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateScriptableObjectCapability());
            RegisterCapabilityPlugin("list_unity_packages", "List Unity packages", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new ListUnityPackagesCapability());
            RegisterCapabilityPlugin("run_process", "Run external process", CapabilityPermission.Process, AIRuntimeEditorPluginHost.All, () => new RunProcessCapability());
            RegisterCapabilityPlugin("inspect_asset_importer", "Inspect asset importer", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectAssetImporterCapability());
            RegisterCapabilityPlugin("set_asset_importer_property", "Set asset importer property", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new SetAssetImporterPropertyCapability());
            RegisterCapabilityPlugin("find_unity_assets", "Find Unity assets", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new FindUnityAssetsCapability());
            RegisterCapabilityPlugin("inspect_unity_asset", "Inspect Unity asset", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new InspectUnityAssetCapability());
            RegisterCapabilityPlugin("get_unity_asset_referencers", "Get Unity asset referencers", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetUnityAssetReferencersCapability());
            RegisterCapabilityPlugin("move_unity_asset", "Move Unity asset", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new MoveUnityAssetCapability());
            RegisterCapabilityPlugin("copy_unity_asset", "Copy Unity asset", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CopyUnityAssetCapability());
            RegisterCapabilityPlugin("delete_unity_asset", "Delete Unity asset", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new DeleteUnityAssetCapability());
            RegisterCapabilityPlugin("refresh_unity_assets", "Refresh Unity assets", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new RefreshUnityAssetsCapability());
            RegisterCapabilityPlugin("get_unity_operation", "Get Unity operation", CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.All, () => new GetUnityOperationCapability());
            RegisterCapabilityPlugin("edit_serialized_collection", "Edit serialized collection", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new EditSerializedCollectionCapability());
            RegisterCapabilityPlugin("add_editor_component", "Add Unity component", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new AddEditorComponentCapability());
            RegisterCapabilityPlugin("remove_editor_component", "Remove Unity component", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new RemoveEditorComponentCapability());
            RegisterCapabilityPlugin("write_file", "Write file", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new WriteFileCapability());
            RegisterCapabilityPlugin("append_file", "Append file", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new AppendFileCapability());
            RegisterCapabilityPlugin("copy_file", "Copy file", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CopyFileCapability());
            RegisterCapabilityPlugin("create_directory", "Create directory", CapabilityPermission.Write, AIRuntimeEditorPluginHost.All, () => new CreateDirectoryCapability());
            RegisterCapabilityPlugin("move_file", "Move file", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new MoveFileCapability());
            RegisterCapabilityPlugin("delete_file", "Delete file", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new DeleteFileCapability());
            RegisterCapabilityPlugin("delete_directory", "Delete directory", CapabilityPermission.Destructive, AIRuntimeEditorPluginHost.All, () => new DeleteDirectoryCapability());
            RegisterScopedCapabilityPlugin("save_kanban_checkpoint", "Save Kanban checkpoint",
                CapabilityPermission.Write, AIRuntimeEditorPluginHost.Kanban,
                () => new SaveKanbanCheckpointCapability(),
                context => new SaveKanbanCheckpointCapability(context.GetService<IKanbanCardMemoryServices>()));
            RegisterScopedCapabilityPlugin("read_kanban_memory", "Read Kanban memory",
                CapabilityPermission.ReadOnly, AIRuntimeEditorPluginHost.Kanban,
                () => new ReadKanbanMemoryCapability(),
                context => new ReadKanbanMemoryCapability(context.GetService<IKanbanCardMemoryServices>()));
            RegisterScopedCapabilityPlugin(
                "get_kanban_task_context",
                "Get Kanban task context",
                CapabilityPermission.ReadOnly,
                AIRuntimeEditorPluginHost.Kanban,
                () => new GetKanbanTaskContextCapability(),
                context => new GetKanbanTaskContextCapability(
                    context.GetService<IKanbanTaskExecutionServices>()));
            RegisterScopedCapabilityPlugin(
                "create_kanban_child_task",
                "Create Kanban child task",
                CapabilityPermission.Write,
                AIRuntimeEditorPluginHost.Kanban,
                () => new CreateKanbanChildTaskCapability(),
                context => new CreateKanbanChildTaskCapability(
                    context.GetService<IKanbanTaskExecutionServices>()));
            RegisterScopedCapabilityPlugin(
                "commit_kanban_plan",
                "Commit Kanban plan",
                CapabilityPermission.Write,
                AIRuntimeEditorPluginHost.Kanban,
                () => new CommitKanbanPlanCapability(),
                context => new CommitKanbanPlanCapability(
                    context.GetService<IKanbanTaskExecutionServices>()));
        }


        private static void RegisterCapabilityPlugin(
            string name,
            string label,
            CapabilityPermission permission,
            AIRuntimeEditorPluginHost hosts,
            Func<ICapability> factory)
        {
            RegisterCapability(
                new CapabilityPlugin(
                    name,
                    label,
                    permission,
                    hosts,
                    factory));
        }


        private static void RegisterScopedCapabilityPlugin(
            string name,
            string label,
            CapabilityPermission permission,
            AIRuntimeEditorPluginHost hosts,
            Func<ICapability> fallbackFactory,
            Func<AIRuntimeEditorCapabilityCreationContext, ICapability> scopedFactory)
        {
            RegisterCapability(
                new CapabilityPlugin(
                    name,
                    label,
                    permission,
                    hosts,
                    fallbackFactory,
                    scopedFactory));
        }


        private static void RegisterBuiltInAgents()
        {
            RegisterAgent(
                new AgentPlugin(
                    "Custom",
                    "Custom",
                    () => new AgentDefinition(
                        "EditorAgent",
                        "Editor Agent",
                        "Interactive agent configured in the AIRuntime chat console.",
                        "You are a helpful assistant working inside the Unity Editor.")));

            RegisterAgent(new AgentPlugin("Architect", "Architect", ArchitectAgentDefinition.Create));
            RegisterAgent(new AgentPlugin("Developer", "Developer", DeveloperAgentDefinition.Create));
            RegisterAgent(new AgentPlugin("Reviewer", "Reviewer", ReviewAgentDefinition.Create));
            RegisterAgent(new AgentPlugin("Testing", "Testing", TestingAgentDefinition.Create));
        }
    }
}
