// 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.Drawing;
using Windows.Win32.UI.Accessibility;
using static System.Windows.Forms.TreeNode;

namespace System.Windows.Forms.Tests.AccessibleObjects;

public class TreeNodeAccessibleObjectTests
{
    [WinFormsFact]
    public void TreeNodeAccessibleObject_Ctor_Default()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        node.AccessibilityObject.Should().NotBeNull();
        node.AccessibilityObject.CanGetDefaultActionInternal.Should().BeFalse();
        node.AccessibilityObject.CanGetNameInternal.Should().BeFalse();
        node.AccessibilityObject.IsItemSelected.Should().BeFalse();
        node.AccessibilityObject.CanGetValueInternal.Should().BeFalse();
        control.IsHandleCreated.Should().BeFalse();
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Ctor_ThrowsException_IfOwningNodeIsNull()
    {
        using TreeView control = new();

        Assert.Throws<ArgumentNullException>(() => new TreeNodeAccessibleObject(null, control));
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Ctor_ThrowsException_IfOwningTreeIsNull()
    {
        TreeNode node = new();

        Assert.Throws<ArgumentNullException>(() => new TreeNodeAccessibleObject(node, null));
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_DefaultAction_IsEmptyString_IfNodeIsLeaf()
    {
        using TreeView control = new() { CheckBoxes = false };
        TreeNode node = new(control);

        Assert.Empty(node.AccessibilityObject.DefaultAction);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_DefaultAction_ReturnsExpected_IfNodeIsParent(bool isExpanded)
    {
        using TreeView control = new() { CheckBoxes = false };
        TreeNode node = new("Root node", [new()]);
        control.Nodes.Add(node);
        if (isExpanded)
        {
            node.Expand();
        }

        string expected = isExpanded ? SR.AccessibleActionCollapse : SR.AccessibleActionExpand;

        Assert.Equal(expected, node.AccessibilityObject.DefaultAction);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_DefaultAction_ReturnsExpected_IfNodesAreCheckBoxes(bool isChecked)
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control) { Checked = isChecked };

        string expected = isChecked ? SR.AccessibleActionUncheck : SR.AccessibleActionCheck;

        Assert.Equal(expected, node.AccessibilityObject.DefaultAction);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_DoDefaultAction_ToggleNodeCheckBox_IfNodesAreCheckBoxes(bool isChecked)
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control) { Checked = isChecked };

        Assert.Equal(isChecked, node.Checked);

        node.AccessibilityObject.DoDefaultAction();

        Assert.Equal(!isChecked, node.Checked);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_DoDefaultAction_ExpandOrCollapse_IfNodeIsNotLeaf(bool isExpanded)
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new()]);
        control.Nodes.Add(node);
        if (isExpanded)
        {
            node.Expand();
        }

        Assert.Equal(isExpanded, node.IsExpanded);

        node.AccessibilityObject.DoDefaultAction();

