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

using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using Moq;

namespace System.Windows.Forms.Design.Tests;

public class ControlCommandSetTests : IDisposable
{
    private readonly Mock<ISite> _siteMock;
    private readonly Mock<IDesignerHost> _designerHostMock;
    private readonly Mock<IMenuCommandService> _menuServiceMock;
    private readonly Mock<IEventHandlerService> _eventHandlerServiceMock;
    private readonly Mock<ISelectionService> _selectionServiceMock;
    private readonly Mock<IDictionaryService> _dictionaryServiceMock;
    private readonly ControlCommandSet _controlCommandSet;
    private readonly Control _baseControl;

    public ControlCommandSetTests()
    {
        _siteMock = new();
        _designerHostMock = new();
        _menuServiceMock = new();
        _eventHandlerServiceMock = new();
        _selectionServiceMock = new();
        _dictionaryServiceMock = new();
        _baseControl = new();

        _siteMock.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(_designerHostMock.Object);
        _siteMock.Setup(s => s.GetService(typeof(IMenuCommandService))).Returns(_menuServiceMock.Object);
        _siteMock.Setup(s => s.GetService(typeof(IEventHandlerService))).Returns(_eventHandlerServiceMock.Object);
        _siteMock.Setup(s => s.GetService(typeof(ISelectionService))).Returns(_selectionServiceMock.Object);
        _siteMock.Setup(s => s.GetService(typeof(IDictionaryService))).Returns(_dictionaryServiceMock.Object);
        _designerHostMock.Setup(h => h.RootComponent).Returns(_baseControl);

        _controlCommandSet = new(_siteMock.Object);
    }

    public void Dispose()
    {
        _controlCommandSet.Dispose();
        _baseControl.Dispose();
    }

    [Fact]
    public void Constructor_ShouldInitializeCommandSet()
    {
        _controlCommandSet.Should().NotBeNull();

        StatusCommandUI statusCommandUI = _controlCommandSet.TestAccessor.Dynamic._statusCommandUI;
        statusCommandUI.Should().NotBeNull();

        MenuCommand[] commandSet = _controlCommandSet.TestAccessor.Dynamic._commandSet;
        commandSet.Should().NotBeNull();
        commandSet.Should().NotBeEmpty();

        TabOrder tabOrder = _controlCommandSet.TestAccessor.Dynamic._tabOrder;
        tabOrder.Should().BeNull();
    }

    [Fact]
    public void Dispose_ShouldRemoveCommandsFromMenuService()
    {
        _controlCommandSet.Dispose();

        _menuServiceMock.Verify(m => m.RemoveCommand(It.IsAny<MenuCommand>()), Times.AtLeastOnce);
    }

    [Fact]
    public void OnMenuLockControls_ShouldToggleLockControls()
    {
        using Control component = new();
        Mock<IContainer> containerMock = new();
        _siteMock.Setup(s => s.Container).Returns(containerMock.Object);
        component.Site = _siteMock.Object;

        _designerHostMock.Setup(h => h.RootComponent).Returns(_baseControl);
        _designerHostMock.Setup(h => h.Container.Components).Returns(new ComponentCollection([component]));

        CommandID commandID = new(Guid.NewGuid(), 0);
        MenuCommand menuCommand = new(null, commandID) { Checked = false };

        _controlCommandSet.TestAccessor.Dynamic.OnMenuLockControls(menuCommand, EventArgs.Empty);

        menuCommand.Checked.Should().BeTrue();
    }

    [Fact]
    public void GetSnapInformation_WithSnapToGridPropertyInParent_ShouldReturnThatProperty()
    {
        using Control childComponent = new();
        using Control parentComponent = new();

        childComponent.Parent = parentComponent;

        Mock<IContainer> containerMock = new();
        _siteMock.Setup(s => s.Container).Returns(containerMock.Object);
        childComponent.Site = _siteMock.Object;
        parentComponent.Site = _siteMock.Object;

        _designerHostMock.Setup(h => h.RootComponent).Returns(_baseControl);

        Mock<IDesigner> designerMock = new();
        designerMock.Setup(d => d.Component).Returns(childComponent);
        _designerHostMock.Setup(h => h.GetDesigner(childComponent)).Returns(designerMock.Object);

        Mock<PropertyDescriptor> snapPropertyDescriptorMock = new("SnapToGrid", Array.Empty<Attribute>());
        snapPropertyDescriptorMock.Setup(p => p.PropertyType).Returns(typeof(bool));
        snapPropertyDescriptorMock.Setup(p => p.ComponentType).Returns(typeof(Control));
        snapPropertyDescriptorMock.Setup(p => p.Name).Returns("SnapToGrid");

        var originalProvider = TypeDescriptor.GetProvider(parentComponent);
        var mockProvider = new MockTypeDescriptionProvider(originalProvider, snapPropertyDescriptorMock.Object);
        TypeDescriptor.AddProvider(mockProvider, parentComponent);

        try
        {
            _controlCommandSet.TestAccessor.Dynamic.GetSnapInformation(
                _designerHostMock.Object,
                childComponent,
                out Size snapSize,
                out IComponent snapComponent,
                out PropertyDescriptor snapProperty);

            snapComponent.Should().Be(parentComponent);
            snapProperty.Should().NotBeNull();
            snapProperty.Name.Should().Be("SnapToGrid");
            snapSize.Should().Be(Size.Empty);
        }
        finally
        {
            TypeDescriptor.RemoveProvider(mockProvider, parentComponent);
        }
    }

    /// <summary>
    ///  Helper class to provide mock property descriptors.
    /// </summary>
    private class MockTypeDescriptionProvider : TypeDescriptionProvider
    {
        private readonly TypeDescriptionProvider _baseProvider;
        private readonly PropertyDescriptor _snapToGridProperty;

        public MockTypeDescriptionProvider(TypeDescriptionProvider baseProvider, PropertyDescriptor snapToGridProperty)
        {
            _baseProvider = baseProvider;
            _snapToGridProperty = snapToGridProperty;
        }

        public override ICustomTypeDescriptor? GetTypeDescriptor(Type objectType, object? instance)
        {
            var baseDescriptor = _baseProvider.GetTypeDescriptor(objectType, instance);
            return baseDescriptor is not null ? new MockCustomTypeDescriptor(baseDescriptor, _snapToGridProperty) : null;
        }
    }

    private class MockCustomTypeDescriptor : CustomTypeDescriptor
    {
        private readonly PropertyDescriptor _snapToGridProperty;

        public MockCustomTypeDescriptor(ICustomTypeDescriptor parent, PropertyDescriptor snapToGridProperty)
            : base(parent)
        {
            _snapToGridProperty = snapToGridProperty;
        }

        public override PropertyDescriptorCollection GetProperties()
        {
            PropertyDescriptorCollection baseProperties = base.GetProperties();

            PropertyDescriptor[] allProperties = new PropertyDescriptor[baseProperties.Count + 1];
            baseProperties.CopyTo(allProperties, 0);
            allProperties[baseProperties.Count] = _snapToGridProperty;

            return new PropertyDescriptorCollection(allProperties);
        }

        public override PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
        {
            PropertyDescriptorCollection baseProperties = base.GetProperties(attributes);

            PropertyDescriptor[] allProperties = new PropertyDescriptor[baseProperties.Count + 1];
            baseProperties.CopyTo(allProperties, 0);
            allProperties[baseProperties.Count] = _snapToGridProperty;

            return new PropertyDescriptorCollection(allProperties);
        }
    }
}
