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

#nullable disable

using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Configuration;
using System.Drawing;
using System.Windows.Forms.Design.Behavior;

namespace System.Windows.Forms.Design;

/// <summary>
///  Designer for ToolStripDropDowns...just provides the Edit... verb.
/// </summary>
internal class ToolStripDropDownDesigner : ComponentDesigner
{
    private ISelectionService _selectionService;
    private MenuStrip _designMenu;
    private ToolStripMenuItem _menuItem;
    private IDesignerHost _host;
    private ToolStripDropDown _dropDown;
    private bool _selected;
    private ControlBodyGlyph _dummyToolStripGlyph;
    private uint _editingCollection; // non-zero if the collection editor is up for this ToolStrip or a child of it.
    internal ToolStripMenuItem _currentParent;
    private INestedContainer _nestedContainer; // NestedContainer for our DesignTime MenuItem.
    private UndoEngine _undoEngine;

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

    private bool AllowDrop
    {
        get => (bool)ShadowProperties[nameof(AllowDrop)];
        set => ShadowProperties[nameof(AllowDrop)] = value;
    }

    /// <summary>
    ///  Adds designer actions to the ActionLists collection.
    /// </summary>
    public override DesignerActionListCollection ActionLists
    {
        get
        {
            DesignerActionListCollection actionLists = new();
            actionLists.AddRange(base.ActionLists);
            ContextMenuStripActionList cmActionList = new(this);
            if (cmActionList is not null)
            {
                actionLists.Add(cmActionList);
            }

            // finally add the verbs for this component there...
            DesignerVerbCollection cmVerbs = Verbs;
            if (cmVerbs is not null && cmVerbs.Count != 0)
            {
                DesignerVerb[] cmverbsArray = new DesignerVerb[cmVerbs.Count];
                cmVerbs.CopyTo(cmverbsArray, 0);
                actionLists.Add(new DesignerActionVerbList(cmverbsArray));
            }

            return actionLists;
        }
    }

    /// <summary>
    ///  The ToolStripItems are the associated components. We want those to come with in any cut, copy operations.
    /// </summary>
    public override ICollection AssociatedComponents
    {
        get => ((ToolStrip)Component).Items;
    }

    // Dummy menuItem that is used for the contextMenuStrip design
    public ToolStripMenuItem DesignerMenuItem
    {
        get => _menuItem;
    }

    /// <summary>
    ///  Set by the ToolStripItemCollectionEditor when it's launched for this The Items property doesn't open another instance
    ///  of collectionEditor. We count this so that we can deal with nestings.
    /// </summary>
    internal bool EditingCollection
    {
        get => _editingCollection != 0;
        set
        {
            if (value)
            {
                _editingCollection++;
            }
            else
            {
                _editingCollection--;
            }
        }
    }

    // ContextMenuStrip if Inherited ACT as Readonly.
    protected override InheritanceAttribute InheritanceAttribute
    {
        get
        {
            if ((base.InheritanceAttribute == InheritanceAttribute.Inherited))
            {
                return InheritanceAttribute.InheritedReadOnly;
            }

            return base.InheritanceAttribute;
        }
    }

    /// <summary>
    ///  Prefilter this property so that we can set the right To Left on the Design Menu...
    /// </summary>
    private RightToLeft RightToLeft
    {
        get => _dropDown.RightToLeft;
        set
        {
            if (_menuItem is not null && _designMenu is not null && value != RightToLeft)
            {
                Rectangle bounds = Rectangle.Empty;
                try
                {
                    bounds = _dropDown.Bounds;
                    _menuItem.HideDropDown();
                    _designMenu.RightToLeft = value;
                    _dropDown.RightToLeft = value;
                }
                finally
                {
                    if (bounds != Rectangle.Empty)
                    {
                        GetService<BehaviorService>()?.Invalidate(bounds);
                    }

                    ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_host.GetDesigner(_menuItem);
                    itemDesigner?.InitializeDropDown();
                }
            }
        }
    }

