// 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.Runtime.CompilerServices;
using System.Windows.Forms.Automation;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
using IAccessible = Accessibility.IAccessible;

namespace System.Windows.Forms.Tests.AccessibleObjects;

public class Control_ControlAccessibleObjectTests
{
    // These controls return an empty "AccessKey" property because they have a ControlStyles.UseTextForAccessibility flag
    // with "false" value, which prohibits the use of text to create the AccessKey.
    // This behavior is consistent with .NET Framework 4.7.2
    private static readonly Type[] s_controlsNotUseTextForAccessibility =
    [
        typeof(CheckedListBox),
        typeof(ComboBox),
        typeof(DataGridViewComboBoxEditingControl),
        typeof(DataGridViewTextBoxEditingControl),
        typeof(DateTimePicker),
        typeof(DomainUpDown),
        typeof(HScrollBar),
        typeof(ListBox),
        typeof(ListView),
        typeof(MaskedTextBox),
        typeof(NumericUpDown),
        typeof(ProgressBar),
        typeof(RichTextBox),
        typeof(TextBox),
        typeof(TrackBar),
        typeof(TreeView),
        typeof(VScrollBar),
        typeof(WebBrowser),
    ];

    // These controls have special conditions for setting the Text property.
    // Please check if the control type isn't contained here in cases where text change is needed
    // otherwise an error can be thrown.
    private static readonly Type[] s_controlsIgnoringTextChangesForTests =
    [
        typeof(DateTimePicker), typeof(WebBrowser)
    ];

#pragma warning disable WFDEV006 // Type or member is obsolete
    private static readonly Type[] s_unsupportedControls =
    [
        // These controls are implemented in .NET Framework only, NET10+ ships hollowed out types that can't be instantiated.
        typeof(DataGrid),
        typeof(StatusBar),
        typeof(ToolBar),
        typeof(DataGridTextBox)
    ];
#pragma warning restore WFDEV006

    [WinFormsFact]
    public void ControlAccessibleObject_Ctor_ControlWithoutHandle()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.False(ownerControl.IsHandleCreated);

