// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.Reflection;

namespace System.ComponentModel.Design;

/// <summary>
///  This is the main hosting object. <see cref="DesignerHost"/> implements services and interfaces specific to the
///  design time <see cref="IContainer"/> object. The services this class implements are generally non-removable (they
///  work as a unit so removing them would break things).
/// </summary>
internal sealed partial class DesignerHost : Container, IDesignerLoaderHost2, IDesignerHostTransactionState, IComponentChangeService, IReflect
{
    // State flags for the state of the designer host
    // Designer is currently loading from the loader host.
    private static readonly int s_stateLoading = BitVector32.CreateMask();
    // Designer is currently unloading.
    private static readonly int s_stateUnloading = BitVector32.CreateMask(s_stateLoading);
    // A transaction is in the process of being Canceled or Committed.
    private static readonly int s_stateIsClosingTransaction = BitVector32.CreateMask(s_stateUnloading);

    private static readonly Type[] s_defaultServices = [typeof(IDesignerHost), typeof(IContainer), typeof(IComponentChangeService), typeof(IDesignerLoaderHost2)];

    // IDesignerHost events
    private static readonly object s_eventActivated = new(); // Designer has been activated
    private static readonly object s_eventDeactivated = new(); // Designer has been deactivated
    private static readonly object s_eventLoadComplete = new(); // Loading has been completed
    private static readonly object s_eventTransactionClosed = new(); // The last transaction has been closed
    private static readonly object s_eventTransactionClosing = new(); // The last transaction is about to be closed
    private static readonly object s_eventTransactionOpened = new(); // The first transaction has been opened
    private static readonly object s_eventTransactionOpening = new(); // The first transaction is about to be opened

    // IComponentChangeService events
    private static readonly object s_eventComponentAdding = new(); // A component is about to be added to the container
    private static readonly object s_eventComponentAdded = new(); // A component was just added to the container
    private static readonly object s_eventComponentChanging = new(); // A component is about to be changed
    private static readonly object s_eventComponentChanged = new(); // A component has changed
    // A component is about to be removed from the container
    private static readonly object s_eventComponentRemoving = new();
    private static readonly object s_eventComponentRemoved = new(); // A component has been removed from the container
    private static readonly object s_eventComponentRename = new(); // A component has been renamed

    // Member variables
    private BitVector32 _state; // state for this host
    private DesignSurface? _surface; // the owning designer surface.
    private string? _newComponentName; // transient value indicating the name of a component that is being created
    private Stack<DesignerTransaction>? _transactions; // stack of transactions
    private IComponent? _rootComponent; // the root of our design
    private string? _rootComponentClassName; // class name of the root of our design
    private readonly Dictionary<IComponent, IDesigner> _designers;  // designer -> component mapping
    private readonly EventHandlerList _events; // event list
    private DesignerLoader? _loader; // the loader that loads our designers
    private List<string>? _savedSelection; // set of selected components names saved across reloads
    private HostDesigntimeLicenseContext? _licenseCtx;
    private IDesignerEventService? _designerEventService;
    private static readonly object s_selfLock = new();
    private bool _ignoreErrorsDuringReload;
    private TypeDescriptionProviderService? _typeService;
    private bool _typeServiceChecked;

    public DesignerHost(DesignSurface surface)
    {
        _surface = surface;
        _state = default;
        _designers = [];
        _events = new EventHandlerList();

        // Add the relevant services. We try to add these as "fixed" services. A fixed service cannot be removed by
        // the user. The reason for this is that each of these services depends on each other, so you can't really
        // remove and replace just one of them. If we can't get our own service container that supports fixed services,
        // we add these as regular services.

        if (this.TryGetService(out DesignSurfaceServiceContainer? dsc))
        {
            foreach (Type t in s_defaultServices)
            {
                dsc.AddFixedService(t, this);
            }
        }
        else
        {
            if (this.TryGetService(out IServiceContainer? sc))
            {
                foreach (Type t in s_defaultServices)
                {
                    sc.AddService(t, this);
                }
            }
            else
            {
                Debug.Fail("DesignerHost: Ctor needs a service provider that provides IServiceContainer");
            }
        }
    }

    [MemberNotNull(nameof(_licenseCtx))]
    internal HostDesigntimeLicenseContext LicenseContext => _licenseCtx ??= new HostDesigntimeLicenseContext(this);

    // Internal flag which is used to track when we are in the process of committing or canceling a transaction.
    internal bool IsClosingTransaction
    {
        get { return _state[s_stateIsClosingTransaction]; }
        set { _state[s_stateIsClosingTransaction] = value; }
    }

    bool IDesignerHostTransactionState.IsClosingTransaction => IsClosingTransaction;

