// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor.Editor;
using Microsoft.VisualStudio.Editor.Razor;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Razor.IntegrationTests.Extensions;
using Microsoft.VisualStudio.Razor.IntegrationTests.InProcess;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;

namespace Microsoft.VisualStudio.Extensibility.Testing;

internal partial class EditorInProcess
{
    public async Task<ITextSnapshot> GetActiveSnapshotAsync(CancellationToken cancellationToken)
        => (await GetActiveTextViewAsync(cancellationToken)).TextSnapshot;

    public async Task ActivateAsync(CancellationToken cancellationToken)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

        var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
        dte.ActiveDocument.Activate();
    }

    public async Task<bool> IsUseSuggestionModeOnAsync(CancellationToken cancellationToken)
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

        var textView = await GetActiveTextViewAsync(cancellationToken);

        var subjectBuffer = textView.GetBufferContainingCaret();
        Assumes.Present(subjectBuffer);

        var options = textView.Options.GlobalOptions;
        EditorOptionKey<bool> optionKey;
        bool defaultOption;
        if (IsDebuggerTextView(textView))
        {
            optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName);
            defaultOption = true;
        }
        else
        {
            optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInCompletionOptionName);
            defaultOption = false;
        }

        if (!options.IsOptionDefined(optionKey, localScopeOnly: false))
        {
            return defaultOption;
        }

        return options.GetOptionValue(optionKey);

        static bool IsDebuggerTextView(IWpfTextView textView)
        {
            return textView.Roles.Contains("DEBUGVIEW");
        }
    }

    public async Task SetUseSuggestionModeAsync(bool value, CancellationToken cancellationToken)
    {
        if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
        {
            var dispatcher = await GetRequiredGlobalServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>(cancellationToken);
            ErrorHandler.ThrowOnFailure(dispatcher.Exec(typeof(VSConstants.VSStd2KCmdID).GUID, (uint)VSConstants.VSStd2KCmdID.ToggleConsumeFirstCompletionMode, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero));

            if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
            {
                throw new InvalidOperationException($"Edit.ToggleCompletionMode did not leave the editor in the expected state.");
            }
        }

        if (!value)
        {
            // For blocking completion mode, make sure we don't have responsive completion interfering when
            // integration tests run slowly.
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            var view = await GetActiveTextViewAsync(cancellationToken);
            var options = view.Options.GlobalOptions;
            options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, false);

            var latencyGuardOptionKey = new EditorOptionKey<bool>("EnableTypingLatencyGuard");
            options.SetOptionValue(latencyGuardOptionKey, false);
        }
    }

    public async Task SetAdvancedSettingsAsync(ClientAdvancedSettings clientAdvancedSettings, CancellationToken cancellationToken)
    {
        var clientSettingsManager = await GetComponentModelServiceAsync<IClientSettingsManager>(cancellationToken);

        clientSettingsManager.Update(clientAdvancedSettings);
    }

    public async Task<ImmutableArray<TagSpan<TTag>>> WaitForTagsAsync<TTag>(CancellationToken cancellationToken)
        where TTag : ITag
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

        return await Helper.RetryAsync(async ct =>
        {
            var tags = await GetTagsAsync<TTag>(cancellationToken);
            if (tags.Length == 0)
            {
                return default;
            }

            return tags;
        }, TimeSpan.FromMilliseconds(50), cancellationToken);
    }

    public async Task<ImmutableArray<TagSpan<TTag>>> GetTagsAsync<TTag>(CancellationToken cancellationToken)
        where TTag : ITag
    {
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

        var view = await GetActiveTextViewAsync(cancellationToken);
        var viewTagAggregatorFactory = await GetComponentModelServiceAsync<IViewTagAggregatorFactoryService>(cancellationToken);

        var aggregator = viewTagAggregatorFactory.CreateTagAggregator<TTag>(view);
        var tags = aggregator
            .GetTags(new SnapshotSpan(view.TextSnapshot, 0, view.TextSnapshot.Length))
            .Cast<IMappingTagSpan<ITag>>();

        return tags.SelectAsArray(tag => (new TagSpan<TTag>(tag.Span.GetSpans(view.TextBuffer).Single(), (TTag)tag.Tag)));
    }
}
