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

using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design.Behavior;

namespace System.ComponentModel.Design;

internal partial class DesignerActionUI
{
    internal class DesignerActionToolStripDropDown : ToolStripDropDown
    {
        private readonly IWin32Window? _mainParentWindow;
        private ToolStripControlHost? _panel;
        private readonly DesignerActionUI _designerActionUI;
        private bool _cancelClose;
        private Glyph? _relatedGlyph;

        public DesignerActionToolStripDropDown(DesignerActionUI designerActionUI, IWin32Window? mainParentWindow)
        {
            _mainParentWindow = mainParentWindow;
            _designerActionUI = designerActionUI;
        }

        public DesignerActionPanel? CurrentPanel => _panel?.Control as DesignerActionPanel;

        // we're not topmost because we can show modal editors above us.
        protected override bool TopMost => false;

        public void UpdateContainerSize()
        {
            if (CurrentPanel is not null)
            {
                Size panelSize = CurrentPanel.GetPreferredSize(new Size(150, int.MaxValue));
                if (CurrentPanel.Size == panelSize)
                {
                    // If the panel size didn't actually change, we still have to force a call to PerformLayout to make
                    // sure that controls get repositioned properly within the panel. The issue arises because we did a
                    // measure-only Layout that determined some sizes, and then we end up painting with those values even
                    // though there wasn't an actual Layout performed.
                    CurrentPanel.PerformLayout();
                }
                else
                {
                    CurrentPanel.Size = panelSize;
                }

                ClientSize = panelSize;
            }
        }

        public void CheckFocusIsRight()
        {
            // fix to get the focus to NOT stay on ContainerControl
            HWND focusedControl = PInvoke.GetFocus();
            if (focusedControl == Handle)
            {
                _panel?.Focus();
            }

            focusedControl = PInvoke.GetFocus();
            if (CurrentPanel is not null && CurrentPanel.Handle == focusedControl)
            {
                CurrentPanel.SelectNextControl(null, true, true, true, true);
            }
        }

        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout(levent);

            UpdateContainerSize();
        }

        protected override void OnClosing(ToolStripDropDownClosingEventArgs e)
        {
            if (e.CloseReason == ToolStripDropDownCloseReason.AppFocusChange && _cancelClose)
            {
                _cancelClose = false;
                e.Cancel = true;
            }

            // When we get closing event as a result of an activation change, pre-populate e.Cancel based on why we're exiting.
            // - if it's a modal window that's owned by VS don't exit
            // - if it's a window that's owned by the toolstrip dropdown don't exit
            else if (e.CloseReason is ToolStripDropDownCloseReason.AppFocusChange or ToolStripDropDownCloseReason.AppClicked)
            {
                HWND hwndActivating = PInvoke.GetActiveWindow();
                if (Handle == hwndActivating && e.CloseReason == ToolStripDropDownCloseReason.AppClicked)
                {
                    e.Cancel = false;
                }
                else if (WindowOwnsWindow((HWND)Handle, hwndActivating))
                {
                    // We're being de-activated for someone owned by the panel.
                    e.Cancel = true;
                }
                else if (_mainParentWindow is not null && !WindowOwnsWindow((HWND)_mainParentWindow.Handle, hwndActivating))
                {
                    if (IsWindowEnabled(_mainParentWindow.Handle))
                    {
                        // The activated windows is not a child/owned windows of the main top level windows let toolstripdropdown handle this
                        e.Cancel = false;
                    }
                    else
                    {
                        e.Cancel = true;
                    }

                    base.OnClosing(e);
                    return;
                }

                // What's the owner of the windows being activated?
                HWND parent = (HWND)PInvokeCore.GetWindowLong(
                    new HandleRef<HWND>(this, hwndActivating),
                    WINDOW_LONG_PTR_INDEX.GWL_HWNDPARENT);

                // is it currently disabled (ie, the activating windows is in modal mode)
                if (!IsWindowEnabled(parent))
                {
                    // We are in a modal case
                    e.Cancel = true;
                }
            }

            base.OnClosing(e);
        }

        public void SetDesignerActionPanel(DesignerActionPanel panel, Glyph relatedGlyph)
        {
            if (_panel is not null && panel == (DesignerActionPanel)_panel.Control)
            {
                return;
            }

            Debug.Assert(relatedGlyph is not null, "related glyph cannot be null");
            _relatedGlyph = relatedGlyph;
            panel.SizeChanged += PanelResized;

            if (_panel is not null)
            {
                Items.Remove(_panel);
                _panel.Dispose();
                _panel = null;
            }

            _panel = new ToolStripControlHost(panel)
            {
                // We don't want a margin
                Margin = Padding.Empty,
                Size = panel.Size
            };

            SuspendLayout();
            Size = panel.Size;
            Items.Add(_panel);
            ResumeLayout();
            if (Visible)
            {
                CheckFocusIsRight();
            }
        }

        private void PanelResized(object? sender, EventArgs e)
        {
            Control ctrl = (Control)sender!;
            if (Size.Width != ctrl.Size.Width || Size.Height != ctrl.Size.Height)
            {
                SuspendLayout();
                Size = ctrl.Size;
                _panel?.Size = ctrl.Size;

                _designerActionUI.UpdateDAPLocation(component: null, _relatedGlyph as DesignerActionGlyph);
                ResumeLayout();
            }
        }

        protected override void SetVisibleCore(bool visible)
        {
            base.SetVisibleCore(visible);
            if (visible)
            {
                CheckFocusIsRight();
            }
        }

        /// <summary>
        ///  Determines whether a given window (specified using native window handle) is a descendant of this control.
        ///  This catches both contained descendants and 'owned' windows such as modal dialogs. Using window handles
        ///  rather than Control objects allows it to catch un-managed windows as well.
        /// </summary>
        private static bool WindowOwnsWindow(HWND hWndOwner, HWND hWndDescendant)
        {
            if (hWndDescendant == hWndOwner)
            {
                return true;
            }

            while (!hWndDescendant.IsNull)
            {
                hWndDescendant = (HWND)PInvokeCore.GetWindowLong(hWndDescendant, WINDOW_LONG_PTR_INDEX.GWL_HWNDPARENT);
                if (hWndDescendant.IsNull)
                {
                    return false;
                }

                if (hWndDescendant == hWndOwner)
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsWindowEnabled(IntPtr handle)
        {
            int style = (int)PInvokeCore.GetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
            return (style & (int)WINDOW_STYLE.WS_DISABLED) == 0;
        }

        private void WmActivate(ref Message m)
        {
            if ((nint)m.WParamInternal == PInvoke.WA_INACTIVE)
            {
                HWND hwndActivating = (HWND)m.LParamInternal;
                _cancelClose = WindowOwnsWindow((HWND)Handle, hwndActivating);
            }
            else
            {
                _cancelClose = false;
            }

            base.WndProc(ref m);
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.MsgInternal)
            {
                case PInvokeCore.WM_ACTIVATE:
                    WmActivate(ref m);
                    return;
            }

            base.WndProc(ref m);
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            // since we're not hosted in a form we need to do the same logic as Form.cs.
            // If we get an enter key we need to find the current focused control.
            // if it's a button, we click it and return that we handled the message
            if (keyData == Keys.Enter)
            {
                HWND focusedControlPtr = PInvoke.GetFocus();
                Control? focusedControl = FromChildHandle(focusedControlPtr);
                if (focusedControl is IButtonControl button)
                {
                    button.PerformClick();
                    return true;
                }
            }

            return base.ProcessDialogKey(keyData);
        }
    }
}