    /// <summary>
    ///  shadowing the SettingsKey so we can default it to be RootComponent.Name + "." + Control.Name
    /// </summary>
    private string SettingsKey
    {
        get
        {
            if (string.IsNullOrEmpty((string)ShadowProperties[SettingsKeyName]))
            {
                if (Component is IPersistComponentSettings persistableComponent && _host is not null)
                {
                    if (persistableComponent.SettingsKey is null)
                    {
                        IComponent rootComponent = _host.RootComponent;
                        if (rootComponent is not null && rootComponent != persistableComponent)
                        {
                            ShadowProperties[SettingsKeyName] = $"{rootComponent.Site.Name}.{Component.Site.Name}";
                        }
                        else
                        {
                            ShadowProperties[SettingsKeyName] = Component.Site.Name;
                        }
                    }

                    persistableComponent.SettingsKey = ShadowProperties[SettingsKeyName] as string;
                    return persistableComponent.SettingsKey;
                }
            }

            return ShadowProperties[SettingsKeyName] as string;
        }
        set
        {
            ShadowProperties[SettingsKeyName] = value;
            if (Component is IPersistComponentSettings persistableComponent)
            {
                persistableComponent.SettingsKey = value;
            }
        }
    }

    // We have to add the glyphs ourselves.
    private void AddSelectionGlyphs(SelectionManager selectionManager, ISelectionService selectionService)
    {
        // If one or many of our items are selected then Add Selection Glyphs ourselves since this is a
        // ComponentDesigner which won't get called on the "GetGlyphs"
        ICollection selComponents = selectionService.GetSelectedComponents();
        GlyphCollection glyphs = [];
        foreach (object selComp in selComponents)
        {
            if (selComp is ToolStripItem item)
            {
                ToolStripItemDesigner itemDesigner = (ToolStripItemDesigner)_host.GetDesigner(item);
                itemDesigner?.GetGlyphs(ref glyphs, new ResizeBehavior(item.Site));
            }
        }

        // Get the Glyphs union Rectangle.
        if (glyphs.Count > 0)
        {
            // Add Glyphs and then invalidate the unionRect
            selectionManager.SelectionGlyphAdorner.Glyphs.AddRange(glyphs);
        }
    }

    // internal method called by outside designers to add glyphs for the ContextMenuStrip
    internal void AddSelectionGlyphs()
    {
        if (TryGetService(out SelectionManager selectionManager))
        {
            AddSelectionGlyphs(selectionManager, _selectionService);
        }
    }

    /// <summary>
    ///  Disposes of this designer.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Unhook our services
            if (_selectionService is not null)
            {
                _selectionService.SelectionChanged -= OnSelectionChanged;
                _selectionService.SelectionChanging -= OnSelectionChanging;
            }

            DisposeMenu();
            _designMenu?.Dispose();
            _designMenu = null;

            if (_dummyToolStripGlyph is not null)
            {
                _dummyToolStripGlyph = null;
            }

            _undoEngine?.Undone -= OnUndone;
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Disposes of this dummy menuItem and its designer..
    /// </summary>
    private void DisposeMenu()
    {
        HideMenu();
        if (_host.RootComponent is Control form)
        {
            if (_designMenu is not null)
            {
                form.Controls.Remove(_designMenu);
            }

            if (_menuItem is not null)
            {
                _nestedContainer?.Dispose();
                _nestedContainer = null;

                _menuItem.Dispose();
                _menuItem = null;
            }
        }
    }

    // private helper function to Hide the ContextMenu structure.
    private void HideMenu()
    {
        if (_menuItem is null)
        {
            return;
        }

        _selected = false;

        if (_host.RootComponent is not Control)
        {
            return;
        }

        _menuItem.DropDown.AutoClose = true;
        _menuItem.HideDropDown();
        _menuItem.Visible = false;

        // Hide the MenuItem DropDown.
        _designMenu.Visible = false;

        // Invalidate the Bounds..
        // toolStripAdornerWindowService.Invalidate(boundsToInvalidate);
        GetService<ToolStripAdornerWindowService>()?.Invalidate();

        // Query for the Behavior Service and Remove Glyph....
        if (TryGetService(out BehaviorService _))
        {
            if (_dummyToolStripGlyph is not null && TryGetService(out SelectionManager selectionManager))
            {
                if (selectionManager.BodyGlyphAdorner.Glyphs.Contains(_dummyToolStripGlyph))
                {
                    selectionManager.BodyGlyphAdorner.Glyphs.Remove(_dummyToolStripGlyph);
                }

                selectionManager.Refresh();
            }

            _dummyToolStripGlyph = null;
        }

        // Unhook all the events for DesignMenuItem
        if (_menuItem is not null)
        {
            if (_host.GetDesigner(_menuItem) is ToolStripMenuItemDesigner itemDesigner)
            {
                itemDesigner.UnHookEvents();
                itemDesigner.RemoveTypeHereNode(_menuItem);
            }
        }
    }

