// 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.Design;
using System.ComponentModel;
using System.Collections;

namespace System.Windows.Forms.Design;

/// <summary>
///  This class handles all design time behavior for the list box class.
///  It adds a sample item to the list box at design time.
/// </summary>
internal class ListBoxDesigner : ControlDesigner
{
    private DesignerActionListCollection? _actionLists;

    public bool IntegralHeight
    {
        get => (bool)ShadowProperties[nameof(IntegralHeight)]!;
        set
        {
            ShadowProperties[nameof(IntegralHeight)] = value;

            ListBox listBox = (ListBox)Component;
            if (listBox.Dock is not DockStyle.Fill
                and not DockStyle.Left
                and not DockStyle.Right)
            {
                listBox.IntegralHeight = value;
            }
        }
    }

    public DockStyle Dock
    {
        get
        {
            return ((ListBox)Component).Dock;
        }
        set
        {
            ListBox listBox = (ListBox)Component;
            if (value is DockStyle.Fill or DockStyle.Left or DockStyle.Right)
            {
                // VSO 159543
                // Allow partial listbox item displays so that we don't try to resize the listbox after we dock.
                listBox.IntegralHeight = false;
                listBox.Dock = value;
            }
            else
            {
                listBox.Dock = value;
                // VSO 159543
                // Restore the IntegralHeight after we dock. Order is necessary here. Setting IntegralHeight will
                // potentially resize the control height, but we don't want to base the height on the dock.
                // Instead, undock the control first, so the IntegralHeight is based on the restored size.
                listBox.IntegralHeight = (bool)ShadowProperties[nameof(IntegralHeight)]!;
            }
        }
    }

    protected override void PreFilterProperties(IDictionary properties)
    {
        if (properties[nameof(IntegralHeight)] is PropertyDescriptor integralHeightProp)
        {
            properties[nameof(IntegralHeight)] = TypeDescriptor.CreateProperty(typeof(ListBoxDesigner), integralHeightProp, []);
        }

        if (properties[nameof(Dock)] is PropertyDescriptor dockProp)
        {
            properties[nameof(Dock)] = TypeDescriptor.CreateProperty(typeof(ListBoxDesigner), dockProp, []);
        }

        base.PreFilterProperties(properties);
    }

    /// <summary>
    ///  Destroys this designer.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Now, hook the component rename event so we can update the text in the list box.
            if (TryGetService<IComponentChangeService>(out IComponentChangeService? componentChangeService))
            {
                componentChangeService.ComponentRename -= OnComponentRename;
                componentChangeService.ComponentChanged -= OnComponentChanged;
            }
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Called by the host when we're first initialized.
    /// </summary>
    public override void Initialize(IComponent component)
    {
        base.Initialize(component);

        if (component is ListBox listBox)
        {
            IntegralHeight = listBox.IntegralHeight;
        }

        AutoResizeHandles = true;

        // Now, hook the component rename event so we can update the text in the list box.
        if (TryGetService(out IComponentChangeService? componentChangeService))
        {
            componentChangeService.ComponentRename += OnComponentRename;
            componentChangeService.ComponentChanged += OnComponentChanged;
        }
    }

    public override void InitializeNewComponent(IDictionary? defaultValues)
    {
        base.InitializeNewComponent(defaultValues);

        // in Whidbey, formattingEnabled is true
        ((ListBox)Component).FormattingEnabled = true;

        // VSWhidbey 497239 - Setting FormattingEnabled clears the text we set in
        // OnCreateHandle so let's set it here again. We need to keep setting the text in
        // OnCreateHandle, otherwise we introduce VSWhidbey 498162.
        if (TypeDescriptorHelper.TryGetPropertyValue(Component, nameof(ListBox.Name), out string? name)
            && name is not null)
        {
            UpdateControlName(name);
        }
    }

    /// <summary>
    ///  Raised when a component's name changes. Here we update the contents of the list box
    ///  if we are displaying the component's name in it.
    /// </summary>
    private void OnComponentRename(object? sender, ComponentRenameEventArgs e)
    {
        if (e.Component == Component && e.NewName is not null)
        {
            UpdateControlName(e.NewName);
        }
    }

    /// <summary>
    ///  Raised when ComponentChanges. We listen to this to check if the "Items" propertychanged.
    ///  and if so .. then update the Text within the ListBox.
    /// </summary>
    private void OnComponentChanged(object? sender, ComponentChangedEventArgs e)
    {
        if (e.Component == Component
            && e.Member is not null
            && e.Member.Name == nameof(ListBox.Items)
            && TypeDescriptorHelper.TryGetPropertyValue(Component, nameof(ListBox.Name), out string? name)
            && name is not null)
        {
            UpdateControlName(name);
        }
    }

    /// <summary>
    ///  This is called immediately after the control handle has been created.
    /// </summary>
    protected override void OnCreateHandle()
    {
        base.OnCreateHandle();

        if (TypeDescriptorHelper.TryGetPropertyValue(Component, nameof(ListBox.Name), out string? name)
            && name is not null)
        {
            UpdateControlName(name);
        }
    }

    /// <summary>
    ///  Updates the name being displayed on this control. This will do nothing if
    ///  the control has items in it.
    /// </summary>
    private void UpdateControlName(string name)
    {
        ListBox listBox = (ListBox)Control;
        if (listBox.IsHandleCreated && listBox.Items.Count == 0)
        {
            PInvokeCore.SendMessage(listBox, PInvoke.LB_RESETCONTENT);
            PInvokeCore.SendMessage(listBox, PInvoke.LB_ADDSTRING, 0, name);
        }
    }

    public override DesignerActionListCollection ActionLists
    {
        get
        {
            if (_actionLists is null)
            {
                _actionLists = new DesignerActionListCollection();
                if (Component is CheckedListBox)
                {
                    _actionLists.Add(new ListControlUnboundActionList(this));
                }
                else
                {
                    // TODO: investigate necessity and possibility of porting databinding infra
#if DESIGNER_DATABINDING
                    // Requires:
                    // - System.Windows.Forms.Design.DataMemberFieldEditor
                    // - System.Windows.Forms.Design.DesignBindingConverter
                    // - System.Windows.Forms.Design.DesignBindingEditor
                    //
                    _actionLists.Add(new ListControlBoundActionList(this));
#else
                    _actionLists.Add(new ListControlUnboundActionList(this));
#endif
                }
            }

            return _actionLists;
        }
    }
}
