// 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.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Runtime.Versioning;
using System.Windows.Forms.Design.Behavior;

namespace System.Windows.Forms.Design;

/// <summary>
///  Designer for the ToolStripPanel.
/// </summary>
internal class ToolStripPanelDesigner : ScrollableControlDesigner
{
    private static Padding s_defaultPadding = new(0);
    private IComponentChangeService? _componentChangeService;
    private IDesignerHost? _designerHost;

    // The container selector glyph which is associated with this designer.
    private ToolStripPanelSelectionGlyph? _containerSelectorGlyph;
    private ToolStripPanelSelectionBehavior? _behavior;

    // Designer context Menu for this designer
    private BaseContextMenuStrip? _contextMenu;

    // The SelectionService..
    private ISelectionService? _selectionService;

    private MenuCommand? _designerShortCutCommand;
    private MenuCommand? _oldShortCutCommand;

    /// <summary>
    ///  Creates a Dashed-Pen of appropriate color.
    /// </summary>
    private Pen BorderPen
    {
        [ResourceExposure(ResourceScope.Process)]
        [ResourceConsumption(ResourceScope.Process)]
        get
        {
            Color penColor = Control.BackColor.GetBrightness() < .5 ?
                          ControlPaint.Light(Control.BackColor) :
                          ControlPaint.Dark(Control.BackColor);

            Pen pen = new(penColor)
            {
                DashStyle = DashStyle.Dash
            };

            return pen;
        }
    }

    // Custom ContextMenu.
    private ContextMenuStrip? DesignerContextMenu
    {
        get
        {
            if (_contextMenu is null)
            {
                ISite? site = Component.Site;

                if (site is not null)
                {
                    _contextMenu = new BaseContextMenuStrip(site);
                    // If multiple Items Selected don't show the custom properties...
                    _contextMenu.GroupOrdering.Clear();
                    _contextMenu.GroupOrdering.AddRange([StandardGroups.Code,
                        StandardGroups.Verbs,
                        StandardGroups.Custom,
                        StandardGroups.Selection,
                        StandardGroups.Edit,
                        StandardGroups.Properties
                    ]);
                    _contextMenu.Text = "CustomContextMenu";
                }
            }

            return _contextMenu;
        }
    }

    public override ToolStripPanel Control => (ToolStripPanel)Component;

    // ToolStripPanels if Inherited ACT as Readonly.
    protected override InheritanceAttribute? InheritanceAttribute
    {
        get => Control.Parent is ToolStripContainer && (base.InheritanceAttribute == InheritanceAttribute.Inherited)
               ? InheritanceAttribute.InheritedReadOnly
               : base.InheritanceAttribute;
    }

    private Padding Padding
    {
        get => (Padding)ShadowProperties[nameof(Padding)]!;
        set
        {
            ShadowProperties[nameof(Padding)] = value;
        }
    }

    /// <summary>
    ///  This designer doesn't participate in SnapLines for the controls contained..
    /// </summary>
    public override bool ParticipatesWithSnapLines => false;

    /// <summary>
    ///  Retrieves a set of rules concerning the movement capabilities of a component.
    ///  This should be one or more flags from the SelectionRules class. If no designer
    ///  provides rules for a component, the component will not get any UI services.
    /// </summary>
    public override SelectionRules SelectionRules
         => Control.Parent is ToolStripContainer
            ? SelectionRules.Locked
            : base.SelectionRules;

    /// <summary>
    ///  Called from <see cref="ToolStripContainerActionList"/> to set the Expanded
    ///  state to false when the panel's visibility is changed.
    /// </summary>
    public ToolStripPanelSelectionGlyph? ToolStripPanelSelectorGlyph { get; private set; }

    /// <summary>
    ///  ShadowProperty.
    /// </summary>
    private bool Visible
    {
        get => (bool)ShadowProperties[nameof(Visible)]!;
        set
        {
            ShadowProperties[nameof(Visible)] = value;
            Control.Visible = value;
        }
    }

    /// <summary>
    ///  Determines if the this designer can parent to the specified designers
    ///  generally this means if the control for this designer can parent the
    ///  given ControlDesigner's designer.
    /// </summary>
    public override bool CanParent(Control control)
         => control is ToolStrip;

    /// <summary>
    ///  This designer can be parented to only ToolStripContainer.
    /// </summary>
    public override bool CanBeParentedTo(IDesigner parentDesigner)
        => Control.Parent is not ToolStripContainer;

    /// <summary>
    ///  Update the glyph whenever component is changed.
    /// </summary>
    private void OnComponentChanged(object? sender, ComponentChangedEventArgs e)
        => _containerSelectorGlyph?.UpdateGlyph();