    /// <summary>
    ///  Initialize the item.
    /// </summary>
    public override void Initialize(IComponent component)
    {
        base.Initialize(component);
        _host = (IDesignerHost)GetService(typeof(IDesignerHost));

        // Add the EditService so that the ToolStrip can do its own Tab and Keyboard Handling
        ToolStripKeyboardHandlingService keyboardHandlingService = (ToolStripKeyboardHandlingService)GetService(typeof(ToolStripKeyboardHandlingService));
        keyboardHandlingService ??= new ToolStripKeyboardHandlingService(component.Site);

        // Add the InsituEditService so that the ToolStrip can do its own InSitu Editing
        ISupportInSituService inSituService = (ISupportInSituService)GetService(typeof(ISupportInSituService));
        inSituService ??= new ToolStripInSituService(Component.Site);

        _dropDown = (ToolStripDropDown)Component;
        _dropDown.Visible = false;

        // Shadow properties as we would change these for DropDowns at DesignTime.
        AutoClose = _dropDown.AutoClose;
        AllowDrop = _dropDown.AllowDrop;

        if (TryGetService(out _selectionService))
        {
            // first select the rootComponent and then hook on the events... but not if we are loading - VSWhidbey #484576
            if (_host is not null && !_host.Loading)
            {
                _selectionService.SetSelectedComponents(new IComponent[] { _host.RootComponent }, SelectionTypes.Replace);
            }

            _selectionService.SelectionChanging += OnSelectionChanging;
            _selectionService.SelectionChanged += OnSelectionChanged;
        }

        _designMenu = new MenuStrip
        {
            Visible = false,
            AutoSize = false,
            Dock = DockStyle.Top
        };

        _designMenu.Height = ScaleHelper.ScaleToInitialSystemDpi(_designMenu.Height);

        // Add MenuItem
        if (_host.RootComponent is Control form)
        {
            _menuItem = new ToolStripMenuItem
            {
                BackColor = SystemColors.Window,
                Name = Component.Site.Name
            };
            _menuItem.Text = (_dropDown is not null) ? _dropDown.GetType().Name : _menuItem.Name;
            _designMenu.Items.Add(_menuItem);
            form.Controls.Add(_designMenu);
            _designMenu.SendToBack();

            if (TryGetService(out _nestedContainer))
            {
                _nestedContainer.Add(_menuItem, "ContextMenuStrip");
            }
        }

        // EditorServiceContext is newed up to add Edit Items verb.
        new EditorServiceContext(this, TypeDescriptor.GetProperties(Component)["Items"], SR.ToolStripItemCollectionEditorVerb);

        // Use the UndoEngine.Undone to show the dropdown again
        if (_undoEngine is null && TryGetService(out _undoEngine))
        {
            _undoEngine.Undone += OnUndone;
        }
    }

    // Helper function to check if the ToolStripItem on the ContextMenu is selected.
    private bool IsContextMenuStripItemSelected(ISelectionService selectionService)
    {
        bool showDesignMenu = false;
        if (_menuItem is null)
        {
            return showDesignMenu;
        }

        ToolStripDropDown topmost = null;
        IComponent comp = (IComponent)selectionService.PrimarySelection;
        if (comp is null && _dropDown.Visible)
        {
            if (TryGetService(out ToolStripKeyboardHandlingService keyboardHandlingService))
            {
                comp = (IComponent)keyboardHandlingService.SelectedDesignerControl;
            }
        }

        // This case covers (a) and (b) above....
        if (comp is ToolStripDropDownItem currentItem)
        {
            if (currentItem == _menuItem)
            {
                topmost = _menuItem.DropDown;
            }
            else
            {
                ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_host.GetDesigner(comp);
                if (itemDesigner is not null)
                {
                    topmost = ToolStripItemDesigner.GetFirstDropDown(currentItem);
                }
            }
        }
        else if (comp is ToolStripItem item) // case (c)
        {
            if (item.GetCurrentParent() is not ToolStripDropDown parent)
            {
                // Try if the item has not laid out.
                parent = item.Owner as ToolStripDropDown;
            }

            if (parent is not null && parent.Visible)
            {
                ToolStripItem ownerItem = parent.OwnerItem;
                if (ownerItem is not null && ownerItem == _menuItem)
                {
                    topmost = _menuItem.DropDown;
                }
                else
                {
                    ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_host.GetDesigner(ownerItem);
                    if (itemDesigner is not null)
                    {
                        topmost = ToolStripItemDesigner.GetFirstDropDown((ToolStripDropDownItem)ownerItem);
                    }
                }
            }
        }

        if (topmost is not null)
        {
            ToolStripItem topMostItem = topmost.OwnerItem;
            if (topMostItem == _menuItem)
            {
                showDesignMenu = true;
            }
        }

        return showDesignMenu;
    }

