﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Extensibility.Testing;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.TableControl;
using Microsoft.VisualStudio.Threading;

namespace Roslyn.VisualStudio.IntegrationTests.InProcess
{
    [TestService]
    internal partial class FindReferencesWindowInProcess
    {
        // Guid of the FindRefs window.  Defined here:
        // https://devdiv.visualstudio.com/DevDiv/_git/VS?path=/src/env/ErrorList/Pkg/Guids.cs&version=GBmain&line=24
        internal static readonly Guid FindReferencesWindowGuid = new("{a80febb4-e7e0-4147-b476-21aaf2453969}");

        public async Task<ImmutableArray<ITableEntryHandle2>> GetContentsAsync(CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.FindReferences, cancellationToken);

            // Find the tool window
            var tableControl = await GetFindReferencesWindowAsync(cancellationToken);

            // Remove all grouping
            var columnStates = tableControl.ColumnStates;
            var newColumnsStates = new List<ColumnState2>();
            foreach (ColumnState2 state in columnStates)
            {
                var newState = new ColumnState2(
                    state.Name,
                    state.IsVisible,
                    state.Width,
                    state.SortPriority,
                    state.DescendingSort,
                    groupingPriority: 0);
                newColumnsStates.Add(newState);
            }

            tableControl.SetColumnStates(newColumnsStates);

            // Force a refresh, if necessary. This doesn't re-run the Find References or
            // Find Implementations operation itself, it just forces the results to be
            // realized in the table.
            var forcedUpdateResult = await tableControl.ForceUpdateAsync().WithCancellation(cancellationToken);

            // Extract the basic text of the results.
            return forcedUpdateResult.AllEntries.Cast<ITableEntryHandle2>().ToImmutableArray();
        }

        public async Task NavigateToAsync(ITableEntryHandle2 referenceInGeneratedFile, bool isPreview, bool shouldActivate, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            referenceInGeneratedFile.NavigateTo(isPreview, shouldActivate);

            // Navigation operations handled by Roslyn are tracked by FeatureAttribute.FindReferences
            await TestServices.Workspace.WaitForAllAsyncOperationsAsync(new[] { FeatureAttribute.Workspace, FeatureAttribute.FindReferences }, cancellationToken);

            // Navigation operations handled by the editor are tracked within its own JoinableTaskFactory instance
            await TestServices.Editor.WaitForEditorOperationsAsync(cancellationToken);
        }

        private async Task<IWpfTableControl2> GetFindReferencesWindowAsync(CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var shell = await GetRequiredGlobalServiceAsync<SVsUIShell, IVsUIShell>(cancellationToken);
            ErrorHandler.ThrowOnFailure(shell.FindToolWindowEx((uint)__VSFINDTOOLWIN.FTW_fFindFirst, FindReferencesWindowGuid, dwToolWinId: 0, out var windowFrame));
            ErrorHandler.ThrowOnFailure(windowFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out var toolWindow));

            // Dig through to get the Find References control.
            var toolWindowType = toolWindow.GetType();
            var toolWindowControlField = toolWindowType.GetField("Control");
            var toolWindowControl = toolWindowControlField.GetValue(toolWindow);

            // Dig further to get the results table (as opposed to the toolbar).
            var tableControlAndCommandTargetType = toolWindowControl.GetType();
            var tableControlField = tableControlAndCommandTargetType.GetField("TableControl");
            var tableControl = (IWpfTableControl2)tableControlField.GetValue(toolWindowControl);
            return tableControl;
        }
    }
}