        Assert.True(accessibleObject.Bounds.X >= 0);
        Assert.True(accessibleObject.Bounds.Y >= 0);
        Assert.Equal(0, accessibleObject.Bounds.Width);
        Assert.Equal(0, accessibleObject.Bounds.Height);
        Assert.Equal(IntPtr.Zero, accessibleObject.HandleInternal);
        Assert.Null(accessibleObject.DefaultAction);
        Assert.Null(accessibleObject.Description);
        Assert.Null(accessibleObject.Help);
        Assert.Null(accessibleObject.KeyboardShortcut);
        Assert.Null(accessibleObject.Name);
        Assert.Same(ownerControl, accessibleObject.Owner);
        Assert.Null(accessibleObject.Parent);
        Assert.Equal(AccessibleRole.None, accessibleObject.Role);
        Assert.Equal(AccessibleStates.None, accessibleObject.State);
        Assert.Equal(string.Empty, accessibleObject.Value);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_Ctor_ControlWithHandle()
    {
        using Control ownerControl = new();
        Assert.NotEqual(IntPtr.Zero, ownerControl.Handle);
        int invalidatedCallCount = 0;
        ownerControl.Invalidated += (sender, e) => invalidatedCallCount++;
        int styleChangedCallCount = 0;
        ownerControl.StyleChanged += (sender, e) => styleChangedCallCount++;
        int createdCallCount = 0;
        ownerControl.HandleCreated += (sender, e) => createdCallCount++;

        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.True(ownerControl.IsHandleCreated);
        Assert.Equal(0, invalidatedCallCount);
        Assert.Equal(0, styleChangedCallCount);
        Assert.Equal(0, createdCallCount);

        Assert.True(accessibleObject.Bounds.X >= 0);
        Assert.True(accessibleObject.Bounds.Y >= 0);
        Assert.Equal(0, accessibleObject.Bounds.Width);
        Assert.Equal(0, accessibleObject.Bounds.Height);
        Assert.Null(accessibleObject.DefaultAction);
        Assert.Null(accessibleObject.Description);
        Assert.Equal(ownerControl.Handle, accessibleObject.Handle);
        Assert.Null(accessibleObject.Help);
        Assert.Null(accessibleObject.KeyboardShortcut);
        Assert.Null(accessibleObject.Name);
        Assert.Same(ownerControl, accessibleObject.Owner);
        Assert.NotNull(accessibleObject.Parent);
        Assert.Equal(AccessibleRole.Client, accessibleObject.Role);
        Assert.Equal(AccessibleStates.Focusable, accessibleObject.State);
        Assert.Null(accessibleObject.Value);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_Ctor_NullControl_ThrowsArgumentNullException()
    {
        Assert.Throws<ArgumentNullException>("ownerControl", () => new Control.ControlAccessibleObject(null));
    }

    [WinFormsFact]
    public void ControlAccessibleObject_created_via_owner_ensure_handle_set_when_owner_created()
    {
        using Control ownerControl = new();

        AccessibleObject accessibleObject = ownerControl.AccessibilityObject;
        Assert.IsType<Control.ControlAccessibleObject>(accessibleObject);

        Control.ControlAccessibleObject controlAccessibleObject = (Control.ControlAccessibleObject)accessibleObject;
        Assert.False(ownerControl.IsHandleCreated);
        Assert.Equal(IntPtr.Zero, controlAccessibleObject.HandleInternal);

        // force the owner control to create its handle
        ownerControl.CreateControl();

        Assert.True(ownerControl.IsHandleCreated);
        Assert.Equal(ownerControl.Handle, controlAccessibleObject.Handle);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_created_via_owner_ensure_handle_reset_when_owner_destroyed()
    {
        using Control ownerControl = new();
        ownerControl.CreateControl();

        AccessibleObject accessibleObject = ownerControl.AccessibilityObject;
        Assert.IsType<Control.ControlAccessibleObject>(accessibleObject);

        Control.ControlAccessibleObject controlAccessibleObject = (Control.ControlAccessibleObject)accessibleObject;
        Assert.True(ownerControl.IsHandleCreated);
        Assert.Equal(ownerControl.Handle, controlAccessibleObject.Handle);

        ownerControl.Dispose();

        Assert.False(ownerControl.IsHandleCreated);
        Assert.Equal(IntPtr.Zero, controlAccessibleObject.Handle);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_created_detached_owner_ensure_handle_set_when_owner_created()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.False(ownerControl.IsHandleCreated);
        Assert.Equal(IntPtr.Zero, accessibleObject.HandleInternal);

        // force the owner control to create its handle
        ownerControl.CreateControl();

        Assert.True(ownerControl.IsHandleCreated);
        Assert.Equal(ownerControl.Handle, accessibleObject.Handle);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_created_detached_owner_ensure_handle_reset_when_owner_destroyed()
    {
        using Control ownerControl = new();
        var controlAccessibleObject = new Control.ControlAccessibleObject(ownerControl);
        ownerControl.CreateControl();

        Assert.True(ownerControl.IsHandleCreated);
        Assert.Equal(ownerControl.Handle, controlAccessibleObject.Handle);

        ownerControl.Dispose();

        Assert.False(ownerControl.IsHandleCreated);

        // NB: Detached object, so we don't get notifications
        Assert.NotEqual(IntPtr.Zero, controlAccessibleObject.Handle);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_DefaultAction_GetWithAccessibleDefaultActionDescription_ReturnsExpected(string accessibleDefaultActionDescription)
    {
        using Control ownerControl = new()
        {
            AccessibleDefaultActionDescription = accessibleDefaultActionDescription
        };
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(accessibleDefaultActionDescription, accessibleObject.DefaultAction);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_GetPropertyValue_LegacyIAccessibleDefaultActionPropertyId_ReturnsExpected(string accessibleDefaultActionDescription)
    {
        using Control ownerControl = new()
        {
            AccessibleDefaultActionDescription = accessibleDefaultActionDescription
        };
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        if (!string.IsNullOrEmpty(accessibleDefaultActionDescription))
        {
            Assert.Equal(accessibleDefaultActionDescription, ((BSTR)accessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_LegacyIAccessibleDefaultActionPropertyId)).ToStringAndFree());
        }
        else
        {
            Assert.Equal(VARIANT.Empty, accessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_LegacyIAccessibleDefaultActionPropertyId));
        }
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_Description_GetWithAccessibleDescription_ReturnsExpected(string accessibleDescription)
    {
        using Control ownerControl = new()
        {
            AccessibleDescription = accessibleDescription
        };
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(accessibleDescription, accessibleObject.Description);
    }

    public static TheoryData<object> Handle_Set_DataTestData()
    {
        return
        [
            null,
            IntPtr.Zero,
            new IntPtr(-1),
            new IntPtr(1),
            new IntPtr(250)
        ];
    }

    [WinFormsTheory]
    [MemberData(nameof(Handle_Set_DataTestData))]
    public void ControlAccessibleObject_Handle_Set_Success(object testValue)
    {
        IntPtr value;
        if (testValue is null)
        {
            using Control control = new();
            value = control.Handle;
        }
        else
        {
            value = (IntPtr)testValue;
        }

        using Control ownerControl = new();
        ownerControl.CreateControl();
        Assert.True(ownerControl.IsHandleCreated);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.True(ownerControl.IsHandleCreated);

        // Set value.
        accessibleObject.Handle = value;
        Assert.Equal(value, accessibleObject.Handle);

        // Set same value.
        accessibleObject.Handle = value;
        Assert.Equal(value, accessibleObject.Handle);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_Help_GetWithQueryAccessibilityHelpEvent_Success(string result)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);

        int callCount = 0;
        void handler(object sender, QueryAccessibilityHelpEventArgs e)
        {
            Assert.Same(ownerControl, sender);
            Assert.Null(e.HelpKeyword);
            Assert.Null(e.HelpNamespace);
            Assert.Null(e.HelpString);

            e.HelpString = result;
            callCount++;
        }

        ownerControl.QueryAccessibilityHelp += handler;

        // Get with handler.
        Assert.Equal(result, accessibleObject.Help);
        Assert.Equal(1, callCount);

        // Get again.
        Assert.Equal(result, accessibleObject.Help);
        Assert.Equal(2, callCount);

        // Remove handler.
        ownerControl.QueryAccessibilityHelp -= handler;
        Assert.Null(accessibleObject.Help);
        Assert.Equal(2, callCount);
    }

    public static IEnumerable<object[]> KeyboardShortcut_Get_TestData()
    {
        yield return new object[] { true, null, null };
        yield return new object[] { true, string.Empty, null };
        yield return new object[] { true, "Text", null };
        yield return new object[] { false, null, null };
        yield return new object[] { false, string.Empty, null };
        yield return new object[] { false, "Text", null };

        // With mnemonic.
        yield return new object[] { true, "&", null };
        yield return new object[] { true, "&&a", null };
        yield return new object[] { true, "a&", null };
        yield return new object[] { true, "a&b", "Alt+b" };
        yield return new object[] { true, "a&bc", "Alt+b" };
        yield return new object[] { false, "&", null };
        yield return new object[] { false, "&&a", null };
        yield return new object[] { false, "a&", null };
        yield return new object[] { false, "a&b", null };
        yield return new object[] { false, "a&bc", null };
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_Get_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_Get_ReturnsExpected(bool useTextForAccessibility, string text, string expected)
    {
        using SubControl ownerControl = new()
        {
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_Get_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_GetWithNonContainerControlParent_IgnoresParent(bool useTextForAccessibility, string text, string expected)
    {
        using Control parent = new();
        using SubControl ownerControl = new()
        {
            Parent = parent,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_Get_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_GetWithContainerControlParentWithoutPreviousLabel_IgnoresParent(bool useTextForAccessibility, string text, string expected)
    {
        using ContainerControl parent = new();
        using SubControl ownerControl = new()
        {
            Parent = parent,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    public static IEnumerable<object[]> KeyboardShortcut_GetWithPreviousLabel_TestData()
    {
        yield return new object[] { true, null, null, null };
        yield return new object[] { true, null, string.Empty, null };
        yield return new object[] { true, null, "LabelText", null };
        yield return new object[] { true, string.Empty, null, null };
        yield return new object[] { true, string.Empty, string.Empty, null };
        yield return new object[] { true, string.Empty, "LabelText", null };
        yield return new object[] { true, "Text", null, null };
        yield return new object[] { true, "Text", string.Empty, null };
        yield return new object[] { true, "Text", "LabelText", null };
        yield return new object[] { false, null, null, null };
        yield return new object[] { false, null, string.Empty, null };
        yield return new object[] { false, null, "LabelText", null };
        yield return new object[] { false, string.Empty, null, null };
        yield return new object[] { false, string.Empty, string.Empty, null };
        yield return new object[] { false, string.Empty, "LabelText", null };
        yield return new object[] { false, "Text", null, null };
        yield return new object[] { false, "Text", string.Empty, null };
        yield return new object[] { false, "Text", "LabelText", null };

        // With mnemonic.
        yield return new object[] { true, "&", null, null };
        yield return new object[] { true, "&&a", null, null };
        yield return new object[] { true, "a&", null, null };
        yield return new object[] { true, "a&b", null, "Alt+b" };
        yield return new object[] { true, "a&bc", null, "Alt+b" };
        yield return new object[] { true, null, "&", null };
        yield return new object[] { true, null, "&&a", null };
        yield return new object[] { true, null, "a&", null };
        yield return new object[] { true, null, "a&b", "Alt+b" };
        yield return new object[] { true, null, "a&bc", "Alt+b" };

        yield return new object[] { false, "&", null, null };
        yield return new object[] { false, "&&a", null, null };
        yield return new object[] { false, "a&", null, null };
        yield return new object[] { false, "a&b", null, null };
        yield return new object[] { false, "a&bc", null, null };
        yield return new object[] { false, null, "&", null };
        yield return new object[] { false, null, "&&a", null };
        yield return new object[] { false, null, "a&", null };
        yield return new object[] { false, null, "a&b", "Alt+b" };
        yield return new object[] { false, null, "a&bc", "Alt+b" };
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_GetWithPreviousLabel_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_GetWithContainerControlParentWithPreviousLabel_ReturnsExpected(bool useTextForAccessibility, string text, string labelText, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = labelText
        };
        using Control previousControl = new()
        {
            Parent = parent,
            Visible = false
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_GetWithPreviousLabel_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_GetWithPreviousNotTabStopAndPreviousLabel_IgnoresPreviousLabel(bool useTextForAccessibility, string text, string labelText, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = labelText
        };
        using Control previousControl = new()
        {
            Parent = parent,
            TabStop = false
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    [WinFormsTheory]
    [MemberData(nameof(KeyboardShortcut_Get_TestData))]
    public void ControlAccessibleObject_KeyboardShortcut_GetWithPreviousTabStopVisibleAndPreviousLabel_IgnoresPreviousLabel(bool useTextForAccessibility, string text, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = "LabelText"
        };
        using Control previousControl = new()
        {
            Parent = parent
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.KeyboardShortcut);
    }

    public static IEnumerable<object[]> Name_Get_TestData()
    {
        yield return new object[] { true, null, null, null };
        yield return new object[] { true, null, string.Empty, null };
        yield return new object[] { true, null, "text", "text" };
        yield return new object[] { true, string.Empty, null, string.Empty };
        yield return new object[] { true, string.Empty, string.Empty, string.Empty };
        yield return new object[] { true, string.Empty, "text", string.Empty };
        yield return new object[] { true, "name", null, "name" };
        yield return new object[] { true, "name", string.Empty, "name" };
        yield return new object[] { true, "name", "text", "name" };

        yield return new object[] { false, null, null, null };
        yield return new object[] { false, null, string.Empty, null };
        yield return new object[] { false, null, "text", null };
        yield return new object[] { false, string.Empty, null, string.Empty };
        yield return new object[] { false, string.Empty, string.Empty, string.Empty };
        yield return new object[] { false, string.Empty, "text", string.Empty };
        yield return new object[] { false, "name", null, "name" };
        yield return new object[] { false, "name", string.Empty, "name" };
        yield return new object[] { false, "name", "text", "name" };

        // With mnemonic.
        yield return new object[] { true, "Name1&Name2", "text", "Name1&Name2" };
        yield return new object[] { true, null, "&", string.Empty };
        yield return new object[] { true, null, "&&Name", "&Name" };
        yield return new object[] { true, null, "Name1&Name2", "Name1Name2" };
        yield return new object[] { true, null, "Name1&Name2&Name3", "Name1Name2Name3" };
        yield return new object[] { true, null, "Name1&Name2&", "Name1Name2" };
        yield return new object[] { false, "Name1&Name2", "text", "Name1&Name2" };
        yield return new object[] { false, null, "&", null };
        yield return new object[] { false, null, "&&Name", null };
        yield return new object[] { false, null, "Name1&Name2", null };
        yield return new object[] { false, null, "Name1&Name2&Name3", null };
        yield return new object[] { false, null, "Name1&Name2&", null };
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_Get_TestData))]
    public void ControlAccessibleObject_Name_Get_ReturnsExpected(bool useTextForAccessibility, string accessibleName, string text, string expected)
    {
        using SubControl ownerControl = new()
        {
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_Get_TestData))]
    public void ControlAccessibleObject_Name_GetWithNonContainerControlParent_IgnoresParent(bool useTextForAccessibility, string accessibleName, string text, string expected)
    {
        using Control parent = new();
        using SubControl ownerControl = new()
        {
            Parent = parent,
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_Get_TestData))]
    public void ControlAccessibleObject_Name_GetWithContainerControlParentEmpty_IgnoresParent(bool useTextForAccessibility, string accessibleName, string text, string expected)
    {
        using ContainerControl parent = new();
        using SubControl ownerControl = new()
        {
            Parent = parent,
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    public static IEnumerable<object[]> Name_GetWithPreviousLabel_TestData()
    {
        yield return new object[] { true, null, null, null, null };
        yield return new object[] { true, null, null, string.Empty, null };
        yield return new object[] { true, null, null, "LabelText", "LabelText" };
        yield return new object[] { true, null, string.Empty, null, null };
        yield return new object[] { true, null, string.Empty, string.Empty, null };
        yield return new object[] { true, null, string.Empty, "LabelText", "LabelText" };
        yield return new object[] { true, null, "text", null, "text" };
        yield return new object[] { true, null, "text", string.Empty, "text" };
        yield return new object[] { true, null, "text", "LabelText", "text" };
        yield return new object[] { true, string.Empty, null, null, string.Empty };
        yield return new object[] { true, string.Empty, null, string.Empty, string.Empty };
        yield return new object[] { true, string.Empty, null, "LabelText", string.Empty };
        yield return new object[] { true, string.Empty, string.Empty, null, string.Empty };
        yield return new object[] { true, string.Empty, string.Empty, string.Empty, string.Empty };
        yield return new object[] { true, string.Empty, string.Empty, "LabelText", string.Empty };
        yield return new object[] { true, string.Empty, "text", null, string.Empty };
        yield return new object[] { true, string.Empty, "text", string.Empty, string.Empty };
        yield return new object[] { true, string.Empty, "text", "LabelText", string.Empty };
        yield return new object[] { true, "name", null, null, "name" };
        yield return new object[] { true, "name", null, string.Empty, "name" };
        yield return new object[] { true, "name", null, "LabelText", "name" };
        yield return new object[] { true, "name", string.Empty, null, "name" };
        yield return new object[] { true, "name", string.Empty, string.Empty, "name" };
        yield return new object[] { true, "name", string.Empty, "LabelText", "name" };
        yield return new object[] { true, "name", "text", null, "name" };
        yield return new object[] { true, "name", "text", string.Empty, "name" };
        yield return new object[] { true, "name", "text", "LabelText", "name" };

        yield return new object[] { false, null, null, null, null };
        yield return new object[] { false, null, null, string.Empty, null };
        yield return new object[] { false, null, null, "LabelText", "LabelText" };
        yield return new object[] { false, null, string.Empty, null, null };
        yield return new object[] { false, null, string.Empty, string.Empty, null };
        yield return new object[] { false, null, string.Empty, "LabelText", "LabelText" };
        yield return new object[] { false, null, "text", null, null };
        yield return new object[] { false, null, "text", string.Empty, null };
        yield return new object[] { false, null, "text", "LabelText", "LabelText" };
        yield return new object[] { false, string.Empty, null, null, string.Empty };
        yield return new object[] { false, string.Empty, null, string.Empty, string.Empty };
        yield return new object[] { false, string.Empty, null, "LabelText", string.Empty };
        yield return new object[] { false, string.Empty, string.Empty, null, string.Empty };
        yield return new object[] { false, string.Empty, string.Empty, string.Empty, string.Empty };
        yield return new object[] { false, string.Empty, string.Empty, "LabelText", string.Empty };
        yield return new object[] { false, string.Empty, "text", null, string.Empty };
        yield return new object[] { false, string.Empty, "text", string.Empty, string.Empty };
        yield return new object[] { false, string.Empty, "text", "LabelText", string.Empty };
        yield return new object[] { false, "name", null, null, "name" };
        yield return new object[] { false, "name", null, string.Empty, "name" };
        yield return new object[] { false, "name", null, "LabelText", "name" };
        yield return new object[] { false, "name", string.Empty, null, "name" };
        yield return new object[] { false, "name", string.Empty, string.Empty, "name" };
        yield return new object[] { false, "name", string.Empty, "LabelText", "name" };
        yield return new object[] { false, "name", "text", null, "name" };
        yield return new object[] { false, "name", "text", string.Empty, "name" };
        yield return new object[] { false, "name", "text", "LabelText", "name" };

        // With mnemonic.
        yield return new object[] { true, "Name1&Name2", "text", null, "Name1&Name2" };
        yield return new object[] { true, null, "&", null, string.Empty };
        yield return new object[] { true, null, "&&Name", null, "&Name" };
        yield return new object[] { true, null, "Name1&Name2", null, "Name1Name2" };
        yield return new object[] { true, null, "Name1&Name2&Name3", null, "Name1Name2Name3" };
        yield return new object[] { true, null, "Name1&Name2&", null, "Name1Name2" };
        yield return new object[] { true, null, null, "&", string.Empty };
        yield return new object[] { true, null, null, "&&Name", "&Name" };
        yield return new object[] { true, null, null, "Name1&Name2", "Name1Name2" };
        yield return new object[] { true, null, null, "Name1&Name2&Name3", "Name1Name2Name3" };
        yield return new object[] { true, null, null, "Name1&Name2&", "Name1Name2" };
        yield return new object[] { false, "Name1&Name2", "text", null, "Name1&Name2" };
        yield return new object[] { false, null, "&", null, null };
        yield return new object[] { false, null, "&&Name", null, null };
        yield return new object[] { false, null, "Name1&Name2", null, null };
        yield return new object[] { false, null, "Name1&Name2&Name3", null, null };
        yield return new object[] { false, null, "Name1&Name2&", null, null };
        yield return new object[] { false, null, null, "&", string.Empty };
        yield return new object[] { false, null, null, "&&Name", "&Name" };
        yield return new object[] { false, null, null, "Name1&Name2", "Name1Name2" };
        yield return new object[] { false, null, null, "Name1&Name2&Name3", "Name1Name2Name3" };
        yield return new object[] { false, null, null, "Name1&Name2&", "Name1Name2" };
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_GetWithPreviousLabel_TestData))]
    public void ControlAccessibleObject_Name_GetWithContainerControlParentWithPreviousLabel_ReturnsExpected(bool useTextForAccessibility, string accessibleName, string text, string labelText, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = labelText
        };
        using Control previousControl = new()
        {
            Parent = parent,
            Visible = false
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_GetWithPreviousLabel_TestData))]
    public void ControlAccessibleObject_Name_GetWithPreviousNotTabStopAndPreviousLabel_ReturnsExpected(bool useTextForAccessibility, string accessibleName, string text, string labelText, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = labelText
        };
        using Control previousControl = new()
        {
            Parent = parent,
            TabStop = false
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    [WinFormsTheory]
    [MemberData(nameof(Name_Get_TestData))]
    public void ControlAccessibleObject_Name_GetWithPreviousTabStopVisibleAndPreviousLabel_ReturnsExpected(bool useTextForAccessibility, string accessibleName, string text, string expected)
    {
        using ContainerControl parent = new();
        using Label previousLabel = new()
        {
            Parent = parent,
            Text = "LabelText"
        };
        using Control previousControl = new()
        {
            Parent = parent
        };
        using SubControl ownerControl = new()
        {
            Parent = parent,
            AccessibleName = accessibleName,
            Text = text
        };
        ownerControl.SetStyle(ControlStyles.UseTextForAccessibility, useTextForAccessibility);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(expected, accessibleObject.Name);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_Name_Set_GetReturnsExpected(string value)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl)
        {
            Name = value
        };
        Assert.Equal(value, accessibleObject.Name);
        Assert.Empty(ownerControl.Name);
        Assert.Equal(value, ownerControl.AccessibleName);

        // Set same.
        accessibleObject.Name = value;
        Assert.Equal(value, accessibleObject.Name);
        Assert.Empty(ownerControl.Name);
        Assert.Equal(value, ownerControl.AccessibleName);
    }

    public static IEnumerable<object[]> Role_Get_TestData()
    {
        yield return new object[] { AccessibleRole.None };
        yield return new object[] { AccessibleRole.TitleBar };
        yield return new object[] { AccessibleRole.MenuBar };
        yield return new object[] { AccessibleRole.ScrollBar };
        yield return new object[] { AccessibleRole.Grip };
        yield return new object[] { AccessibleRole.Sound };
        yield return new object[] { AccessibleRole.Cursor };
        yield return new object[] { AccessibleRole.Caret };
        yield return new object[] { AccessibleRole.Alert };
        yield return new object[] { AccessibleRole.Window };
        yield return new object[] { AccessibleRole.Client };
        yield return new object[] { AccessibleRole.MenuPopup };
        yield return new object[] { AccessibleRole.MenuItem };
        yield return new object[] { AccessibleRole.ToolTip };
        yield return new object[] { AccessibleRole.Application };
        yield return new object[] { AccessibleRole.Document };
        yield return new object[] { AccessibleRole.Pane };
        yield return new object[] { AccessibleRole.Chart };
        yield return new object[] { AccessibleRole.Dialog };
        yield return new object[] { AccessibleRole.Border };
        yield return new object[] { AccessibleRole.Grouping };
        yield return new object[] { AccessibleRole.Separator };
        yield return new object[] { AccessibleRole.ToolBar };
        yield return new object[] { AccessibleRole.StatusBar };
        yield return new object[] { AccessibleRole.Table };
        yield return new object[] { AccessibleRole.ColumnHeader };
        yield return new object[] { AccessibleRole.RowHeader };
        yield return new object[] { AccessibleRole.Column };
        yield return new object[] { AccessibleRole.Row };
        yield return new object[] { AccessibleRole.Cell };
        yield return new object[] { AccessibleRole.Link };
        yield return new object[] { AccessibleRole.HelpBalloon };
        yield return new object[] { AccessibleRole.Character };
        yield return new object[] { AccessibleRole.List };
        yield return new object[] { AccessibleRole.ListItem };
        yield return new object[] { AccessibleRole.Outline };
        yield return new object[] { AccessibleRole.OutlineItem };
        yield return new object[] { AccessibleRole.PageTab };
        yield return new object[] { AccessibleRole.PropertyPage };
        yield return new object[] { AccessibleRole.Indicator };
        yield return new object[] { AccessibleRole.Graphic };
        yield return new object[] { AccessibleRole.StaticText };
        yield return new object[] { AccessibleRole.Text };
        yield return new object[] { AccessibleRole.PushButton };
        yield return new object[] { AccessibleRole.CheckButton };
        yield return new object[] { AccessibleRole.RadioButton };
        yield return new object[] { AccessibleRole.ComboBox };
        yield return new object[] { AccessibleRole.DropList };
        yield return new object[] { AccessibleRole.ProgressBar };
        yield return new object[] { AccessibleRole.Dial };
        yield return new object[] { AccessibleRole.HotkeyField };
        yield return new object[] { AccessibleRole.Slider };
        yield return new object[] { AccessibleRole.SpinButton };
        yield return new object[] { AccessibleRole.Diagram };
        yield return new object[] { AccessibleRole.Animation };
        yield return new object[] { AccessibleRole.Equation };
        yield return new object[] { AccessibleRole.ButtonDropDown };
        yield return new object[] { AccessibleRole.ButtonMenu };
        yield return new object[] { AccessibleRole.ButtonDropDownGrid };
        yield return new object[] { AccessibleRole.WhiteSpace };
        yield return new object[] { AccessibleRole.PageTabList };
        yield return new object[] { AccessibleRole.Clock };
        yield return new object[] { AccessibleRole.SplitButton };
        yield return new object[] { AccessibleRole.IpAddress };
        yield return new object[] { AccessibleRole.OutlineButton };
    }

    [WinFormsTheory]
    [MemberData(nameof(Role_Get_TestData))]
    public void ControlAccessibleObject_Role_GetWithAccessibleRole_ReturnsExpected(AccessibleRole accessibleRole)
    {
        using Control ownerControl = new()
        {
            AccessibleRole = accessibleRole
        };
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(accessibleRole, accessibleObject.Role);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_Value_Set_GetReturnsNull_IfHandleIsCreated(string value)
    {
        using Control ownerControl = new();
        ownerControl.CreateControl();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl)
        {
            Value = value
        };
        Assert.Null(accessibleObject.Value);

        // Set same.
        accessibleObject.Value = value;
        Assert.Null(accessibleObject.Value);
        Assert.True(ownerControl.IsHandleCreated);
    }

    [WinFormsTheory]
    [StringWithNullData]
    public void ControlAccessibleObject_Value_Set_GetReturnsNull_IfHandleIsNotCreated(string value)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl)
        {
            Value = value
        };
        Assert.Equal(string.Empty, accessibleObject.Value);

        // Set same.
        accessibleObject.Value = value;
        Assert.Equal(string.Empty, accessibleObject.Value);
        Assert.False(ownerControl.IsHandleCreated);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_DoDefaultAction_InvokeDefault_Nop()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        accessibleObject.DoDefaultAction();
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetChildCount_InvokeDefault_ReturnsMinusOne()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(-1, accessibleObject.GetChildCount());
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetChildCount_InvokeWithChildren_ReturnsMinusOne()
    {
        using Control child = new();
        using Control ownerControl = new();
        ownerControl.Controls.Add(child);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(-1, accessibleObject.GetChildCount());
    }

    [WinFormsTheory]
    [InlineData(-1)]
    [InlineData(0)]
    [InlineData(1)]
    public void ControlAccessibleObject_GetChild_InvokeDefault_ReturnsNull(int index)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Null(accessibleObject.GetChild(index));
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetFocused_InvokeDefault_ReturnsNull()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Null(accessibleObject.GetFocused());
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetHelpTopic_InvokeDefault_ReturnsMinusOne()
    {
        AccessibleObject accessibleObject = new();
        Assert.Equal(-1, accessibleObject.GetHelpTopic(out string fileName));
        Assert.Null(fileName);
    }

    [WinFormsTheory]
    [InlineData(null, null, 0, true, 0)]
    [InlineData("", "", 0, true, 0)]
    [InlineData("HelpNamespace", "invalid", 0, true, 0)]
    [InlineData("HelpNamespace", "1", 1, true, 0)]
    [InlineData(null, null, 0, false, -1)]
    [InlineData("", "", 0, false, -1)]
    [InlineData("HelpNamespace", "invalid", 0, false, -1)]
    [InlineData("HelpNamespace", "1", 1, false, -1)]
    public void ControlAccessibleObject_GetHelpTopic_InvokeWithQueryAccessibilityHelpEvent_ReturnsExpected(
        string helpNamespace,
        string helpKeyword,
        int expectedResult,
        bool createControl,
        int expectedResultWithoutHandler)
    {
        using Control ownerControl = new();
        if (createControl)
        {
            ownerControl.CreateControl();
        }

        Assert.Equal(createControl, ownerControl.IsHandleCreated);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(createControl, ownerControl.IsHandleCreated);

        int callCount = 0;
        void handler(object sender, QueryAccessibilityHelpEventArgs e)
        {
            Assert.Same(ownerControl, sender);
            Assert.Null(e.HelpKeyword);
            Assert.Null(e.HelpNamespace);
            Assert.Null(e.HelpString);

            e.HelpNamespace = helpNamespace;
            e.HelpKeyword = helpKeyword;
            callCount++;
        }

        ownerControl.QueryAccessibilityHelp += handler;

        // Get with handler.
        Assert.Equal(expectedResult, accessibleObject.GetHelpTopic(out string fileName));
        Assert.Equal(helpNamespace, fileName);
        Assert.Equal(1, callCount);

        // Get again.
        Assert.Equal(expectedResult, accessibleObject.GetHelpTopic(out fileName));
        Assert.Equal(helpNamespace, fileName);
        Assert.Equal(2, callCount);

        // Remove handler.
        ownerControl.QueryAccessibilityHelp -= handler;
        Assert.Equal(expectedResultWithoutHandler, accessibleObject.GetHelpTopic(out fileName));
        Assert.Null(fileName);
        Assert.Equal(2, callCount);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetSelected_InvokeDefault_ReturnsNull()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Null(accessibleObject.GetSelected());
    }

    [WinFormsTheory]
    [InlineData(-1, -2)]
    [InlineData(0, 0)]
    [InlineData(1, 2)]
    public void AccessibleObject_HitTest_InvokeDefault_ReturnsNull(int x, int y)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Null(accessibleObject.HitTest(x, y));
    }

    [WinFormsTheory]
    [EnumData<AccessibleNavigation>]
    public void AccessibleObject_Navigate_InvokeDefault_ReturnsNull(AccessibleNavigation navdir)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Null(accessibleObject.Navigate(navdir));
    }

    [WinFormsTheory]
    [InlineData(AccessibleEvents.Create)]
    public void ControlAccessibleObject_NotifyClients_InvokeAccessibleEvents_Success(AccessibleEvents accEvent)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        accessibleObject.NotifyClients(accEvent);
    }

    [WinFormsTheory]
    [InlineData(AccessibleEvents.Create, -1)]
    [InlineData(AccessibleEvents.Create, 0)]
    [InlineData(AccessibleEvents.Create, 1)]
    public void ControlAccessibleObject_NotifyClients_InvokeAccessibleEventsInt_Success(AccessibleEvents accEvent, int childID)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        accessibleObject.NotifyClients(accEvent, childID);
    }

    [WinFormsTheory]
    [InlineData(AccessibleEvents.Create, -1, -1)]
    [InlineData(AccessibleEvents.Create, 1, 0)]
    [InlineData(AccessibleEvents.Create, 2, 1)]
    public void ControlAccessibleObject_NotifyClients_InvokeAccessibleEventsIntInt_Success(AccessibleEvents accEvent, int objectID, int childID)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        accessibleObject.NotifyClients(accEvent, objectID, childID);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_RaiseLiveRegionChanged_Invoke_Success()
    {
        using AutomationLiveRegionControl ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        accessibleObject.RaiseLiveRegionChanged();
    }

    [WinFormsFact]
    public void ControlAccessibleObject_RaiseLiveRegionChanged_InvokeNotIAutomationLiveRegion_ThrowsInvalidOperationException()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Throws<InvalidOperationException>(() => accessibleObject.RaiseLiveRegionChanged());
    }

