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

using System.Drawing;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
using static System.Windows.Forms.ListView;

namespace System.Windows.Forms;

public partial class ListViewGroup
{
    internal sealed class ListViewGroupAccessibleObject : AccessibleObject
    {
        private readonly ListView _owningListView;
        private readonly ListViewAccessibleObject _owningListViewAccessibilityObject;
        private readonly ListViewGroup _owningGroup;
        private readonly bool _owningGroupIsDefault;

        public ListViewGroupAccessibleObject(ListViewGroup owningGroup, bool owningGroupIsDefault)
        {
            _owningGroup = owningGroup.OrThrowIfNull();

            // Using item from group for getting of ListView is a workaround for https://github.com/dotnet/winforms/issues/4019
            _owningListView = owningGroup.ListView
                ?? (owningGroup.Items.Count > 0 && _owningGroup.Items[0].ListView is ListView listView
                    ? listView
                    : throw new InvalidOperationException(nameof(owningGroup.ListView)));

            _owningListViewAccessibilityObject = _owningListView.AccessibilityObject as ListViewAccessibleObject
                ?? throw new InvalidOperationException(nameof(_owningListView.AccessibilityObject));

            _owningGroupIsDefault = owningGroupIsDefault;
        }

        private protected override string AutomationId
            => $"{nameof(ListViewGroup)}-{CurrentIndex}";

        public override Rectangle Bounds
        {
            get
            {
                if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed || IsEmpty)
                {
                    return Rectangle.Empty;
                }

                int nativeGroupId = GetNativeGroupId();
                if (nativeGroupId == -1)
                {
                    return Rectangle.Empty;
                }

                uint rectType = _owningGroup.CollapsedState == ListViewGroupCollapsedState.Collapsed
                    ? PInvoke.LVGGR_HEADER
                    : PInvoke.LVGGR_GROUP;

                // Get the native rectangle
                RECT groupRect = default;

                // Using the "top" property, we set which rectangle type of the group we want to get
                // This is described in more detail in https://docs.microsoft.com/windows/win32/controls/lvm-getgrouprect
                groupRect.top = (int)rectType;
                PInvoke.SendMessage(_owningListView, PInvoke.LVM_GETGROUPRECT, (WPARAM)nativeGroupId, ref groupRect);

                // Using the following code, we limit the size of the ListViewGroup rectangle
                // so that it does not go beyond the rectangle of the ListView
                Rectangle listViewBounds = _owningListView.AccessibilityObject.Bounds;
                groupRect = _owningListView.RectangleToScreen(groupRect);
                groupRect.top = Math.Max(listViewBounds.Top, groupRect.top);
                groupRect.bottom = Math.Min(listViewBounds.Bottom, groupRect.bottom);
                groupRect.left = Math.Max(listViewBounds.Left, groupRect.left);
                groupRect.right = Math.Min(listViewBounds.Right, groupRect.right);

                return groupRect;
            }
        }

        internal int CurrentIndex =>
            // The default group has 0 index, as it is always displayed first.
            _owningGroupIsDefault
                ? 0
                // When calculating the index of other groups, we add a shift if the default group is displayed
                : _owningListViewAccessibilityObject.OwnerHasDefaultGroup
                    ? _owningListView.Groups.IndexOf(_owningGroup) + 1
                    : _owningListView.Groups.IndexOf(_owningGroup);

        public override string DefaultAction => SR.AccessibleActionDoubleClick;

        private protected override bool IsInternal => true;

        internal override bool CanGetDefaultActionInternal => false;

        internal override ExpandCollapseState ExpandCollapseState =>
            _owningGroup.CollapsedState == ListViewGroupCollapsedState.Collapsed
                ? ExpandCollapseState.ExpandCollapseState_Collapsed
                : ExpandCollapseState.ExpandCollapseState_Expanded;

        internal override IRawElementProviderFragmentRoot.Interface FragmentRoot => _owningListView.AccessibilityObject;

        public override string Name =>
            !string.IsNullOrEmpty(_owningGroup.Subtitle)
                ? $"{_owningGroup.Header}. {_owningGroup.Subtitle}"
                : _owningGroup.Header;

        internal override bool CanGetNameInternal => false;

        public override AccessibleRole Role => AccessibleRole.Grouping;

        internal override int[] RuntimeId
        {
            get
            {
                int[] id = _owningListViewAccessibilityObject.RuntimeId;

                Debug.Assert(id.Length >= 2);

                return
                [
                    id[0],
                    id[1],
                    // Win32 control specific RuntimeID constant, is used in similar Win32 controls and is used in WinForms controls for consistency.
                    4,
                    GetHashCode()
                ];
            }
        }

        public override AccessibleStates State
        {
            get
            {
                AccessibleStates state = AccessibleStates.Focusable;

                if (Focused)
                {
                    state |= AccessibleStates.Focused;
                }

                return state;
            }
        }

        private bool IsEmpty => GetVisibleItems().Count == 0;