    /// <summary>
    ///  This is the worker method of all CreateTool methods. It is the only one
    ///  that can be overridden.
    /// </summary>
    protected override IComponent[]? CreateToolCore(ToolboxItem tool, int x, int y, int width, int height, bool hasLocation, bool hasSize)
    {
        if (tool is null)
        {
            return null;
        }

        Type? toolType = tool.GetType(_designerHost);

        if (!typeof(ToolStrip).IsAssignableFrom(toolType))
        {
            if (Control.Parent is ToolStripContainer { ContentPanel: { } contentPanel }
                && _designerHost?.GetDesigner(contentPanel) is PanelDesigner designer)
            {
                InvokeCreateTool(designer, tool);
            }
        }
        else
        {
            base.CreateToolCore(tool, x, y, width, height, hasLocation, hasSize);
        }

        return null;
    }

    /// <summary>
    ///  Disposes of this designer.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        try
        {
            // Bug 904525: Need to recover the old menu item when deleting the component.
            if (_selectionService is not null && _selectionService.PrimarySelection is null)
            {
                OnSelectionChanging(_selectionService, EventArgs.Empty);
            }

            base.Dispose(disposing);
        }
        finally
        {
            if (disposing)
            {
                _contextMenu?.Dispose();
            }

            if (_selectionService is not null)
            {
                _selectionService.SelectionChanging -= OnSelectionChanging;
                _selectionService.SelectionChanged -= OnSelectionChanged;
                _selectionService = null;
            }

            _componentChangeService?.ComponentChanged -= OnComponentChanged;

            if (HasComponent)
            {
                Control.ControlAdded -= OnControlAdded;
                Control.ControlRemoved -= OnControlRemoved;
            }
        }
    }

    /// <summary>
    ///  This draws a nice border around our RaftingContainer. We need
    ///  this because the Control can have no border and you can't
    ///  tell where it is.
    /// </summary>
    private void DrawBorder(Graphics graphics)
    {
        Pen pen = BorderPen;
        Rectangle rc = Control.ClientRectangle;

        rc.Width--;
        rc.Height--;

        graphics.DrawRectangle(pen, rc);

        pen.Dispose();
    }

    /// <summary>
    ///  We need to expand the TopToolStripPanel only when the control is Dropped onto the form .. for the first time.
    /// </summary>
    internal void ExpandTopPanel()
    {
        if (_containerSelectorGlyph is null && Component.Site is not null)
        {
            // get the adorner window-relative coordinates for the container control
            _behavior = new ToolStripPanelSelectionBehavior(Control, Component.Site);
            _containerSelectorGlyph = new ToolStripPanelSelectionGlyph(Rectangle.Empty, Cursors.Default, Control, Component.Site, _behavior);
        }

        if (_containerSelectorGlyph is not null && Control.Dock == DockStyle.Top)
        {
            Control.Padding = new Padding(0, 0, 25, 25);
            _containerSelectorGlyph.IsExpanded = true;
        }
    }

    private void OnKeyShowDesignerActions(object? sender, EventArgs e)
    {
        if (_containerSelectorGlyph is null)
        {
            return;
        }

        _behavior?.OnMouseDown(_containerSelectorGlyph, MouseButtons.Left, Point.Empty);
    }

    /// <summary>
    ///  Since we have to initialize glyphs for SplitterPanel (which is not a part of Components.) we override the
    ///  GetGlyphs for the parent.
    /// </summary>
    internal Glyph? GetGlyph()
    {
        // Add own Glyphs.
        if (_containerSelectorGlyph is null && Component.Site is not null)
        {
            // get the adorner window-relative coordinates for the container control
            _behavior = new ToolStripPanelSelectionBehavior(Control, Component.Site);
            _containerSelectorGlyph = new ToolStripPanelSelectionGlyph(Rectangle.Empty, Cursors.Default, Control, Component.Site, _behavior);
        }

        // Show the Glyph only if Panel is Visible.
        return Control.Visible ? _containerSelectorGlyph : null;
    }

    /// <summary>
    ///  This property is used by deriving classes to determine if it returns the control being designed or some other Container
    ///  while adding a component to it.
    ///  e.g: When SplitContainer is selected and a component is being added ... the SplitContainer designer would return a
    ///  SelectedPanel as the ParentControl for all the items being added rather than itself.
    /// </summary>
    protected override Control? GetParentForComponent(IComponent component)
    {
        Type toolType = component.GetType();

        if (typeof(ToolStrip).IsAssignableFrom(toolType))
        {
            return Control;
        }

        ToolStripContainer? parent = Control.Parent as ToolStripContainer;
        return parent?.ContentPanel;
    }

    /// <summary>
    ///  Get the designer set up to run.
    /// </summary>
    public override void Initialize(IComponent component)
    {
        base.Initialize(component);

        Padding = Control.Padding;
        _designerHost = component.Site?.GetService<IDesignerHost>();

        if (_selectionService is null)
        {
            _selectionService = GetRequiredService<ISelectionService>();
            _selectionService.SelectionChanging += OnSelectionChanging;
            _selectionService.SelectionChanged += OnSelectionChanged;
        }

        if (_designerHost is not null)
        {
            _componentChangeService = _designerHost.GetService<IComponentChangeService>();
        }

        _componentChangeService?.ComponentChanged += OnComponentChanged;

        // Hook up the ControlAdded Event
        Control.ControlAdded += OnControlAdded;
        Control.ControlRemoved += OnControlRemoved;
    }

    /// <summary>
    ///  We need to invalidate the glyphBounds when the glyphs are turned off.
    /// </summary>
    internal void InvalidateGlyph()
    {
        if (_containerSelectorGlyph is null)
        {
            return;
        }

        BehaviorService?.Invalidate(_containerSelectorGlyph.Bounds);
    }

    /// <summary>
    ///  Required to CodeGen the Controls collection.
    /// </summary>
    private void OnControlAdded(object? sender, ControlEventArgs e)
    {
        if (e.Control is not ToolStrip)
        {
            return;
        }

        // Change the padding which might have been set by the Behavior if the _panel is Expanded.
        Control.Padding = new Padding(0);

        _containerSelectorGlyph?.IsExpanded = false;

        // Smoke the dock property whenever we add a toolstrip to a toolstrip _panel.
        PropertyDescriptor? dockProp = TypeDescriptor.GetProperties(e.Control)["Dock"];
        dockProp?.SetValue(e.Control, DockStyle.None);

        RefreshSelection();
    }

    /// <summary>
    ///  Required to CodeGen the Controls collection.
    /// </summary>
    private void OnControlRemoved(object? sender, ControlEventArgs e)
    {
        if (Control.Controls.Count == 0)
        {
            _containerSelectorGlyph?.IsExpanded = false;

            RefreshSelection();
        }
    }

    private void RefreshSelection()
    {
        if (_designerHost is not null && !_designerHost.Loading)
        {
            SelectionManager? selectionManager = GetService<SelectionManager>();
            selectionManager?.Refresh();
        }
    }

    /// <summary>
    ///  Called when ContextMenu is invoked.
    /// </summary>
    protected override void OnContextMenu(int x, int y)
    {
        if (Control.Parent is ToolStripContainer)
        {
            DesignerContextMenu?.Show(x, y);
        }
        else
        {
            base.OnContextMenu(x, y);
        }
    }

    private void OnSelectionChanging(object? sender, EventArgs e)
    {
        if (_designerShortCutCommand is null)
        {
            return;
        }

        // Remove our DesignerShortCutHandler
        if (TryGetService(out IMenuCommandService? menuCommandService))
        {
            menuCommandService.RemoveCommand(_designerShortCutCommand);
            if (_oldShortCutCommand is not null)
            {
                menuCommandService.AddCommand(_oldShortCutCommand);
            }
        }

        _designerShortCutCommand = null;
    }

    private void OnSelectionChanged(object? sender, EventArgs e)
    {
        if (_selectionService?.PrimarySelection == Control)
        {
            _designerShortCutCommand = new MenuCommand(OnKeyShowDesignerActions, MenuCommands.KeyInvokeSmartTag);
            if (TryGetService(out IMenuCommandService? menuCommandService))
            {
                _oldShortCutCommand = menuCommandService.FindCommand(MenuCommands.KeyInvokeSmartTag);
                if (_oldShortCutCommand is not null)
                {
                    menuCommandService.RemoveCommand(_oldShortCutCommand);
                }

                menuCommandService.AddCommand(_designerShortCutCommand);
            }
        }
    }

    /// <summary>
    ///  Paint the borders for the panels.
    /// </summary>
    protected override void OnPaintAdornments(PaintEventArgs paintEvent)
    {
        if (!ToolStripDesignerUtils.DisplayInformation.TerminalServer
            && !ToolStripDesignerUtils.DisplayInformation.HighContrast
            && !ToolStripDesignerUtils.DisplayInformation.LowResolution)
        {
            using Brush brush = new SolidBrush(Color.FromArgb(50, Color.White));
            paintEvent.Graphics.FillRectangle(brush, Control.ClientRectangle);
        }

        DrawBorder(paintEvent.Graphics);
    }

    protected override void PreFilterEvents(IDictionary events)
    {
        base.PreFilterEvents(events);

        if (Control.Parent is ToolStripContainer)
        {
            string[] noBrowseEvents =
            [
                nameof(ToolStripPanel.AutoSizeChanged),
                nameof(ToolStripPanel.BindingContextChanged),
                nameof(ToolStripPanel.CausesValidationChanged),
                nameof(ToolStripPanel.ChangeUICues),
                nameof(ToolStripPanel.DockChanged),
                nameof(ToolStripPanel.DragDrop),
                nameof(ToolStripPanel.DragEnter),
                nameof(ToolStripPanel.DragLeave),
                nameof(ToolStripPanel.DragOver),
                nameof(ToolStripPanel.EnabledChanged),
                nameof(ToolStripPanel.FontChanged),
                nameof(ToolStripPanel.ForeColorChanged),
                nameof(ToolStripPanel.GiveFeedback),
                nameof(ToolStripPanel.ImeModeChanged),
                nameof(ToolStripPanel.KeyDown),
                nameof(ToolStripPanel.KeyPress),
                nameof(ToolStripPanel.KeyUp),
                nameof(ToolStripPanel.LocationChanged),
                nameof(ToolStripPanel.MarginChanged),
                nameof(ToolStripPanel.MouseCaptureChanged),
                nameof(ToolStripPanel.Move),
                nameof(ToolStripPanel.QueryAccessibilityHelp),
                nameof(ToolStripPanel.QueryContinueDrag),
                nameof(ToolStripPanel.RegionChanged),
                nameof(ToolStripPanel.Scroll),
                nameof(ToolStripPanel.Validated),
                nameof(ToolStripPanel.Validating)
            ];

            for (int i = 0; i < noBrowseEvents.Length; i++)
            {
                EventDescriptor? eventDescriptor = (EventDescriptor?)events[noBrowseEvents[i]];
                if (eventDescriptor is not null)
                {
                    events[noBrowseEvents[i]] = TypeDescriptor.CreateEvent(eventDescriptor.ComponentType, eventDescriptor, BrowsableAttribute.No);
                }
            }
        }
    }

    /// <summary>
    ///  Set some properties to non-browsable depending on the Parent.
    ///  (StandAlone ToolStripPanel should support properties that are
    ///  usually hidden when its a part of ToolStripContainer)
    /// </summary>
    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);

        if (Control.Parent is ToolStripContainer)
        {
            properties.Remove("Modifiers");
            properties.Remove("Locked");
            properties.Remove("GenerateMember");

            string[] noBrowseProps =
            [
                nameof(ToolStripPanel.Anchor),
                nameof(ToolStripPanel.AutoSize),
                nameof(ToolStripPanel.Dock),
                nameof(ToolStripPanel.DockPadding),
                nameof(ToolStripPanel.Height),
                nameof(ToolStripPanel.Location),
                nameof(ToolStripPanel.Name),
                nameof(ToolStripPanel.Orientation),
                nameof(ToolStripPanel.Renderer),
                nameof(ToolStripPanel.RowMargin),
                nameof(ToolStripPanel.Size),
                nameof(ToolStripPanel.Visible),
                nameof(ToolStripPanel.Width),
            ];

            for (int i = 0; i < noBrowseProps.Length; i++)
            {
                PropertyDescriptor? propertyDescriptor = (PropertyDescriptor?)properties[noBrowseProps[i]];
                if (propertyDescriptor is not null)
                {
                    properties[noBrowseProps[i]] = TypeDescriptor.CreateProperty(propertyDescriptor.ComponentType, propertyDescriptor, BrowsableAttribute.No, DesignerSerializationVisibilityAttribute.Hidden);
                }
            }
        }

        string[] shadowProps = ["Padding", "Visible"];
        Attribute[] empty = [];
        for (int i = 0; i < shadowProps.Length; i++)
        {
            PropertyDescriptor? propertyDescriptor = (PropertyDescriptor?)properties[shadowProps[i]];
            if (propertyDescriptor is not null)
            {
                properties[shadowProps[i]] = TypeDescriptor.CreateProperty(typeof(ToolStripPanelDesigner), propertyDescriptor, empty);
            }
        }
    }

    /// <summary>
    ///  Should Serialize Padding
    /// </summary>
    private bool ShouldSerializePadding()
    {
        Padding padding = (Padding)ShadowProperties[nameof(Padding)]!;
        return !padding.Equals(s_defaultPadding);
    }

    /// <summary>
    ///  Should serialize for visible property
    /// </summary>
    private bool ShouldSerializeVisible()
    {
        return !Visible;
    }
}