    /// <summary>
    ///  Listens SelectionChanging to Show the MenuDesigner.
    /// </summary>
    private void OnSelectionChanging(object sender, EventArgs e)
    {
        ISelectionService selectionService = (ISelectionService)sender;
        // If we are no longer selected ... Hide the DropDown
        bool showDesignMenu = IsContextMenuStripItemSelected(selectionService) || Component.Equals(selectionService.PrimarySelection);
        if (_selected && !showDesignMenu)
        {
            HideMenu();
        }
    }

    /// <summary>
    ///  Listens SelectionChanged to Show the MenuDesigner.
    /// </summary>
    private void OnSelectionChanged(object sender, EventArgs e)
    {
        if (Component is null || _menuItem is null)
        {
            return;
        }

        ISelectionService selectionService = (ISelectionService)sender;
        // Select the container if TopLevel Dummy MenuItem is selected.
        if (selectionService.GetComponentSelected(_menuItem))
        {
            selectionService.SetSelectedComponents(new IComponent[] { Component }, SelectionTypes.Replace);
        }

        // return if DropDown is already is selected.
        if (Component.Equals(selectionService.PrimarySelection) && _selected)
        {
            return;
        }

        bool showDesignMenu = IsContextMenuStripItemSelected(selectionService) || Component.Equals(selectionService.PrimarySelection);

        if (showDesignMenu)
        {
            if (!_dropDown.Visible)
            {
                ShowMenu();
            }

            // Selection change would remove our Glyph from the BodyGlyph Collection.
            if (TryGetService(out SelectionManager selectionManager))
            {
                if (_dummyToolStripGlyph is not null)
                {
                    selectionManager.BodyGlyphAdorner.Glyphs.Insert(0, _dummyToolStripGlyph);
                }

                // Add our SelectionGlyphs and Invalidate.
                AddSelectionGlyphs(selectionManager, selectionService);
            }
        }
    }

    /// <summary>
    ///  Allows a designer to filter the set of properties the component it is designing will expose through
    ///  the TypeDescriptor object. This method is called immediately before its corresponding "Post" method.
    ///  If you are overriding this method you should call the base implementation before you perform your own filtering.
    /// </summary>
    protected override void PreFilterProperties(IDictionary properties)
    {
        base.PreFilterProperties(properties);
        PropertyDescriptor prop;
        string[] shadowProps = ["AutoClose", SettingsKeyName, "RightToLeft", "AllowDrop"];
        Attribute[] empty = [];
        for (int i = 0; i < shadowProps.Length; i++)
        {
            prop = (PropertyDescriptor)properties[shadowProps[i]];
            if (prop is not null)
            {
                properties[shadowProps[i]] = TypeDescriptor.CreateProperty(typeof(ToolStripDropDownDesigner), prop, empty);
            }
        }
    }

    // Reset Settings.
    public void ResetSettingsKey()
    {
        if (Component is IPersistComponentSettings)
        {
            SettingsKey = null;
        }
    }

    /// <summary>
    ///  Resets the ToolStripDropDown AutoClose to be the default padding
    /// </summary>
    private void ResetAutoClose()
    {
        ShadowProperties[nameof(AutoClose)] = true;
    }

    /// <summary>
    ///  Restores the ToolStripDropDown AutoClose to be the value set in the property grid.
    /// </summary>
    private void RestoreAutoClose()
    {
        _dropDown.AutoClose = (bool)ShadowProperties[nameof(AutoClose)];
    }

    /// <summary>
    ///  Resets the ToolStripDropDown AllowDrop to be the default padding
    /// </summary>
    private void ResetAllowDrop()
    {
        ShadowProperties[nameof(AllowDrop)] = false;
    }

    /// <summary>
    ///  Restores the ToolStripDropDown AllowDrop to be the value set in the property grid.
    /// </summary>
    private void RestoreAllowDrop()
    {
        _dropDown.AutoClose = (bool)ShadowProperties[nameof(AllowDrop)];
    }

    /// <summary>
    ///  Resets the ToolStripDropDown RightToLeft to be the default RightToLeft
    /// </summary>
    private void ResetRightToLeft()
    {
        RightToLeft = RightToLeft.No;
    }

    /// <summary>
    ///  Show the MenuDesigner; used by ToolStripMenuItemDesigner to show the menu when the user
    ///  selects the dropDown item through the PG or Document outline. The editor node will be selected by default.
    /// </summary>
    public void ShowMenu()
    {
        int count = _dropDown.Items.Count - 1;
        if (count >= 0)
        {
            ShowMenu(_dropDown.Items[count]);
        }
        else
        {
            ShowMenu(null);
        }
    }