        internal override void Collapse()
            => _owningGroup.CollapsedState = ListViewGroupCollapsedState.Collapsed;

        public override void DoDefaultAction() => SetFocus();

        internal override void Expand()
            => _owningGroup.CollapsedState = ListViewGroupCollapsedState.Expanded;

        private bool Focused
            => GetNativeFocus() || _owningGroup.Focused;

        private bool GetNativeFocus()
        {
            if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed)
            {
                return false;
            }

            int nativeGroupId = GetNativeGroupId();
            if (nativeGroupId == -1)
            {
                return false;
            }

            return (LIST_VIEW_GROUP_STATE_FLAGS)(uint)PInvoke.SendMessage(
                _owningListView,
                PInvoke.LVM_GETGROUPSTATE,
                (WPARAM)nativeGroupId,
                (LPARAM)(uint)LIST_VIEW_GROUP_STATE_FLAGS.LVGS_FOCUSED) == LIST_VIEW_GROUP_STATE_FLAGS.LVGS_FOCUSED;
        }

        private int GetNativeGroupId()
        {
            if (PInvoke.SendMessage(_owningListView, PInvoke.LVM_HASGROUP, (WPARAM)_owningGroup.ID) == 0)
            {
                return -1;
            }

            return _owningGroup.ID;
        }

        internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID)
            => propertyID switch
            {
                UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_GroupControlTypeId,
                UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)(_owningListView.Focused && Focused),
                UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)_owningListView.Enabled,
                UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focusable),
                UIA_PROPERTY_ID.UIA_NativeWindowHandlePropertyId => UIAHelper.WindowHandleToVariant(HWND.Null),
                _ => base.GetPropertyValue(propertyID)
            };

        internal IReadOnlyList<ListViewItem> GetVisibleItems()
        {
            List<ListViewItem> visibleItems = [];
            if (_owningGroupIsDefault)
            {
                foreach (ListViewItem? listViewItem in _owningListView.Items)
                {
                    if (listViewItem is not null && listViewItem.Group is null)
                    {
                        visibleItems.Add(listViewItem);
                    }
                }

                return visibleItems;
            }

            foreach (ListViewItem listViewItem in _owningGroup.Items)
            {
                if (listViewItem.ListView is not null)
                {
                    visibleItems.Add(listViewItem);
                }
            }

            return visibleItems;
        }

        internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
        {
            if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed || IsEmpty)
            {
                return null;
            }

            switch (direction)
            {
                case NavigateDirection.NavigateDirection_Parent:
                    return _owningListViewAccessibilityObject;
                case NavigateDirection.NavigateDirection_NextSibling:
                    int childIndex = _owningListViewAccessibilityObject.GetChildIndex(this);
                    return childIndex == InvalidIndex
                        ? null
                        : _owningListViewAccessibilityObject.GetChild(childIndex + 1);
                case NavigateDirection.NavigateDirection_PreviousSibling:
                    return _owningListViewAccessibilityObject.GetChild(_owningListViewAccessibilityObject.GetChildIndex(this) - 1);
                case NavigateDirection.NavigateDirection_FirstChild:
                    return GetChild(0);
                case NavigateDirection.NavigateDirection_LastChild:
                    IReadOnlyList<ListViewItem> visibleItems = GetVisibleItems();
                    return visibleItems.Count > 0 ? visibleItems[visibleItems.Count - 1].AccessibilityObject : null;

                default:
                    return null;
            }
        }

        public override AccessibleObject? GetChild(int index)
        {
            if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed || index < 0)
            {
                return null;
            }

            IReadOnlyList<ListViewItem> visibleItems = GetVisibleItems();
            if (index >= visibleItems.Count)
            {
                return null;
            }

            return visibleItems[index].AccessibilityObject;
        }

        internal override int GetChildIndex(AccessibleObject? child)
        {
            if (child is null || !_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed)
            {
                return InvalidIndex;
            }

            IReadOnlyList<ListViewItem> visibleItems = GetVisibleItems();
            for (int i = 0; i < visibleItems.Count; i++)
            {
                if (visibleItems[i].AccessibilityObject == child)
                {
                    return i;
                }
            }

            return InvalidIndex;
        }

        public override int GetChildCount()
        {
            if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed)
            {
                return InvalidIndex;
            }

            return GetVisibleItems().Count;
        }

        internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
            => patternId switch
            {
                UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId => true,
                UIA_PATTERN_ID.UIA_ExpandCollapsePatternId => _owningGroup.CollapsedState != ListViewGroupCollapsedState.Default,
                _ => base.IsPatternSupported(patternId),
            };

        internal override unsafe void SetFocus()
        {
            if (!_owningListView.IsHandleCreated || !_owningListView.GroupsDisplayed || IsEmpty)
            {
                return;
            }

            _owningListView.FocusedGroup = _owningGroup;

            RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId);
        }
    }
}