        Assert.Equal(!isExpanded, node.IsExpanded);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_FragmentRoot_ReturnsTree()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Equal(control.AccessibilityObject, node.AccessibilityObject.FragmentRoot);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_FragmentNavigate_Parent_ReturnsTree()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        var actual = (AccessibleObject)node.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_Parent);

        Assert.Equal(control.AccessibilityObject, actual);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_FragmentNavigate_Parent_ReturnsParent()
    {
        using TreeView control = new();
        TreeNode node = new();
        control.Nodes.Add(new TreeNode("Root node", [node]));

        var actual = (AccessibleObject)node.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_Parent);

        Assert.Equal(node.Parent.AccessibilityObject, actual);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_FragmentNavigate_FirstChild_ReturnsExpected(bool isExpanded)
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new(), new(), new()]);
        control.Nodes.Add(node);
        if (isExpanded)
        {
            node.Expand();
        }

        // If node is collapsed, child is not visible, so returns null instead of child ao.
        AccessibleObject expected = isExpanded ? node.FirstNode?.AccessibilityObject : null;

        Assert.Equal(expected, node.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_FirstChild));
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_FragmentNavigate_LastChild_ReturnsExpected(bool isExpanded)
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new(), new(), new()]);
        control.Nodes.Add(node);
        if (isExpanded)
        {
            node.Expand();
        }

        // If node is collapsed, child is not visible, so returns null instead of child ao.
        AccessibleObject expected = isExpanded ? node.LastNode?.AccessibilityObject : null;

        Assert.Equal(expected, node.AccessibilityObject.FragmentNavigate(NavigateDirection.NavigateDirection_LastChild));
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_FragmentNavigate_NextSibling_ReturnsExpected()
    {
        using TreeView control = new();
        control.Nodes.AddRange([new(), new(), new()]);

        AccessibleObject accessibleObject1 = control.Nodes[0].AccessibilityObject;
        AccessibleObject accessibleObject2 = control.Nodes[1].AccessibilityObject;
        AccessibleObject accessibleObject3 = control.Nodes[2].AccessibilityObject;

        Assert.Equal(accessibleObject2, accessibleObject1.FragmentNavigate(NavigateDirection.NavigateDirection_NextSibling));
        Assert.Equal(accessibleObject3, accessibleObject2.FragmentNavigate(NavigateDirection.NavigateDirection_NextSibling));
        Assert.Null(accessibleObject3.FragmentNavigate(NavigateDirection.NavigateDirection_NextSibling));
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_FragmentNavigate_PreviousSibling_ReturnsExpected()
    {
        using TreeView control = new();
        control.Nodes.AddRange([new(), new(), new()]);

        AccessibleObject accessibleObject1 = control.Nodes[0].AccessibilityObject;
        AccessibleObject accessibleObject2 = control.Nodes[1].AccessibilityObject;
        AccessibleObject accessibleObject3 = control.Nodes[2].AccessibilityObject;

        Assert.Null(accessibleObject1.FragmentNavigate(NavigateDirection.NavigateDirection_PreviousSibling));
        Assert.Equal(accessibleObject1, accessibleObject2.FragmentNavigate(NavigateDirection.NavigateDirection_PreviousSibling));
        Assert.Equal(accessibleObject2, accessibleObject3.FragmentNavigate(NavigateDirection.NavigateDirection_PreviousSibling));
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [InlineData(true)]
    [InlineData(false)]
    public void TreeNodeAccessibleObject_GetPropertyValue_ControlType_IsTreeItem(bool checkBoxes)
    {
        using TreeView control = new() { CheckBoxes = checkBoxes };
        TreeNode node = new(control);

        UIA_CONTROLTYPE_ID expected = UIA_CONTROLTYPE_ID.UIA_TreeItemControlTypeId;
        var actual = (UIA_CONTROLTYPE_ID)(int)node.AccessibilityObject.GetPropertyValue(UIA_PROPERTY_ID.UIA_ControlTypePropertyId);

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

    [WinFormsTheory]
    [InlineData((int)UIA_PATTERN_ID.UIA_ExpandCollapsePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_ScrollItemPatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_SelectionItemPatternId, true)]
    public void TreeNodeAccessibleObject_IsPatternSupported_IfCommonNodes(int patternId, bool expected)
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Equal(node.AccessibilityObject.IsPatternSupported((UIA_PATTERN_ID)patternId), expected);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [InlineData((int)UIA_PATTERN_ID.UIA_ExpandCollapsePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_ScrollItemPatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_SelectionItemPatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_TogglePatternId, true)]
    public void TreeNodeAccessibleObject_IsPatternSupported_IfNodesAreCheckBoxes(int patternId, bool expected)
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control);

        Assert.Equal(node.AccessibilityObject.IsPatternSupported((UIA_PATTERN_ID)patternId), expected);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [InlineData((int)UIA_PATTERN_ID.UIA_ExpandCollapsePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_ScrollItemPatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_SelectionItemPatternId, true)]
    [InlineData((int)UIA_PATTERN_ID.UIA_ValuePatternId, true)]
    public void TreeNodeAccessibleObject_IsPatternSupported_IfNodesAreEditable(int patternId, bool expected)
    {
        using TreeView control = new() { LabelEdit = true };
        TreeNode node = new(control);

        Assert.Equal(node.AccessibilityObject.IsPatternSupported((UIA_PATTERN_ID)patternId), expected);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Index_ReturnsExpected()
    {
        using TreeView control = new();
        control.Nodes.AddRange([new(), new(), new()]);

        TreeNodeAccessibleObject accessibleObject1 = control.Nodes[0].AccessibilityObject;
        TreeNodeAccessibleObject accessibleObject2 = control.Nodes[1].AccessibilityObject;
        TreeNodeAccessibleObject accessibleObject3 = control.Nodes[2].AccessibilityObject;

        Assert.Equal(0, accessibleObject1.Index);
        Assert.Equal(1, accessibleObject2.Index);
        Assert.Equal(2, accessibleObject3.Index);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Name_EqualsText()
    {
        using TreeView control = new();
        string testText = "This is test string for Text property of TreeNode.";
        TreeNode node = new(control) { Text = testText };

        Assert.Equal(testText, node.AccessibilityObject.Name);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Parent_ReturnsNull_IfNodeHasNoParent()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Null(node.AccessibilityObject.Parent);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Parent_ReturnsParent()
    {
        using TreeView control = new();
        TreeNode node = new();

        control.Nodes.Add(new TreeNode("Root node", [node]));

        Assert.Equal(node.Parent.AccessibilityObject, node.AccessibilityObject.Parent);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Role_IsOutlineItem()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Equal(AccessibleRole.OutlineItem, node.AccessibilityObject.Role);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Role_IsCheckButton_IfNodesAreCheckBoxes()
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control);

        Assert.Equal(AccessibleRole.CheckButton, node.AccessibilityObject.Role);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Expand_WorksExpected_IfNodeIsCollapsed()
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new()]);

        control.Nodes.Add(node);

        Assert.False(node.IsExpanded);

        node.AccessibilityObject.Expand();

        Assert.True(node.IsExpanded);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Expand_DoesNothing_IfNodeHasNoChild()
    {
        using TreeView control = new();
        TreeNode node = new();

        control.Nodes.Add(node);

        Assert.False(node.IsExpanded);

        node.AccessibilityObject.Expand();

        Assert.False(node.IsExpanded);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Collapse_WorksExpected_IfNodeIsExpanded()
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new()]);

        control.Nodes.Add(node);

        node.Expand();
        Assert.True(node.IsExpanded);

        node.AccessibilityObject.Collapse();

        Assert.False(node.IsExpanded);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_ExpandCollapseState_ReturnsLeafNode_IfNodeHasNoChild()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Equal(ExpandCollapseState.ExpandCollapseState_LeafNode, node.AccessibilityObject.ExpandCollapseState);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_ExpandCollapseState_ReturnsExpected(bool isExpanded)
    {
        using TreeView control = new();
        TreeNode node = new("Root node", [new()]);

        control.Nodes.Add(node);

        if (isExpanded)
        {
            node.Expand();
        }

        ExpandCollapseState expected = isExpanded
                ? ExpandCollapseState.ExpandCollapseState_Expanded
                : ExpandCollapseState.ExpandCollapseState_Collapsed;

        Assert.Equal(expected, node.AccessibilityObject.ExpandCollapseState);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_ItemSelectionContainer_ReturnsExpected()
    {
        using TreeView control = new();
        TreeNode node = new(control);

        Assert.Equal(control.AccessibilityObject, node.AccessibilityObject.ItemSelectionContainer);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_Toggle_WorksExpected(bool isChecked)
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control) { Checked = isChecked };

        Assert.Equal(isChecked, node.Checked);

        node.AccessibilityObject.Toggle();

        Assert.Equal(!isChecked, node.Checked);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [BoolData]
    public void TreeNodeAccessibleObject_ToggleState_WorksExpected(bool isChecked)
    {
        using TreeView control = new() { CheckBoxes = true };
        TreeNode node = new(control) { Checked = isChecked };

        ToggleState expected = isChecked ? ToggleState.ToggleState_On : ToggleState.ToggleState_Off;

        Assert.Equal(expected, node.AccessibilityObject.ToggleState);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_SelectItem_WorksExpected()
    {
        using TreeView control = new();

        control.Nodes.Add("Node1");
        control.Nodes.Add("Node2");
        control.Nodes.Add("Node3");
        control.Nodes.Add("Node4");

        Assert.Null(control.SelectedNode);

        control.Nodes[1].AccessibilityObject.SelectItem();
        Assert.Equal("Node2", control.SelectedNode?.Text);

        control.Nodes[3].AccessibilityObject.SelectItem();
        Assert.Equal("Node4", control.SelectedNode?.Text);

        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_Value_EqualsText()
    {
        using TreeView control = new() { LabelEdit = true };
        string testText = "This is test string for Text property of TreeNode.";
        TreeNode node = new(control) { Text = testText };

        Assert.Equal(testText, node.AccessibilityObject.Value);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_HasChildren_ExpandCollapsePatternAvailable()
    {
        using TreeView control = new();
        TreeNode node = new(control);
        node.Nodes.Add("ChildNode");

        Assert.True(node.AccessibilityObject.IsPatternSupported(UIA_PATTERN_ID.UIA_ExpandCollapsePatternId));
        Assert.True(node._childCount > 0);
        Assert.False(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_IsDisconnected_WhenTreeViewIsReleased()
    {
        using TreeView control = new();
        AccessibilityObjectDisconnectTrackingTreeNode firstLevelNode = new(control);
        control.Nodes.Add(firstLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode secondLevelNode = new(control);
        firstLevelNode.Nodes.Add(secondLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode thirdLevelNode = new(control);
        secondLevelNode.Nodes.Add(thirdLevelNode);
        control.CreateControl();

        control.ReleaseUiaProvider(control.HWND);

        Assert.True(firstLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(secondLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(thirdLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_IsDisconnected_WhenRemoved()
    {
        using TreeView control = new();
        AccessibilityObjectDisconnectTrackingTreeNode firstLevelNode = new(control);
        control.Nodes.Add(firstLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode secondLevelNode = new(control);
        firstLevelNode.Nodes.Add(secondLevelNode);
        control.CreateControl();

        control.Nodes.Remove(firstLevelNode);

        Assert.True(firstLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(secondLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_IsDisconnected_WhenCleared()
    {
        using TreeView control = new();
        AccessibilityObjectDisconnectTrackingTreeNode firstLevelNode = new(control);
        control.Nodes.Add(firstLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode secondLevelNode = new(control);
        firstLevelNode.Nodes.Add(secondLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode thirdLevelNode = new(control);
        secondLevelNode.Nodes.Add(thirdLevelNode);
        control.CreateControl();

        control.Nodes.Clear();

        Assert.True(firstLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(secondLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(thirdLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(control.IsHandleCreated);
    }

    [WinFormsFact]
    public void TreeNodeAccessibleObject_IsDisconnected_WhenReplacedByIndex()
    {
        using TreeView control = new();
        AccessibilityObjectDisconnectTrackingTreeNode firstLevelNode = new(control);
        control.Nodes.Add(firstLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode secondLevelNode = new(control);
        firstLevelNode.Nodes.Add(secondLevelNode);
        AccessibilityObjectDisconnectTrackingTreeNode thirdLevelNode = new(control);
        secondLevelNode.Nodes.Add(thirdLevelNode);
        control.CreateControl();

        control.Nodes[0] = new TreeNode();

        Assert.True(firstLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(secondLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(thirdLevelNode.IsAccessibilityObjectDisconnected);
        Assert.True(control.IsHandleCreated);
    }

    [WinFormsTheory]
    [InlineData(false, false, true)]
    [InlineData(true, true, false)]
    public void TreeNodeAccessibleObject_Bounds_ShouldMatchExpected_IfConditionsMet(bool createControl, bool makeNodeVisible, bool expectBoundsEmpty)
    {
        using TreeView treeView = new();
        TreeNode node = new("Node");
        treeView.Nodes.Add(node);

        if (createControl)
        {
            treeView.CreateControl();
            if (makeNodeVisible)
            {
                treeView.ExpandAll();
            }
        }

        var accessibleObject = node.AccessibilityObject;

        if (expectBoundsEmpty)
        {
            accessibleObject.Bounds.Should().Be(Rectangle.Empty);
        }
        else
        {
            accessibleObject.Bounds.Should().NotBe(Rectangle.Empty);
        }
    }

    private class AccessibilityObjectDisconnectTrackingTreeNode : TreeNode
    {
        public AccessibilityObjectDisconnectTrackingTreeNode(TreeView treeView) : base(treeView) { }

        public bool IsAccessibilityObjectDisconnected { get; private set; }

        internal override void ReleaseUiaProvider()
        {
            base.ReleaseUiaProvider();
            IsAccessibilityObjectDisconnected = true;
        }
    }
}