    /// <summary>
    ///  Show the MenuDesigner; used by ToolStripMenuItemDesigner to show the menu when the user selects the dropDown
    ///  item through the PG or Document outline. The input toolstrip item will be selected.
    /// </summary>
    public void ShowMenu(ToolStripItem selectedItem)
    {
        if (_menuItem is null)
        {
            return;
        }

        Control parent = _designMenu.Parent;

        _selected = true;
        _designMenu.Visible = true;
        _designMenu.BringToFront();
        _menuItem.Visible = true;

        // Check if this is a design-time DropDown
        if (_currentParent is not null && _currentParent != _menuItem)
        {
            if (_host.GetDesigner(_currentParent) is ToolStripMenuItemDesigner ownerItemDesigner)
            {
                ownerItemDesigner.RemoveTypeHereNode(_currentParent);
            }
        }

        // Every time you hide/show .. set the DropDown of the designer MenuItem to the component dropDown being designed.
        _menuItem.DropDown = _dropDown;
        _menuItem.DropDown.OwnerItem = _menuItem;
        if (_dropDown.Items.Count > 0)
        {
            ToolStripItem[] items = new ToolStripItem[_dropDown.Items.Count];
            _dropDown.Items.CopyTo(items, 0);
            foreach (ToolStripItem toolItem in items)
            {
                if (toolItem is DesignerToolStripControlHost)
                {
                    _dropDown.Items.Remove(toolItem);
                }
            }
        }

        ToolStripMenuItemDesigner itemDesigner = (ToolStripMenuItemDesigner)_host.GetDesigner(_menuItem);
        if (TryGetService(out BehaviorService behaviorService))
        {
            // Show the contextMenu only if the dummy menuStrip is contained in the Form. Refer to VsWhidbey 484317 for more details.
            if (itemDesigner is not null && parent is not null)
            {
                Rectangle parentBounds = behaviorService.ControlRectInAdornerWindow(parent);
                Rectangle menuBounds = behaviorService.ControlRectInAdornerWindow(_designMenu);
                if (ToolStripDesigner.IsGlyphTotallyVisible(menuBounds, parentBounds))
                {
                    itemDesigner.InitializeDropDown();
                }
            }

            if (_dummyToolStripGlyph is null)
            {
                Point loc = behaviorService.ControlToAdornerWindow(_designMenu);
                Rectangle r = _designMenu.Bounds;
                r.Offset(loc);
                _dummyToolStripGlyph = new ControlBodyGlyph(r, Cursor.Current, _menuItem, new ContextMenuStripBehavior());
                GetService<SelectionManager>()?.BodyGlyphAdorner.Glyphs.Insert(0, _dummyToolStripGlyph);
            }

            if (selectedItem is not null)
            {
                GetService<ToolStripKeyboardHandlingService>().SelectedDesignerControl = selectedItem;
            }
        }
    }

    // Should the designer serialize the settings?
    private bool ShouldSerializeSettingsKey() => Component is IPersistComponentSettings persistableComponent
        && persistableComponent.SaveSettings
        && SettingsKey is not null;

    /// <summary>
    ///  Since we're shadowing ToolStripDropDown AutoClose, we get called here to determine whether or not to serialize
    /// </summary>
    private bool ShouldSerializeAutoClose() => (!(bool)ShadowProperties[nameof(AutoClose)]);

    /// <summary>
    ///  Since we're shadowing ToolStripDropDown AllowDrop, we get called here to determine whether or not to serialize
    /// </summary>
    private bool ShouldSerializeAllowDrop() => AllowDrop;

    /// <summary>
    ///  Since we're shadowing ToolStripDropDown RightToLeft, we get called here to determine whether or not to serialize
    /// </summary>
    private bool ShouldSerializeRightToLeft() => RightToLeft != RightToLeft.No;

    /// <summary>
    ///  ResumeLayout after Undone.
    /// </summary>
    private void OnUndone(object source, EventArgs e)
    {
        if (_selectionService is not null && Component.Equals(_selectionService.PrimarySelection))
        {
            HideMenu();
            ShowMenu();
        }
    }

    /// <summary>
    ///  This is an internal class which provides the Behavior for our MenuStrip Body Glyph.
    ///  This will just eat the MouseUps.
    /// </summary>
    internal class ContextMenuStripBehavior : Behavior.Behavior
    {
        internal ContextMenuStripBehavior()
        {
        }

        public override bool OnMouseUp(Glyph g, MouseButtons button) => button == MouseButtons.Left;
    }
}