    /// <summary>
    ///  Override of Container.Add
    /// </summary>
    public override void Add(IComponent? component, string? name)
    {
        if (!_typeServiceChecked)
        {
            this.TryGetService(out _typeService);
            _typeServiceChecked = true;
        }

        if (component is null)
        {
            return;
        }

        // TypeDescriptionProviderService is attached at design time only
        if (_typeService is not null)
        {
            // Check for the attribute that VsTargetFrameworkProvider injects on reflection types to see
            // if VsTargetFrameworkProvider is already attached.
            Type type = TypeDescriptor.GetProvider(component).GetReflectionType(typeof(object));
            if (!type.IsDefined(typeof(ProjectTargetFrameworkAttribute), false))
            {
                TypeDescriptionProvider typeProvider = _typeService.GetProvider(component);
                if (typeProvider is not null)
                {
                    TypeDescriptor.AddProvider(typeProvider, component);
                }
            }
        }

        PerformAdd(component, name);
    }

    private void PerformAdd(IComponent component, string? name)
    {
        if (!AddToContainerPreProcess(component, name, this))
        {
            return;
        }

        // Site creation fabricates a name for this component.
        base.Add(component, name);

        try
        {
            AddToContainerPostProcess(component, this);
        }
        catch (Exception t)
        {
            if (t != CheckoutException.Canceled)
            {
                Remove(component);
            }

            throw;
        }
    }

