// 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.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.StringCopyPaste;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;

namespace Microsoft.CodeAnalysis.Editor.CSharp.StringCopyPaste;

internal sealed partial class StringCopyPasteCommandHandler :
    IChainedCommandHandler<CutCommandArgs>,
    IChainedCommandHandler<CopyCommandArgs>
{
    public const string KeyAndVersion = nameof(StringCopyPasteCommandHandler) + "V1";

    public CommandState GetCommandState(CutCommandArgs args, Func<CommandState> nextCommandHandler)
        => nextCommandHandler();

    public CommandState GetCommandState(CopyCommandArgs args, Func<CommandState> nextCommandHandler)
        => nextCommandHandler();

    public void ExecuteCommand(CutCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
        => ExecuteCutOrCopyCommand(args.TextView, args.SubjectBuffer, nextCommandHandler, executionContext);

    public void ExecuteCommand(CopyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
        => ExecuteCutOrCopyCommand(args.TextView, args.SubjectBuffer, nextCommandHandler, executionContext);

    private void ExecuteCutOrCopyCommand(ITextView textView, ITextBuffer subjectBuffer, Action nextCommandHandler, CommandExecutionContext executionContext)
    {
        Contract.ThrowIfFalse(_threadingContext.HasMainThread);
        var (dataToStore, copyPasteService) = CaptureCutCopyInformation(textView, subjectBuffer, executionContext.OperationContext.UserCancellationToken);

        // Ensure that the copy always goes through all other handlers.
        nextCommandHandler();

        // Always try to store our data to the clipboard (if we have access to the clipboard service).  Even if we
        // didn't capture any useful data, we want to store that to blow away any prior stored data we have.
        copyPasteService?.TrySetClipboardData(KeyAndVersion, dataToStore ?? "");
    }

    private static (string? dataToStore, IStringCopyPasteService service) CaptureCutCopyInformation(
        ITextView textView, ITextBuffer subjectBuffer, CancellationToken cancellationToken)
    {
        var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
        if (document == null)
            return default;

        var copyPasteService = document.Project.Solution.Services.GetService<IStringCopyPasteService>();
        if (copyPasteService == null)
            return default;

        var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken);

        var spans = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer);

        // We only support smart copy/paste when a single selection is copied (and a single selection is pasted
        // over).  This vastly simplifies the logic we need, and it means we don't have to try to reimplement the 
        // editor logic for what it means when you are copying X selections and pasting over Y selections.
        if (spans.Count != 1)
            return default;

        var span = spans[0];
        var snapshot = span.Snapshot;
        if (span.IsEmpty)
        {
            // cut/copy on an empty span means "cut/copy the entire line".
            var line = snapshot.GetLineFromPosition(span.Start);
            span = line.ExtentIncludingLineBreak;
        }

        var stringExpression = TryGetCompatibleContainingStringExpression(
            parsedDocument, new NormalizedSnapshotSpanCollection(span));
        if (stringExpression is null)
            return default;

        var virtualCharService = document.GetRequiredLanguageService<IVirtualCharLanguageService>();
        var stringData = StringCopyPasteData.TryCreate(virtualCharService, stringExpression, span.Span.ToTextSpan());

        return (stringData?.ToJson(), copyPasteService);
    }
}
