// 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 System.Text;

namespace System.Windows.Forms.PropertyGridInternal;

/// <summary>
///  The commands pane optionally shown at the bottom of the <see cref="PropertyGrid"/>. This pane is used
///  to host links to <see cref="DesignerVerb"/>s associated with the <see cref="PropertyGrid"/>'s selected
///  object(s).
/// </summary>
/// <remarks>
///  <para>
///   <see cref="DesignerVerb"/> commands are found by looking for services on <see cref="IComponent.Site"/>.
///   Specifically, it first looks for <see cref="IMenuCommandService"/>, then falls back to looking for
///   <see cref="IDesigner.Verbs"/> from any associated <see cref="IDesignerHost"/>. Note that
///   <see cref="PropertyGrid"/> will not set commands when it is in design mode.
///  </para>
///  <para>
///   <see cref="PropertyGrid.CommandsVisible"/> controls the visibility of this control.
///  </para>
/// </remarks>
internal partial class CommandsPane : PropertyGrid.SnappableControl
{
    private object? _component;
    private DesignerVerb[]? _verbs;
    private LinkLabel? _label;
    private bool _allowVisible = true;
    private int _optimalHeight = -1;

    internal CommandsPane(PropertyGrid owner) : base(owner)
    {
        Text = "Command Pane";
    }

    public virtual bool AllowVisible
    {
        get => _allowVisible;
        set
        {
            if (_allowVisible != value)
            {
                _allowVisible = value;
                Visible = value && WouldBeVisible;
            }
        }
    }

    /// <inheritdoc />
    protected override AccessibleObject CreateAccessibilityInstance()
        => new CommandsPaneAccessibleObject(this, OwnerPropertyGrid);

    public override Rectangle DisplayRectangle
    {
        get
        {
            Size size = ClientSize;
            return new Rectangle(4, 4, size.Width - 8, size.Height - 8);
        }
    }

    public LinkLabel Label
    {
        get
        {
            if (_label is null)
            {
                _label = new LinkLabel
                {
                    Dock = DockStyle.Fill,
                    LinkBehavior = LinkBehavior.AlwaysUnderline,

                    // Use default LinkLabel colors for regular, active, and visited.
                    DisabledLinkColor = SystemColors.ControlDark
                };

                _label.LinkClicked += LinkClicked;
                Controls.Add(_label);
            }

            return _label;
        }
    }

    public virtual bool WouldBeVisible => _component is not null;

    public override int GetOptimalHeight(int width)
    {
        if (_optimalHeight == -1)
        {
            int lineHeight = (int)(1.5 * Font.Height);
            int verbCount = 0;
            if (_verbs is not null)
            {
                verbCount = _verbs.Length;
            }

            _optimalHeight = verbCount * lineHeight + 8;
        }

        return _optimalHeight;
    }

    public override int SnapHeightRequest(int request) => request;

    /// <inheritdoc />
    internal override bool SupportsUiaProviders => true;

    private void LinkClicked(object? sender, LinkLabelLinkClickedEventArgs e)
    {
        try
        {
            if (e.Link is null || !e.Link.Enabled)
            {
                return;
            }

            ((DesignerVerb?)e.Link.LinkData)?.Invoke();
        }
        catch (Exception ex)
        {
            RTLAwareMessageBox.Show(
                this,
                ex.Message,
                SR.PBRSErrorTitle,
                MessageBoxButtons.OK,
                MessageBoxIcon.Warning,
                MessageBoxDefaultButton.Button1,
                0);
        }
    }

    private void OnCommandChanged(object? sender, EventArgs e) => InitializeLabelLinks();

    protected override void OnGotFocus(EventArgs e)
    {
        Label.Focus();
        Label.Invalidate();
    }

    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        _optimalHeight = -1;
    }

    internal void SetColors(Color background, Color normalText, Color link, Color activeLink, Color visitedLink, Color disabledLink)
    {
        Label.BackColor = background;
        Label.ForeColor = normalText;
        Label.LinkColor = link;
        Label.ActiveLinkColor = activeLink;
        Label.VisitedLinkColor = visitedLink;
        Label.DisabledLinkColor = disabledLink;
    }

    public void FocusLabel() => Label.Focus();

    public virtual void SetVerbs(object? component, DesignerVerb[]? verbs)
    {
        if (_verbs is not null)
        {
            for (int i = 0; i < _verbs.Length; i++)
            {
                _verbs[i].CommandChanged -= OnCommandChanged;
            }

            _component = null;
            _verbs = null;
        }

        if (component is null || verbs is null || verbs.Length == 0)
        {
            Visible = false;
            Label.Links.Clear();
            Label.Text = null;
        }
        else
        {
            _component = component;
            _verbs = verbs;

            for (int i = 0; i < verbs.Length; i++)
            {
                verbs[i].CommandChanged += OnCommandChanged;
            }

            if (_allowVisible)
            {
                Visible = true;
            }

            InitializeLabelLinks();
        }

        _optimalHeight = -1;
    }

    private void InitializeLabelLinks()
    {
        Label.Links.Clear();
        StringBuilder sb = new();
        Point[] links = new Point[_verbs!.Length];
        int index = 0;
        bool firstVerb = true;

        for (int i = 0; i < _verbs.Length; i++)
        {
            if (_verbs[i].Visible && _verbs[i].Supported)
            {
                if (!firstVerb)
                {
                    sb.Append(Application.CurrentCulture.TextInfo.ListSeparator);
                    sb.Append(' ');
                    index += 2;
                }

                string name = _verbs[i].Text;

                links[i] = new Point(index, name.Length);
                sb.Append(name);
                index += name.Length;
                firstVerb = false;
            }
        }

        Label.Text = sb.ToString();

        for (int i = 0; i < _verbs.Length; i++)
        {
            if (_verbs[i].Visible && _verbs[i].Supported)
            {
                LinkLabel.Link link = Label.Links.Add(links[i].X, links[i].Y, _verbs[i]);
                if (!_verbs[i].Enabled)
                {
                    link.Enabled = false;
                }
            }
        }
    }
}