    /// <summary>
    ///  We support adding to either our main IDesignerHost container or to a private per-site container for nested
    ///  objects. This code is the stock add code that creates a designer, etc. See Add (above) for an example of how
    ///  to call this correctly. This method is called before the component is actually added. It returns true if the
    ///  component can be added to this container or false if the add should not occur (because the component may
    ///  already be in this container, for example.) It may also throw if adding this component is illegal.
    /// </summary>
    internal bool AddToContainerPreProcess(IComponent component, string? name, IContainer containerToAddTo)
    {
        ArgumentNullException.ThrowIfNull(component);

        // We should never add anything while we're unloading.
        if (_state[s_stateUnloading])
        {
            throw new InvalidOperationException(SR.DesignerHostUnloading)
            {
                HelpLink = SR.DesignerHostUnloading
            };
        }

        // Make sure we're not adding an instance of the root component to itself.
        if (_rootComponent is not null && string.Equals(component.GetType().FullName, _rootComponentClassName, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(string.Format(
                SR.DesignerHostCyclicAdd,
                component.GetType().FullName,
                _rootComponentClassName))
            {
                HelpLink = SR.DesignerHostCyclicAdd
            };
        }

        ISite? existingSite = component.Site;

        // If the component is already in our container, we just rename.
        if (existingSite is not null && existingSite.Container == this)
        {
            if (name is not null)
            {
                existingSite.Name = name;
            }

            return false;
        }

        // Raise an adding event for our container if the container is us.
        ComponentEventArgs ce = new(component);
        (_events[s_eventComponentAdding] as ComponentEventHandler)?.Invoke(containerToAddTo, ce);
        return true;
    }

    /// <summary>
    ///  We support adding to either our main IDesignerHost container or to a private per-site container for nested
    ///  objects. This code is the stock add code that creates a designer, etc. See Add (above) for an example of how
    ///  to call this correctly.
    /// </summary>
    internal void AddToContainerPostProcess(IComponent component, IContainer containerToAddTo)
    {
        // Now that we've added, check to see if this is an extender provider. If it is, add it to our extender provider
        // service so it is available.
        if (component is IExtenderProvider extenderComponent
            && !TypeDescriptor.GetAttributes(extenderComponent).Contains(InheritanceAttribute.InheritedReadOnly)
            && this.TryGetService(out IExtenderProviderService? eps))
        {
            eps.AddExtenderProvider(extenderComponent);
        }

        IDesigner? designer;

        // Is this the first component the loader has created? If so, then it must be the root component (by definition)
        // so we will expect there to be a root designer associated with the component.
        // Otherwise, we search for a normal designer, which can be optionally provided.
        if (_rootComponent is null)
        {
            designer = _surface!.CreateDesigner(component, true) as IRootDesigner ??
                throw new InvalidOperationException(string.Format(SR.DesignerHostNoTopLevelDesigner, component.GetType().FullName))
                {
                    HelpLink = SR.DesignerHostNoTopLevelDesigner
                };
            _rootComponent = component;

            // Check and see if anyone has set the class name of the root component. We default to the component name.
            _rootComponentClassName ??= component.Site!.Name;
        }
        else
        {
            designer = _surface!.CreateDesigner(component, false);
        }

        if (designer is not null)
        {
            // The presence of a designer in this table allows the designer to filter the component's properties, which
            // is often needed during designer initialization. So, we stuff it in the table first, initialize, and if
            // it throws we remove it from the table.
            _designers[component] = designer;
            try
            {
                designer.Initialize(component);
                if (designer.Component is null)
                {
                    throw new InvalidOperationException(SR.DesignerHostDesignerNeedsComponent);
                }
            }
            catch
            {
                _designers.Remove(component);
                throw;
            }

            // Designers can also implement IExtenderProvider.
            if (designer is IExtenderProvider extenderProvider)
            {
                if (this.TryGetService(out eps))
                {
                    eps.AddExtenderProvider(extenderProvider);
                }
            }
        }

        // The component has been added. Note that it is tempting to move this above the designer because the designer
        // will never need to know that its own component just got added, but this would be bad because the designer is
        // needed to extract shadowed properties from the component.
        ComponentEventArgs ce = new(component);
        (_events[s_eventComponentAdded] as ComponentEventHandler)?.Invoke(containerToAddTo, ce);
    }

    /// <summary>
    ///  Called by DesignerSurface to begin loading the designer.
    /// </summary>
    internal void BeginLoad(DesignerLoader loader)
    {
        if (_loader is not null && _loader != loader)
        {
            throw new InvalidOperationException(SR.DesignerHostLoaderSpecified)
            {
                HelpLink = SR.DesignerHostLoaderSpecified
            };
        }

        bool reloading = _loader is not null;
        _loader = loader;

        if (!reloading)
        {
            if (loader is IExtenderProvider extenderProvider && this.TryGetService(out IExtenderProviderService? eps))
            {
                eps.AddExtenderProvider(extenderProvider);
            }

            if (this.TryGetService(out IDesignerEventService? des))
            {
                des.ActiveDesignerChanged += OnActiveDesignerChanged;
                _designerEventService = des;
            }
        }

        _state[s_stateLoading] = true;
        _surface!.OnLoading();

        try
        {
            _loader?.BeginLoad(this);
        }
        catch (Exception e)
        {
            if (e is TargetInvocationException && e.InnerException is { } inner)
            {
                e = inner;
            }

            string message = e.Message;

            // We must handle the case of an exception with no message.
            if (string.IsNullOrEmpty(message))
            {
                e = new InvalidOperationException(string.Format(SR.DesignSurfaceFatalError, e), e);
            }

            // Loader blew up. Add this exception to our error list.
            ((IDesignerLoaderHost)this).EndLoad(string.Empty, successful: false, new object[] { e });
        }

        if (_designerEventService is null)
        {
            // If there is no designer event service, make this designer the currently active designer.
            // It will remain active.
            OnActiveDesignerChanged(sender: null, new ActiveDesignerEventArgs(oldDesigner: null, this));
        }
    }

    /// <summary>
    ///  Override of CreateSite. We create a custom site here, called Site, which is an inner class of DesignerHost.
    ///  DesignerSite contains an instance of the designer for the component.
    /// </summary>
    /// <param name="component">The component to create the site for.</param>
    /// <param name="name">The name of the component. If no name is provided this will fabricate a name for you.</param>
    /// <returns>The newly created site.</returns>
    protected override ISite CreateSite(IComponent component, string? name)
    {
        Debug.Assert(component is not null, "Caller should have guarded against a null component");

        // We need to handle the case where a component's ctor adds itself to the container. We don't want to do the
        // work of creating a name, and then immediately renaming. So, DesignerHost's CreateComponent will set
        // _newComponentName to the newly created name before creating the component.
        if (_newComponentName is not null)
        {
            name = _newComponentName;
            _newComponentName = null;
        }

        this.TryGetService(out INameCreationService? nameCreate);

        // Fabricate a name if one wasn't provided. We try to use the name creation service, but if it is not available
        // we will just use an empty string.
        if (name is null)
        {
            if (nameCreate is not null)
            {
                // VirtualTypes and Compact framework types will need to use reflection type in order to get their
                // "real" name (the one available in the compact FX, for example).
                Type reflectType = TypeDescriptor.GetReflectionType(component);
                if (string.Equals(reflectType.FullName, component.GetType().FullName, StringComparison.Ordinal))
                {
                    reflectType = component.GetType();
                }

                name = nameCreate.CreateName(this, reflectType);
            }
            else
            {
                name = string.Empty;
            }
        }
        else
        {
            nameCreate?.ValidateName(name);
        }

        return new Site(component, this, name, this);
    }

    /// <summary>
    ///  Override of dispose to clean up our state.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            throw new InvalidOperationException(SR.DesignSurfaceContainerDispose);
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  We move all "dispose" functionality to the DisposeHost method. The reason for this is that Dispose is inherited
    ///  from our container implementation, and we do not want someone disposing the container. That would leave the
    ///  design surface still alive, but it would kill the host. Instead, DesignSurface always calls DisposeHost, which
    ///  calls the base version of Dispose to clean out the container.
    /// </summary>
    internal void DisposeHost()
    {
        try
        {
            if (_loader is not null)
            {
                _loader.Dispose();
                Unload();
            }

            if (_surface is not null)
            {
                _designerEventService?.ActiveDesignerChanged -= OnActiveDesignerChanged;

                if (this.TryGetService(out DesignSurfaceServiceContainer? dsc))
                {
                    foreach (Type t in s_defaultServices)
                    {
                        dsc.RemoveFixedService(t);
                    }
                }
                else
                {
                    if (this.TryGetService(out IServiceContainer? sc))
                    {
                        foreach (Type t in s_defaultServices)
                        {
                            sc.RemoveService(t);
                        }
                    }
                    else
                    {
                        Debug.Fail("DesignerHost: Ctor needs a service provider that provides IServiceContainer");
                    }
                }
            }
        }
        finally
        {
            _loader = null;
            _surface = null;
            _events.Dispose();
        }

        base.Dispose(true);
    }

    /// <summary>
    ///  Invokes flush on the designer loader.
    /// </summary>
    internal void Flush() => _loader?.Flush();

    /// <summary>
    ///  Override of Container's GetService method. This just delegates to the parent service provider.
    /// </summary>
    /// <param name="service">The type of service to retrieve.</param>
    /// <returns>An instance of the service.</returns>
    protected override object? GetService(Type service)
    {
        object? serviceInstance = null;
        ArgumentNullException.ThrowIfNull(service);

        if (service == typeof(IMultitargetHelperService))
        {
            if (_loader is IServiceProvider provider)
            {
                serviceInstance = provider.GetService(service);
            }
        }
        else
        {
            serviceInstance = base.GetService(service) ?? _surface?.GetService(service);
        }

        return serviceInstance;
    }

    /// <summary>
    ///  Called in response to a designer becoming active or inactive.
    /// </summary>
    private void OnActiveDesignerChanged(object? sender, ActiveDesignerEventArgs? e)
    {
        // NOTE: sender can be null (we call this directly in BeginLoad)
        if (e is null)
        {
            return;
        }

        object eventobj;

        if (e.OldDesigner == this)
        {
            eventobj = s_eventDeactivated;
        }
        else if (e.NewDesigner == this)
        {
            eventobj = s_eventActivated;
        }
        else
        {
            // Not our document, so we don't fire.
            return;
        }

        // If we are deactivating, flush any code changes. We always route through the design surface so it can
        // correctly raise its Flushed event.
        if (e.OldDesigner == this && _surface is not null)
        {
            _surface.Flush();
        }

        // Fire the appropriate event.
        (_events[eventobj] as EventHandler)?.Invoke(this, EventArgs.Empty);
    }

    /// <summary>
    ///  Method is called by the site when a component is renamed.
    /// </summary>
    private void OnComponentRename(IComponent component, string? oldName, string newName)
    {
        // If the root component is being renamed we need to update RootComponentClassName.
        if (component == _rootComponent)
        {
            string className = _rootComponentClassName!;

            // If the old name occurs at the end of the class name and is preceeded by a period.
            if (oldName is not null && className.EndsWith(oldName, StringComparison.Ordinal)
                && className.Length > oldName.Length && className[className.Length - oldName.Length - 1] == '.')
            {
                // We assume the preceeding chars are the namespace and preserve it.
                _rootComponentClassName = string.Concat(className.AsSpan(0, className.Length - oldName.Length), newName);
            }
            else
            {
                _rootComponentClassName = newName;
            }
        }

        (_events[s_eventComponentRename] as ComponentRenameEventHandler)?.Invoke(this, new ComponentRenameEventArgs(component, oldName, newName));
    }

    /// <summary>
    ///  Method is called when the designer has finished loading.
    /// </summary>
    private void OnLoadComplete(EventArgs e) => (_events[s_eventLoadComplete] as EventHandler)?.Invoke(this, e);

    /// <summary>
    ///  Method is called when the last transaction has closed.
    /// </summary>
    private void OnTransactionClosed(DesignerTransactionCloseEventArgs e) =>
        (_events[s_eventTransactionClosed] as DesignerTransactionCloseEventHandler)?.Invoke(this, e);

    /// <summary>
    ///  Method is called when the last transaction is closing.
    /// </summary>
    private void OnTransactionClosing(DesignerTransactionCloseEventArgs e) =>
        (_events[s_eventTransactionClosing] as DesignerTransactionCloseEventHandler)?.Invoke(this, e);

    /// <summary>
    ///  Method is called when the first transaction has opened.
    /// </summary>
    private void OnTransactionOpened(EventArgs e) =>
        (_events[s_eventTransactionOpened] as EventHandler)?.Invoke(this, e);

    /// <summary>
    ///  Method is called when the first transaction is opening.
    /// </summary>
    private void OnTransactionOpening(EventArgs e) =>
        (_events[s_eventTransactionOpening] as EventHandler)?.Invoke(this, e);

    /// <summary>
    ///  Called to remove a component from its container.
    /// </summary>
    public override void Remove(IComponent? component)
    {
        if (RemoveFromContainerPreProcess(component, this))
        {
            Site? site = component.Site as Site;
            RemoveWithoutUnsiting(component);
            RemoveFromContainerPostProcess(component);
            site?.Disposed = true;
        }
    }

    internal bool RemoveFromContainerPreProcess([NotNullWhen(true)] IComponent? component, IContainer container)
    {
        if (component is null)
        {
            return false;
        }

        ISite? site = component.Site;
        if (site is null || site.Container != container)
        {
            return false;
        }

        ComponentEventArgs ce = new(component);

        ComponentEventHandler? eh = _events[s_eventComponentRemoving] as ComponentEventHandler;
        eh?.Invoke(this, ce);

        // If the component is an extender provider, remove it from the extender provider service, should one exist.
        if (component is IExtenderProvider extenderComponent && this.TryGetService(out IExtenderProviderService? eps))
        {
            eps.RemoveExtenderProvider(extenderComponent);
        }

        // Same for the component's designer
        _designers.TryGetValue(component, out IDesigner? designer);

        if (designer is IExtenderProvider extenderDesigner && this.TryGetService(out eps))
        {
            eps.RemoveExtenderProvider(extenderDesigner);
        }

        if (designer is not null)
        {
            designer.Dispose();
            _designers.Remove(component);
        }

        if (component == _rootComponent)
        {
            _rootComponent = null;
            _rootComponentClassName = null;
        }

        return true;
    }

    internal void RemoveFromContainerPostProcess(IComponent component)
    {
        // At one point during Whidbey, the component used to be un-sited earlier in this process and
        // it would be temporarily re-sited here before raising OnComponentRemoved. The problem with
        // re-siting it is that some 3rd party controls take action when a component is sited (such as
        // displaying a dialog a control is dropped on the form) and re-siting here caused them to think
        // they were being initialized for the first time. To preserve compat, we shouldn't re-site the
        // component during Remove.
        try
        {
            ComponentEventHandler? eh = _events[s_eventComponentRemoved] as ComponentEventHandler;
            ComponentEventArgs ce = new(component);
            eh?.Invoke(this, ce);
        }
        finally
        {
            component.Site = null;
        }
    }

    /// <summary>
    ///  Called to unload the design surface.
    /// </summary>
    private void Unload()
    {
        _surface?.OnUnloading();

        if (this.TryGetService(out IHelpService? helpService)
            && _rootComponent is not null
            && _designers.TryGetValue(_rootComponent, out IDesigner? designer))
        {
            helpService.RemoveContextAttribute("Keyword", $"Designer_{designer.GetType().FullName}");
        }

        ISelectionService? selectionService = (ISelectionService?)GetService(typeof(ISelectionService));
        selectionService?.SetSelectedComponents(null, SelectionTypes.Replace);

        // Now remove all the designers and their components. We save the root for last. Note that we eat any
        // exceptions that components or their designers generate. A bad component or designer should not prevent
        // an unload from happening. We do all of this in a transaction to help reduce the number of events we generate.
        _state[s_stateUnloading] = true;
        DesignerTransaction t = ((IDesignerHost)this).CreateTransaction();
        List<Exception> exceptions = [];

        try
        {
            IComponent[] components = new IComponent[Components.Count];
            Components.CopyTo(components, 0);

            foreach (IComponent comp in components)
            {
                if (!ReferenceEquals(comp, _rootComponent))
                {
                    if (_designers.Remove(comp, out IDesigner? compDesigner))
                    {
                        try
                        {
                            compDesigner.Dispose();
                        }
                        catch (Exception e)
                        {
                            exceptions.Add(e);
                        }
                    }

                    try
                    {
                        comp.Dispose();
                    }
                    catch (Exception e)
                    {
                        exceptions.Add(e);
                    }
                }
            }

            if (_rootComponent is not null)
            {
                if (_designers.Remove(_rootComponent, out IDesigner? rootComponentDesigner))
                {
                    try
                    {
                        rootComponentDesigner.Dispose();
                    }
                    catch (Exception e)
                    {
                        exceptions.Add(e);
                    }
                }

                try
                {
                    _rootComponent.Dispose();
                }
                catch (Exception e)
                {
                    exceptions.Add(e);
                }
            }

            _designers.Clear();
            while (Components.Count > 0)
            {
                Remove(Components[0]);
            }
        }
        finally
        {
            t.Commit();
            _state[s_stateUnloading] = false;
        }

        // There should be no open transactions. Commit all of the ones that are open.
        if (_transactions is not null && _transactions.Count > 0)
        {
            Debug.Fail("There are open transactions at unload");

            // Transactions will get popped in the OnCommit for DesignerHostTransaction.
            while (_transactions.TryPeek(out DesignerTransaction transaction))
            {
                transaction.Commit();
            }
        }

        _surface?.OnUnloaded();

        if (exceptions.Count > 0)
        {
            throw new ExceptionCollection(exceptions);
        }
    }

    event ComponentEventHandler IComponentChangeService.ComponentAdded
    {
        add => _events.AddHandler(s_eventComponentAdded, value);
        remove => _events.RemoveHandler(s_eventComponentAdded, value);
    }

    event ComponentEventHandler IComponentChangeService.ComponentAdding
    {
        add => _events.AddHandler(s_eventComponentAdding, value);
        remove => _events.RemoveHandler(s_eventComponentAdding, value);
    }

    event ComponentChangedEventHandler IComponentChangeService.ComponentChanged
    {
        add => _events.AddHandler(s_eventComponentChanged, value);
        remove => _events.RemoveHandler(s_eventComponentChanged, value);
    }

    event ComponentChangingEventHandler IComponentChangeService.ComponentChanging
    {
        add => _events.AddHandler(s_eventComponentChanging, value);
        remove => _events.RemoveHandler(s_eventComponentChanging, value);
    }

    event ComponentEventHandler IComponentChangeService.ComponentRemoved
    {
        add => _events.AddHandler(s_eventComponentRemoved, value);
        remove => _events.RemoveHandler(s_eventComponentRemoved, value);
    }

    event ComponentEventHandler IComponentChangeService.ComponentRemoving
    {
        add => _events.AddHandler(s_eventComponentRemoving, value);
        remove => _events.RemoveHandler(s_eventComponentRemoving, value);
    }

    event ComponentRenameEventHandler IComponentChangeService.ComponentRename
    {
        add => _events.AddHandler(s_eventComponentRename, value);
        remove => _events.RemoveHandler(s_eventComponentRename, value);
    }

    void IComponentChangeService.OnComponentChanged(object component, MemberDescriptor? member, object? oldValue, object? newValue)
    {
        if (!((IDesignerHost)this).Loading)
        {
            (_events[s_eventComponentChanged] as ComponentChangedEventHandler)?.Invoke(
                this,
                new ComponentChangedEventArgs(component, member, oldValue, newValue));
        }
    }

    void IComponentChangeService.OnComponentChanging(object component, MemberDescriptor? member)
    {
        if (!((IDesignerHost)this).Loading)
        {
            (_events[s_eventComponentChanging] as ComponentChangingEventHandler)?.Invoke(this, new ComponentChangingEventArgs(component, member));
        }
    }

    bool IDesignerHost.Loading =>
        _state[s_stateLoading] || _state[s_stateUnloading] || (_loader is not null && _loader.Loading);

    bool IDesignerHost.InTransaction => (_transactions is not null && _transactions.Count > 0) || IsClosingTransaction;

    IContainer IDesignerHost.Container => this;

    IComponent IDesignerHost.RootComponent => _rootComponent!;

    string IDesignerHost.RootComponentClassName => _rootComponentClassName!;

    string IDesignerHost.TransactionDescription =>
        _transactions is not null && _transactions.TryPeek(out DesignerTransaction? transaction)
            ? transaction.Description
            : string.Empty;

    event EventHandler IDesignerHost.Activated
    {
        add => _events.AddHandler(s_eventActivated, value);
        remove => _events.RemoveHandler(s_eventActivated, value);
    }

    event EventHandler IDesignerHost.Deactivated
    {
        add => _events.AddHandler(s_eventDeactivated, value);
        remove => _events.RemoveHandler(s_eventDeactivated, value);
    }

    event EventHandler IDesignerHost.LoadComplete
    {
        add => _events.AddHandler(s_eventLoadComplete, value);
        remove => _events.RemoveHandler(s_eventLoadComplete, value);
    }

    event DesignerTransactionCloseEventHandler IDesignerHost.TransactionClosed
    {
        add => _events.AddHandler(s_eventTransactionClosed, value);
        remove => _events.RemoveHandler(s_eventTransactionClosed, value);
    }

    event DesignerTransactionCloseEventHandler IDesignerHost.TransactionClosing
    {
        add => _events.AddHandler(s_eventTransactionClosing, value);
        remove => _events.RemoveHandler(s_eventTransactionClosing, value);
    }

    event EventHandler IDesignerHost.TransactionOpened
    {
        add => _events.AddHandler(s_eventTransactionOpened, value);
        remove => _events.RemoveHandler(s_eventTransactionOpened, value);
    }

    event EventHandler IDesignerHost.TransactionOpening
    {
        add => _events.AddHandler(s_eventTransactionOpening, value);
        remove => _events.RemoveHandler(s_eventTransactionOpening, value);
    }

    void IDesignerHost.Activate() => _surface?.OnViewActivate();

    // The CreateComponent implementation has special handling of null and string.Empty,
    // and we want to preserve this distinction.
    IComponent IDesignerHost.CreateComponent(Type componentType) =>
        ((IDesignerHost)this).CreateComponent(componentType, null!);

    IComponent IDesignerHost.CreateComponent(Type componentType, string name)
    {
        ArgumentNullException.ThrowIfNull(componentType);

        IComponent? component;
        LicenseContext oldContext = LicenseManager.CurrentContext;

        // We don't want if there is a recursively (creating a component create another one) to change the context again.
        // We already have the one we want and that would create a locking problem.
        bool changingContext = false;
        if (oldContext != LicenseContext)
        {
            LicenseManager.CurrentContext = LicenseContext;
            LicenseManager.LockContext(s_selfLock);
            changingContext = true;
        }

        try
        {
            try
            {
                _newComponentName = name;
                if (_surface is null)
                {
                    throw new InvalidOperationException();
                }

                component = _surface.CreateInstance(componentType) as IComponent;
            }
            finally
            {
                _newComponentName = null;
            }

            if (component is null)
            {
                throw new InvalidOperationException(string.Format(SR.DesignerHostFailedComponentCreate, componentType.Name))
                {
                    HelpLink = SR.DesignerHostFailedComponentCreate
                };
            }

            // Add this component to our container
            if (component.Site is null || component.Site.Container != this)
            {
                PerformAdd(component, name);
            }
        }
        finally
        {
            if (changingContext)
            {
                LicenseManager.UnlockContext(s_selfLock);
                LicenseManager.CurrentContext = oldContext;
            }
        }

        return component;
    }

    DesignerTransaction IDesignerHost.CreateTransaction() =>
        ((IDesignerHost)this).CreateTransaction(SR.DesignerHostGenericTransactionName);

    DesignerTransaction IDesignerHost.CreateTransaction(string description) =>
        new DesignerHostTransaction(this, description ?? SR.DesignerHostGenericTransactionName);

    void IDesignerHost.DestroyComponent(IComponent component)
    {
        ArgumentNullException.ThrowIfNull(component);

        string name = component.Site?.Name ?? component.GetType().Name;

        // Make sure the component is not being inherited -- we can't delete these!
        if (TypeDescriptorHelper.TryGetAttribute(component, out InheritanceAttribute? ia) && ia.InheritanceLevel != InheritanceLevel.NotInherited)
        {
            throw new InvalidOperationException(string.Format(SR.DesignerHostCantDestroyInheritedComponent, name))
            {
                HelpLink = SR.DesignerHostCantDestroyInheritedComponent
            };
        }

        if (((IDesignerHost)this).InTransaction)
        {
            Remove(component);
            component.Dispose();
        }
        else
        {
            DesignerTransaction t;
            using (t = ((IDesignerHost)this).CreateTransaction(string.Format(SR.DesignerHostDestroyComponentTransaction, name)))
            {
                // We need to signal changing and then perform the remove. Remove must be done by us and not by Dispose
                // because (a) people need a chance to cancel through a Removing event, and (b) Dispose removes from the
                // container last and anything that would sync Removed would end up with a dead component.
                Remove(component);
                component.Dispose();
                t.Commit();
            }
        }
    }

    IDesigner? IDesignerHost.GetDesigner(IComponent component)
    {
        ArgumentNullException.ThrowIfNull(component);
        _designers.TryGetValue(component, out IDesigner? designer);
        return designer;
    }

    Type? IDesignerHost.GetType(string typeName)
    {
        ArgumentNullException.ThrowIfNull(typeName);

        if (this.TryGetService(out ITypeResolutionService? ts))
        {
            return ts.GetType(typeName);
        }

        return Type.GetType(typeName);
    }

    void IDesignerLoaderHost.EndLoad(string? rootClassName, bool successful, ICollection? errorCollection)
    {
        bool wasLoading = _state[s_stateLoading];
        _state[s_stateLoading] = false;

        if (rootClassName is not null)
        {
            _rootComponentClassName = rootClassName;
        }
        else if (_rootComponent?.Site is not null)
        {
            _rootComponentClassName = _rootComponent.Site.Name;
        }

        // If the loader indicated success, but it never created a component, that is an error.
        if (successful && _rootComponent is null)
        {
            errorCollection = new List<object>()
            {
                new InvalidOperationException(SR.DesignerHostNoBaseClass)
                {
                    HelpLink = SR.DesignerHostNoBaseClass
                }
            };

            successful = false;
        }

        // If we failed, unload the doc so that the OnLoaded event can't get to anything that actually did work.
        if (!successful)
        {
            Unload();
        }

        if (wasLoading)
        {
            _surface?.OnLoaded(successful, errorCollection);
        }

        // We may be invoked to do an EndLoad when we are already loaded. This can happen if the user called
        // AddLoadDependency, essentially putting us in a loading state while we are already loaded. This is OK,
        // and is used as a hint that the user is going to monkey with settings but doesn't want the code engine
        // to report it.

        if (!successful || !wasLoading)
        {
            return;
        }

        IRootDesigner rootDesigner = (((IDesignerHost)this).GetDesigner(_rootComponent!) as IRootDesigner)
            ?? throw new InvalidOperationException("Root designer is null.");

        // Offer up our base help attribute.
        if (this.TryGetService(out IHelpService? helpService))
        {
            helpService.AddContextAttribute("Keyword", $"Designer_{rootDesigner.GetType().FullName}", HelpKeywordType.F1Keyword);
        }

        // And let everyone know that we're loaded.
        try
        {
            OnLoadComplete(EventArgs.Empty);
        }
        catch (Exception ex)
        {
            Debug.Fail($"Exception thrown on LoadComplete event handler. You should not throw here : {ex}");

            // The load complete failed. Put us back in the loading state and unload.
            _state[s_stateLoading] = true;
            Unload();

            List<object> errorList = errorCollection is null ? [] : [..errorCollection.Cast<object>()];
            errorList.Insert(0, ex);

            errorCollection = errorList;

            successful = false;

            _surface?.OnLoaded(successful, errorCollection);

            // We re-throw. If this was a synchronous load this will error back to BeginLoad (and, as a side effect,
            // may call us again). For asynchronous loads we need to throw so the caller knows what happened.
            throw;
        }

        // If we saved a selection as a result of a reload, try to replace it.
        if (successful && _savedSelection is not null && this.TryGetService(out ISelectionService? ss))
        {
            List<IComponent> selectedComponents = new(_savedSelection.Count);
            foreach (string name in _savedSelection)
            {
                IComponent? component = Components[name];
                if (component is not null)
                {
                    selectedComponents.Add(component);
                }
            }

            _savedSelection = null;
            ss.SetSelectedComponents(selectedComponents, SelectionTypes.Replace);
        }
    }

    /// <summary>
    ///  This is called by the designer loader when it wishes to reload the design document.
    ///  The reload will happen immediately so the caller should ensure that it is in a state
    ///  where BeginLoad may be called again.
    /// </summary>
    void IDesignerLoaderHost.Reload()
    {
        if (_loader is null)
        {
            return;
        }

        // Flush the loader to make sure there aren't any pending changes. We always route through the design
        // surface so it can correctly raise its Flushed event.
        _surface!.Flush();

        // Next, stash off the set of selected objects by name. After the reload we will attempt to re-select them.
        if (this.TryGetService(out ISelectionService? ss))
        {
            List<string> list = new(ss.SelectionCount);
            foreach (object item in ss.GetSelectedComponents())
            {
                if (item is IComponent component && component.Site?.Name is string name)
                {
                    list.Add(name);
                }
            }

            _savedSelection = list;
        }

        Unload();
        BeginLoad(_loader);
    }

    bool IDesignerLoaderHost2.IgnoreErrorsDuringReload
    {
        get => _ignoreErrorsDuringReload;
        set
        {
            // Only allow to set to true if we CanReloadWithErrors.
            if (!value || ((IDesignerLoaderHost2)this).CanReloadWithErrors)
            {
                _ignoreErrorsDuringReload = value;
            }
        }
    }

    bool IDesignerLoaderHost2.CanReloadWithErrors { get; set; }

    // We implement IReflect to keep our reflection surface constrained to IDesignerHost.

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
    MethodInfo? IReflect.GetMethod(string name, BindingFlags bindingAttr, Binder? binder, Type[] types, ParameterModifier[]? modifiers) =>
        typeof(IDesignerHost).GetMethod(name, bindingAttr, binder, types, modifiers);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
    MethodInfo? IReflect.GetMethod(string name, BindingFlags bindingAttr) => typeof(IDesignerHost).GetMethod(name, bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]
    MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr) => typeof(IDesignerHost).GetMethods(bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
    FieldInfo? IReflect.GetField(string name, BindingFlags bindingAttr) => typeof(IDesignerHost).GetField(name, bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
    FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr) => typeof(IDesignerHost).GetFields(bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
    PropertyInfo? IReflect.GetProperty(string name, BindingFlags bindingAttr) => typeof(IDesignerHost).GetProperty(name, bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
    PropertyInfo? IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder? binder, Type? returnType, Type[] types, ParameterModifier[]? modifiers) =>
        typeof(IDesignerHost).GetProperty(name, bindingAttr, binder, returnType, types, modifiers);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
    PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr) => typeof(IDesignerHost).GetProperties(bindingAttr);

    internal const DynamicallyAccessedMemberTypes GetAllMembers = DynamicallyAccessedMemberTypes.PublicFields
        | DynamicallyAccessedMemberTypes.NonPublicFields
        | DynamicallyAccessedMemberTypes.PublicMethods
        | DynamicallyAccessedMemberTypes.NonPublicMethods
        | DynamicallyAccessedMemberTypes.PublicEvents
        | DynamicallyAccessedMemberTypes.NonPublicEvents
        | DynamicallyAccessedMemberTypes.PublicProperties
        | DynamicallyAccessedMemberTypes.NonPublicProperties
        | DynamicallyAccessedMemberTypes.PublicConstructors
        | DynamicallyAccessedMemberTypes.NonPublicConstructors
        | DynamicallyAccessedMemberTypes.PublicNestedTypes
        | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;

    [DynamicallyAccessedMembers(GetAllMembers)]
    MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr) => typeof(IDesignerHost).GetMember(name, bindingAttr);

