// 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 opreations.
    /// </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 doesnt 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 -= new EventHandler(OnSelectionChanged);
                _selectionService.SelectionChanging -= new EventHandler(OnSelectionChanging);
            }

            DisposeMenu();
            if (_designMenu is not null)
            {
                _designMenu.Dispose();
                _designMenu = null;
            }

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

            if (_undoEngine is not null)
            {
                _undoEngine.Undone -= new EventHandler(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)
            {
                if (_nestedContainer 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 += new EventHandler(OnSelectionChanging);
            _selectionService.SelectionChanged += new EventHandler(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 += new EventHandler(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;
    }
}
