// 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.

#nullable disable

using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;

namespace Microsoft.CodeAnalysis.ExtractInterface;

internal abstract class AbstractExtractInterfaceCommandHandler : ICommandHandler<ExtractInterfaceCommandArgs>
{
    private readonly IThreadingContext _threadingContext;

    protected AbstractExtractInterfaceCommandHandler(IThreadingContext threadingContext)
    {
        _threadingContext = threadingContext;
    }

    public string DisplayName => EditorFeaturesResources.Extract_Interface;

    public CommandState GetCommandState(ExtractInterfaceCommandArgs args)
        => IsAvailable(args.SubjectBuffer, out _) ? CommandState.Available : CommandState.Unspecified;

    public bool ExecuteCommand(ExtractInterfaceCommandArgs args, CommandExecutionContext context)
    {
        using (context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Extract_Interface))
        {
            var subjectBuffer = args.SubjectBuffer;
            if (!IsAvailable(subjectBuffer, out var workspace))
            {
                return false;
            }

            var caretPoint = args.TextView.GetCaretPoint(subjectBuffer);
            if (!caretPoint.HasValue)
            {
                return false;
            }

            var document = subjectBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges(
                context.OperationContext, _threadingContext);
            if (document == null)
            {
                return false;
            }

            // We are about to show a modal UI dialog so we should take over the command execution
            // wait context. That means the command system won't attempt to show its own wait dialog 
            // and also will take it into consideration when measuring command handling duration.
            context.OperationContext.TakeOwnership();

            var extractInterfaceService = document.GetLanguageService<AbstractExtractInterfaceService>();
            _threadingContext.JoinableTaskFactory.Run(async () =>
            {
                // ConfigureAwait(true) here so we are back on the UI thread
                // before calling TryApplyChanges below. Make sure if other
                // async code is added between the two calls to handle thread
                // affinity accordingly
                var result = await extractInterfaceService.ExtractInterfaceAsync(
                    document,
                    caretPoint.Value.Position,
                    (errorMessage, severity) => workspace.Services.GetService<INotificationService>().SendNotification(errorMessage, severity: severity),
                    CancellationToken.None).ConfigureAwait(true);

                if (result == null || !result.Succeeded)
                {
                    return;
                }

                if (!document.Project.Solution.Workspace.TryApplyChanges(result.UpdatedSolution))
                {
                    // TODO: handle failure
                    return;
                }

                var navigationService = workspace.Services.GetService<IDocumentNavigationService>();
                await navigationService.TryNavigateToPositionAsync(
                    _threadingContext, workspace, result.NavigationDocumentId, position: 0, CancellationToken.None).ConfigureAwait(false);
            });

            return true;
        }
    }

    private static bool IsAvailable(ITextBuffer subjectBuffer, out Workspace workspace)
        => subjectBuffer.TryGetWorkspace(out workspace) &&
           workspace.CanApplyChange(ApplyChangesKind.AddDocument) &&
           workspace.CanApplyChange(ApplyChangesKind.ChangeDocument) &&
           subjectBuffer.SupportsRefactorings();
}