    [WinFormsTheory]
    [InlineData(false)]
    [InlineData(true)]
    public void ControlAccessibleObject_RaiseAutomationEvent_IsHandleCreatedFlag(bool isHandleCreated)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);

        Assert.False(ownerControl.IsHandleCreated);

        if (isHandleCreated)
        {
            Assert.NotEqual(IntPtr.Zero, ownerControl.Handle);
        }

        Assert.Equal(isHandleCreated, accessibleObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationPropertyChangedEventId));
        Assert.Equal(isHandleCreated, ownerControl.IsHandleCreated);
    }

    [WinFormsTheory]
    [InlineData(false)]
    [InlineData(true)]
    public void ControlAccessibleObject_RaiseAutomationPropertyChangedEvent_IsHandleCreatedFlag(bool isHandleCreated)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);

        Assert.False(ownerControl.IsHandleCreated);

        if (isHandleCreated)
        {
            Assert.NotEqual(IntPtr.Zero, ownerControl.Handle);
        }

        using var ownerControlName = (VARIANT)ownerControl.Name;
        Assert.Equal(isHandleCreated, accessibleObject.RaiseAutomationPropertyChangedEvent(UIA_PROPERTY_ID.UIA_NamePropertyId, ownerControlName, ownerControlName));
        Assert.Equal(isHandleCreated, ownerControl.IsHandleCreated);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_ToString_Invoke_Success()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal("ControlAccessibleObject: Owner = System.Windows.Forms.Control", accessibleObject.ToString());
    }

    [WinFormsFact]
    public void ControlAccessibleObject_IAccessibleAccFocus_InvokeDefault_ReturnsNull()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        IAccessible iAccessible = accessibleObject;
        Assert.Null(iAccessible.accFocus);
    }

    [WinFormsTheory]
    [InlineData(true, -1, -2, 0)]
    [InlineData(true, 0, 0, 0)]
    [InlineData(true, 1, 2, 0)]
    [InlineData(false, -1, -2, null)]
    [InlineData(false, 0, 0, null)]
    [InlineData(false, 1, 2, null)]
    public void AccessibleObject_IAccessibleAccHitTest_InvokeDefault_ReturnsExpectedValue(bool createControl, int x, int y, int? expectedValue)
    {
        using Control ownerControl = new();
        if (createControl)
        {
            ownerControl.CreateControl();
        }

        Assert.Equal(createControl, ownerControl.IsHandleCreated);

        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(createControl, ownerControl.IsHandleCreated);
        IAccessible iAccessible = accessibleObject;
        Assert.Equal(expectedValue, iAccessible.accHitTest(x, y));
    }

    [WinFormsTheory]
    [InlineData(true)]
    [InlineData(false)]
    public void ControlAccessibleObject_IAccessibleAccParent_InvokeDefault_ReturnsExpectedValue(bool createControl)
    {
        using Control ownerControl = new();
        if (createControl)
        {
            ownerControl.CreateControl();
        }

        Assert.Equal(createControl, ownerControl.IsHandleCreated);

        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(createControl, ownerControl.IsHandleCreated);
        IAccessible iAccessible = accessibleObject;
        Assert.Equal(createControl, iAccessible.accParent is not null);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_IAccessibleAccSelection_InvokeDefault_ReturnsNull()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        IAccessible iAccessible = accessibleObject;
        Assert.Null(iAccessible.accSelection);
    }

    [WinFormsTheory]
    [InlineData(-1)]
    [InlineData(1)]
    public void ControlAccessibleObject_IAccessibleGet_accChild_InvokeNoSuchChild_ReturnsNull(int childID)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        IAccessible iAccessible = accessibleObject;
        Assert.Null(iAccessible.get_accChild(childID));
    }

    [WinFormsFact]
    public void ControlAccessibleObject_IAccessibleGet_accChildCount_InvokeDefault_ReturnsExpected()
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        IAccessible iAccessible = accessibleObject;
        Assert.Equal(0, iAccessible.accChildCount);
    }

    [WinFormsTheory]
    [InlineData(true, 1)]
    [InlineData(false, 0)]
    public void ControlAccessibleObject_IAccessibleGet_accChildCount_InvokeWithChildren_ReturnsExpected(bool createControl, int expectedCount)
    {
        using Control child = new();
        using Control ownerControl = new();
        if (createControl)
        {
            ownerControl.CreateControl();
        }

        Assert.Equal(createControl, ownerControl.IsHandleCreated);
        ownerControl.Controls.Add(child);
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        Assert.Equal(createControl, ownerControl.IsHandleCreated);
        IAccessible iAccessible = accessibleObject;
        Assert.Equal(expectedCount, iAccessible.accChildCount);
    }

    [WinFormsTheory]
    [InlineData(-1)]
    [InlineData(1)]
    public void ControlAccessibleObject_IAccessibleGet_accRole_InvokeNoSuchChild_ReturnsNull(object varChild)
    {
        using Control ownerControl = new();
        var accessibleObject = new Control.ControlAccessibleObject(ownerControl);
        IAccessible iAccessible = accessibleObject;
        Assert.Null(iAccessible.get_accRole(varChild));
    }

    [WinFormsFact]
    public void ControlAccessibleObject_DoesNotSupport_LegacyIAccessiblePattern()
    {
        using Control control = new();
        var accessibleObject = control.AccessibilityObject;

        bool expected = control.SupportsUiaProviders;
        Assert.False(expected);

        bool actual = accessibleObject.IsPatternSupported(UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId);
        Assert.Equal(expected, actual);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_Supports_LegacyIAccessiblePattern_IfOwnerSupportsUia()
    {
        using Label control = new();
        var accessibleObject = new Control.ControlAccessibleObject(control);

        Assert.True(control.SupportsUiaProviders);
        bool actual = accessibleObject.IsPatternSupported(UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId);
        Assert.True(actual);
    }

    public static IEnumerable<object[]> ControlAccessibleObject_TestData()
    {
        return ReflectionHelper.GetPublicNotAbstractClasses<Control>()
           .Where(t => !s_unsupportedControls.Contains(t))
           .Select(type => new object[] { type });
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_Custom_Role_ReturnsExpected(Type type)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        control.AccessibleRole = AccessibleRole.Link;
        AccessibleObject controlAccessibleObject = control.AccessibilityObject;

        var accessibleObjectRole = controlAccessibleObject.Role;

        Assert.Equal(AccessibleRole.Link, accessibleObjectRole);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_GetPropertyValue_AccessKey_ReturnExpected(Type type)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        if (!s_controlsIgnoringTextChangesForTests.Contains(type))
        {
            control.Text = "&Name";
        }

        AccessibleObject controlAccessibleObject = control.AccessibilityObject;
        string expectedValue = s_controlsNotUseTextForAccessibility.Contains(type) ? string.Empty : "Alt+n";

        Assert.Equal(expectedValue, ((BSTR)controlAccessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_AccessKeyPropertyId)).ToStringAndFree());
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_IsPatternSupported_LegacyIAccessible_ReturnsTrue(Type type)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        AccessibleObject controlAccessibleObject = control.AccessibilityObject;

        bool supportsLegacyIAccessiblePatternId = controlAccessibleObject.IsPatternSupported(UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId);

        Assert.True(supportsLegacyIAccessiblePatternId);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_Custom_Description_ReturnsExpected(Type type)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        control.AccessibleDescription = "Test Accessible Description";
        AccessibleObject controlAccessibleObject = control.AccessibilityObject;

        string accessibleObjectDescription = controlAccessibleObject.Description;

        Assert.Equal("Test Accessible Description", accessibleObjectDescription);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_GetPropertyValue_Custom_Name_ReturnsExpected(Type type)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        AccessibleObject controlAccessibleObject = control.AccessibilityObject;
        control.Name = "Name1";
        control.AccessibleName = "Test Name";

        Assert.Equal("Test Name", ((BSTR)controlAccessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_LegacyIAccessibleNamePropertyId)).ToStringAndFree());
        Assert.Equal("Test Name", ((BSTR)controlAccessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_NamePropertyId)).ToStringAndFree());
    }

    public static IEnumerable<object[]> ControlAccessibleObject_DefaultName_TestData()
    {
        // These controls have AccessibleName defined.
        // MonthCalendar has "Month" view by default and returns current date as AccessibleName
        var typeDefaultValues = new Dictionary<Type, string>
        {
            { typeof(DataGridViewTextBoxEditingControl), SR.DataGridView_AccEditingControlAccName },
            { typeof(DateTimePicker), string.Empty },
            { typeof(PrintPreviewDialog), SR.PrintPreviewDialog_PrintPreview },
            { typeof(TextBox), string.Empty},
            { typeof(MaskedTextBox), string.Empty}
        };

        foreach (Type type in ReflectionHelper.GetPublicNotAbstractClasses<Control>())
        {
            if (s_unsupportedControls.Contains(type))
            {
                continue;
            }

            yield return new object[]
            {
                type,
                typeDefaultValues.TryGetValue(type, out string value) ? value : null
            };
        }
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_DefaultName_TestData))]
    public void ControlAccessibleObject_GetPropertyValue_Default_Name_ReturnsExpected(Type type, string expectedName)
    {
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        if (!control.SupportsUiaProviders)
        {
            return;
        }

        AccessibleObject controlAccessibleObject = control.AccessibilityObject;
        if (expectedName is not null)
        {
            Assert.Equal(expectedName, ((BSTR)controlAccessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_NamePropertyId)).ToStringAndFree());
        }
        else
        {
            Assert.Equal(VARIANT.Empty, controlAccessibleObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_NamePropertyId));
        }
    }

    public static IEnumerable<object[]> ControlAccessibleObject_GetPropertyValue_ControlTypeProperty_ReturnsCorrectValue_TestData()
    {
        Array roles = Enum.GetValues(typeof(AccessibleRole));

        foreach (AccessibleRole role in roles)
        {
            yield return new object[] { role };
        }
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_GetPropertyValue_ControlTypeProperty_ReturnsCorrectValue_TestData))]
    public void ControlAccessibleObject_GetPropertyValue_ControlTypeProperty_ReturnsCorrectValue(AccessibleRole role)
    {
        using Control control = new();
        control.AccessibleRole = role;

        UIA_CONTROLTYPE_ID actual = (UIA_CONTROLTYPE_ID)(int)control.AccessibilityObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_ControlTypePropertyId);
        UIA_CONTROLTYPE_ID expected = AccessibleRoleControlTypeMap.GetControlType(role);

        Assert.Equal(expected, actual);
        // Check if the method returns an exist UIA_ControlTypeId
        Assert.True(actual is >= UIA_CONTROLTYPE_ID.UIA_ButtonControlTypeId and <= UIA_CONTROLTYPE_ID.UIA_AppBarControlTypeId);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentRoot_IsNullOrOwnObject(Type type)
    {
        using NoAssertContext context = new();
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        Assert.False(control.IsHandleCreated);
        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface actual = (IRawElementProviderFragment.Interface)control.AccessibilityObject.FragmentRoot;

        Assert.True(actual is null || actual == control.AccessibilityObject);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public unsafe void ControlAccessibleObject_FragmentRoot_IsToolStrip_IfControlIsUsedAsToolStripItem(Type type)
    {
        using NoAssertContext context = new();
        using ToolStrip toolStrip = new();
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        using ToolStripControlHost host = new(control);

        toolStrip.CreateControl();
        control.CreateControl();

        if (!CanBeAddedToToolStrip(control))
        {
            // A number of controls can't be added to ToolStrip. Double verify it is still the case, and exit.
            Assert.Throws<ArgumentException>(() => toolStrip.Items.Add(host));

            return;
        }

        toolStrip.Items.Add(host);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        using ComScope<IRawElementProviderFragmentRoot> actual = new(null);
        Assert.True(((IRawElementProviderFragment.Interface)control.AccessibilityObject).get_FragmentRoot(actual).Succeeded);

        Assert.Equal(toolStrip.AccessibilityObject, ComHelpers.GetObjectForIUnknown(actual));
        Assert.True(control.IsHandleCreated);
        Assert.True(toolStrip.IsHandleCreated);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentNavigate_Parent_IsNull(Type type)
    {
        using NoAssertContext context = new();
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface parent
            = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_Parent);

        Assert.True(parent is null);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentNavigate_Siblings_AreNull(Type type)
    {
        using NoAssertContext context = new();
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface nextSibling
            = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_NextSibling);
        IRawElementProviderFragment.Interface previousSibling
            = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_PreviousSibling);

        Assert.True(nextSibling is null);
        Assert.True(previousSibling is null);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentNavigate_ParentIsToolStrip_IfControlIsUsedAsToolStripItem(Type type)
    {
        using NoAssertContext noAssertContext = new();
        using ToolStrip toolStrip = new() { Size = new Drawing.Size(1000, 25) };
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        using ToolStripControlHost host = new(control);

        toolStrip.CreateControl();
        control.CreateControl();

        if (!CanBeAddedToToolStrip(control))
        {
            // A number of controls can't be added to ToolStrip. Double verify it is still the case, and exit.
            Assert.Throws<ArgumentException>(() => toolStrip.Items.Add(host));

            return;
        }

        toolStrip.Items.Add(host);
        host.TestAccessor.Dynamic._parent = toolStrip;
        host.SetPlacement(ToolStripItemPlacement.Main);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface actual = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_Parent);
        AccessibleObject expected = toolStrip.AccessibilityObject;

        Assert.Equal(expected, actual);
        Assert.True(control.IsHandleCreated);
        Assert.True(toolStrip.IsHandleCreated);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentNavigate_NextSibling_IsNextItem_IfControlIsUsedAsToolStripItem(Type type)
    {
        using NoAssertContext noAssertContext = new();
        using ToolStrip toolStrip = new() { Size = new Drawing.Size(1000, 25) };
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        using ToolStripControlHost host = new(control);
        using ToolStripLabel label = new();
        using ToolStripButton button = new();

        toolStrip.CreateControl();
        control.CreateControl();
        toolStrip.Items.Add(label);

        if (!CanBeAddedToToolStrip(control))
        {
            // A number of controls can't be added to ToolStrip. Double verify it is still the case, and exit.
            Assert.Throws<ArgumentException>(() => toolStrip.Items.Add(host));

            return;
        }

        toolStrip.Items.Add(host);
        toolStrip.Items.Add(button);
        host.TestAccessor.Dynamic._parent = toolStrip;
        host.SetPlacement(ToolStripItemPlacement.Main);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface actual = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_NextSibling);
        AccessibleObject expected = button.AccessibilityObject;

        Assert.Equal(expected, actual);
        Assert.True(control.IsHandleCreated);
        Assert.True(toolStrip.IsHandleCreated);
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_FragmentNavigate_PreviousSibling_IsPreviousItem_IfControlIsUsedAsToolStripItem(Type type)
    {
        using NoAssertContext noAssertContext = new();
        using ToolStrip toolStrip = new() { Size = new Drawing.Size(1000, 25) };
        using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        using ToolStripControlHost host = new(control);
        using ToolStripLabel label = new();

        toolStrip.CreateControl();
        control.CreateControl();
        toolStrip.Items.Add(label);

        if (!CanBeAddedToToolStrip(control))
        {
            // A number of controls can't be added to ToolStrip. Double verify it is still the case, and exit.
            Assert.Throws<ArgumentException>(() => toolStrip.Items.Add(host));

            return;
        }

        toolStrip.Items.Add(host);
        host.TestAccessor.Dynamic._parent = toolStrip;
        host.SetPlacement(ToolStripItemPlacement.Main);

        Assert.True(control.AccessibilityObject is Control.ControlAccessibleObject);

        IRawElementProviderFragment.Interface actual = control.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_PreviousSibling);
        AccessibleObject expected = label.AccessibilityObject;

        Assert.Equal(expected, actual);
        Assert.True(control.IsHandleCreated);
        Assert.True(toolStrip.IsHandleCreated);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_GetPropertyValue_AutomationId_ReturnsExpected()
    {
        using Control ownerControl = new() { Name = "test name" };
        string expected = ownerControl.Name;
        VARIANT actual = ownerControl.AccessibilityObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_AutomationIdPropertyId);

        Assert.Equal(expected, ((BSTR)actual).ToStringAndFree());
        Assert.False(ownerControl.IsHandleCreated);
    }

    [WinFormsFact]
    public void ControlAccessibleObject_DoesNotRootControl()
    {
        Control.ControlAccessibleObject accessibleObject = CreateAndDisposeControl();
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
        Assert.False(accessibleObject.TryGetOwnerAs(out Control _));

        [MethodImpl(MethodImplOptions.NoInlining)]
        static Control.ControlAccessibleObject CreateAndDisposeControl()
        {
            using Control control = new();
            var accessibleObject = (Control.ControlAccessibleObject)control.AccessibilityObject;
            Assert.NotNull(accessibleObject);
            Assert.True(accessibleObject.TryGetOwnerAs(out Control _));

            return accessibleObject;
        }
    }

    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_TestData))]
    public void ControlAccessibleObject_DoesNotRootControls_AllPublicControl(Type type)
    {
        Control.ControlAccessibleObject accessibleObject = CreateAndDisposeControl(type);
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
        Assert.False(accessibleObject.TryGetOwnerAs(out Control _));

        [MethodImpl(MethodImplOptions.NoInlining)]
        static Control.ControlAccessibleObject CreateAndDisposeControl(Type type)
        {
            using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);
            var accessibleObject = (Control.ControlAccessibleObject)control.AccessibilityObject;
            Assert.NotNull(accessibleObject);
            Assert.True(accessibleObject.TryGetOwnerAs(out Control _));

            return accessibleObject;
        }
    }

    public static IEnumerable<object[]> ControlAccessibleObject_Name_KeyboardShortcut_TestData()
    {
        // Controls that have UseMnemonic property.
        yield return new object[] { typeof(Label) };
        yield return new object[] { typeof(LinkLabel) };
        yield return new object[] { typeof(Button) };
        yield return new object[] { typeof(RadioButton) };
        yield return new object[] { typeof(CheckBox) };
    }

    // Unit test for https://github.com/dotnet/winforms/issues/9296
    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_Name_KeyboardShortcut_TestData))]
    public void ControlAccessibleObject_Shortcut_Invoke_ReturnsExpected(Type type)
    {
        using var control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        control.Text = "&control";
        control.TabIndex = 1;
        var accessibleObject = control.AccessibilityObject;

        Assert.Equal("Alt+c", accessibleObject.KeyboardShortcut);

        control.GetType().GetProperty(nameof(Label.UseMnemonic)).SetValue(control, false);

        Assert.Null(accessibleObject.KeyboardShortcut);

        if (control is not Label)
        {
            using Form form = new();
            using Label label = new();
            label.Text = "&label";
            label.TabIndex = 0;

            form.Controls.Add(label);
            form.Controls.Add(control);
            form.Show();

            Assert.Equal("Alt+l", accessibleObject.KeyboardShortcut);

            label.UseMnemonic = false;

            Assert.Null(accessibleObject.KeyboardShortcut);

            control.GetType().GetProperty(nameof(Label.UseMnemonic)).SetValue(control, true);

            Assert.Equal("Alt+c", accessibleObject.KeyboardShortcut);

            control.Text = "control";
            label.UseMnemonic = true;

            Assert.Equal("Alt+l", accessibleObject.KeyboardShortcut);
        }
    }

    // Unit test for https://github.com/dotnet/winforms/issues/9296
    [WinFormsTheory]
    [MemberData(nameof(ControlAccessibleObject_Name_KeyboardShortcut_TestData))]
    public void ControlAccessibleObject_Name_Invoke_ReturnsExpected(Type type)
    {
        using var control = ReflectionHelper.InvokePublicConstructor<Control>(type);
        control.Text = "&control";
        var accessibleObject = control.AccessibilityObject;

        Assert.Equal("control", accessibleObject.Name);

        control.GetType().GetProperty(nameof(Label.UseMnemonic)).SetValue(control, false);

        Assert.Equal("&control", accessibleObject.Name);

        accessibleObject.Name = "CustomName";

        Assert.Equal("CustomName", accessibleObject.Name);

        control.GetType().GetProperty(nameof(Label.UseMnemonic)).SetValue(control, true);

        Assert.Equal("CustomName", accessibleObject.Name);
        Assert.False(control.IsHandleCreated);
    }

    // ContextMenuStrip, From, ToolStripDropDown, ToolStripDropDownMenu
    // are Top level controls that can't be added to a ToolStrip.
    // A TabPage can be added to a TabControl only (see TabPage.AssignParent method).
    private static bool CanBeAddedToToolStrip(Control control)
        => control is not (ContextMenuStrip
            or Form
            or ToolStripDropDown
            or ToolStripDropDownMenu
            or TabPage);

    private class AutomationLiveRegionControl : Control, IAutomationLiveRegion
    {
        public AutomationLiveSetting LiveSetting { get; set; }
    }

    private class SubControl : Control
    {
        public new void SetStyle(ControlStyles flag, bool value) => base.SetStyle(flag, value);
    }
}