    [DynamicallyAccessedMembers(GetAllMembers)]
    MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr) => typeof(IDesignerHost).GetMembers(bindingAttr);

    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
    object? IReflect.InvokeMember(
        string name,
        BindingFlags invokeAttr,
        Binder? binder,
        object? target,
        object?[]? args,
        ParameterModifier[]? modifiers,
        CultureInfo? culture,
        string[]? namedParameters) =>
        typeof(IDesignerHost).InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);

    Type IReflect.UnderlyingSystemType => typeof(IDesignerHost).UnderlyingSystemType;

    void IServiceContainer.AddService(Type serviceType, object serviceInstance)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.AddService(serviceType, serviceInstance);
    }

    void IServiceContainer.AddService(Type serviceType, object serviceInstance, bool promote)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.AddService(serviceType, serviceInstance, promote);
    }

    void IServiceContainer.AddService(Type serviceType, ServiceCreatorCallback callback)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.AddService(serviceType, callback);
    }

    void IServiceContainer.AddService(Type serviceType, ServiceCreatorCallback callback, bool promote)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.AddService(serviceType, callback, promote);
    }

    void IServiceContainer.RemoveService(Type serviceType)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.RemoveService(serviceType);
    }

    void IServiceContainer.RemoveService(Type serviceType, bool promote)
    {
        ObjectDisposedException.ThrowIf(!this.TryGetService(out IServiceContainer? sc), typeof(IServiceContainer));
        sc.RemoveService(serviceType, promote);
    }

    object? IServiceProvider.GetService(Type serviceType) => GetService(serviceType);
}
