// 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;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Text;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.RenameTracking;

[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.RenameTracking)]
public sealed class RenameTrackingTaggerProviderTests
{
    [WpfFact]
    public async Task RenameTrackingNotOnCreation()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingNotInBlankFile()
    {
        var code = @"$$";
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("d");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingTypingAtEnd()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");
        await state.AssertTag("C", "Cat");
    }

    [WpfFact]
    public async Task RenameTrackingTypingAtBeginning()
    {
        var code = """
            class $$C
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("AB");
        await state.AssertTag("C", "ABC");
    }

    [WpfFact]
    public async Task RenameTrackingTypingInMiddle()
    {
        var code = """
            class AB$$CD
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("ZZ");
        await state.AssertTag("ABCD", "ABZZCD");
    }

    [WpfFact]
    public async Task RenameTrackingDeleteFromEnd()
    {
        var code = """
            class ABC$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        await state.AssertTag("ABC", "AB");
    }

    [WpfFact]
    public async Task RenameTrackingDeleteFromBeginning()
    {
        var code = """
            class $$ABC
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Delete();
        await state.AssertTag("ABC", "BC");
    }

    [WpfFact]
    public async Task RenameTrackingDeleteFromMiddle()
    {
        var code = """
            class AB$$C
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        await state.AssertTag("ABC", "AC");
    }

    [WpfFact]
    public async Task RenameTrackingNotOnClassKeyword()
    {
        var code = """
            class$$ ABCD
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("d");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingNotAtMethodArgument()
    {
        var code = """
            class ABCD
            {
                void Goo(int x)
                {
                    int abc = 3;
                    Goo($$
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("a");
        await state.AssertNoTag();

        state.EditorOperations.InsertText("b");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingSessionContinuesAfterViewingTag()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");
        await state.AssertTag("C", "Cat");

        state.EditorOperations.InsertText("s");
        await state.AssertTag("C", "Cats");
    }

    [WpfFact]
    public async Task RenameTrackingNotInString()
    {
        var code = """
            class C
            {
                void Goo()
                {
                    string s = "abc$$"
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("d");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingHandlesAtSignAsCSharpEscape()
    {
        var code = """
            class $$C
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("@");
        await state.AssertTag("C", "@C");
    }

    [WpfFact]
    public async Task RenameTrackingHandlesSquareBracketsAsVisualBasicEscape()
    {
        var code = """
            Class $$C
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("[");
        await state.AssertNoTag();

        state.MoveCaret(1);
        state.EditorOperations.InsertText("]");
        await state.AssertTag("C", "[C]");
    }

    [WpfFact]
    public async Task RenameTrackingNotOnSquareBracketsInCSharp()
    {
        var code = """
            class $$C
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("[");
        await state.AssertNoTag();

        state.MoveCaret(1);
        state.EditorOperations.InsertText("]");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingHandlesUnicode()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("\u0414\u046E\u046A\u00DB\u00CA\u00DB\u00C4\u00C1\u00CD\u00E4\u00E1\u0152\u0178\u00F5\u00E0\u0178\u00FC\u00C4\u00B5\u00C1i\u00DBE\u00EA\u00E0\u00EA\u00E8\u00E4\u00E5\u00ED\u00F2\u00E8\u00F4\u00E8\u00EA\u00E0\u00F2\u00EE\u00F0\u00F1\u00EB\u00EE\u00E2\u00EE");
        await state.AssertTag("C", "C\u0414\u046E\u046A\u00DB\u00CA\u00DB\u00C4\u00C1\u00CD\u00E4\u00E1\u0152\u0178\u00F5\u00E0\u0178\u00FC\u00C4\u00B5\u00C1i\u00DBE\u00EA\u00E0\u00EA\u00E8\u00E4\u00E5\u00ED\u00F2\u00E8\u00F4\u00E8\u00EA\u00E0\u00F2\u00EE\u00F0\u00F1\u00EB\u00EE\u00E2\u00EE");
    }

    [WpfFact]
    public async Task RenameTrackingThroughKeyword()
    {
        var code = """
            class i$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("n");
        await state.AssertNoTag();

        state.EditorOperations.InsertText("t");
        await state.AssertNoTag();

        state.EditorOperations.InsertText("s");
        await state.AssertTag("i", "ints");
    }

    [WpfFact]
    public async Task RenameTrackingThroughIllegalStartCharacter()
    {
        var code = """
            class $$abc
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("9");
        await state.AssertNoTag();

        state.MoveCaret(-1);
        state.EditorOperations.InsertText("t");
        await state.AssertTag("abc", "t9abc");
    }

    [WpfFact]
    public async Task RenameTrackingOnBothSidesOfIdentifier()
    {
        var code = """
            class $$Def
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("Abc");
        await state.AssertTag("Def", "AbcDef");

        state.MoveCaret(3);
        state.EditorOperations.InsertText("Ghi");
        await state.AssertTag("Def", "AbcDefGhi");
    }

    [WpfFact]
    public async Task RenameTrackingThroughSameIdentifier()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("C", "Cs");

        state.EditorOperations.Backspace();
        await state.AssertNoTag();

        state.EditorOperations.InsertText("s");
        await state.AssertTag("C", "Cs");
    }

    [WpfFact]
    public async Task RenameTrackingThroughEmptyString()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        await state.AssertNoTag();

        state.EditorOperations.InsertText("D");
        await state.AssertTag("C", "D");
    }

    [WpfFact]
    public async Task RenameTrackingThroughEmptyStringWithCaretMove()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.MoveCaret(-4);
        state.MoveCaret(4);
        await state.AssertNoTag();

        state.EditorOperations.InsertText("D");
        await state.AssertTag("C", "D");
    }

    [WpfFact]
    public async Task RenameTrackingNotThroughEmptyStringResumeOnDifferentSpace()
    {
        var code = """
            class  C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();

        // Move to previous space
        state.MoveCaret(-1);

        state.EditorOperations.InsertText("D");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingReplaceIdentifierSuffix()
    {
        var code = """
            class Identifi[|er|]$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        var textSpan = state.HostDocument.SelectedSpans.Single();
        state.EditorOperations.ReplaceText(new Span(textSpan.Start, textSpan.Length), "cation");
        await state.AssertTag("Identifier", "Identification");
    }

    [WpfFact]
    public async Task RenameTrackingReplaceIdentifierPrefix()
    {
        var code = """
            class $$[|Ident|]ifier
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        var textSpan = state.HostDocument.SelectedSpans.Single();
        state.EditorOperations.ReplaceText(new Span(textSpan.Start, textSpan.Length), "Complex");
        await state.AssertTag("Identifier", "Complexifier");
    }

    [WpfFact]
    public async Task RenameTrackingReplaceIdentifierCompletely()
    {
        var code = """
            class [|Cat|]$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        var textSpan = state.HostDocument.SelectedSpans.Single();
        state.EditorOperations.ReplaceText(new Span(textSpan.Start, textSpan.Length), "Dog");
        await state.AssertTag("Cat", "Dog");
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/34280")]
    public async Task RenameTrackingReplaceIdentifierWithDiscard()
    {
        var code = """
            class Class
            {
                int Method()
                {
                    int i;
                    [|i|]$$ = Method();
                    rteurn 0;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        var textSpan = state.HostDocument.SelectedSpans.Single();
        state.EditorOperations.ReplaceText(new Span(textSpan.Start, textSpan.Length), "_");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingNotAfterInvoke()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);

        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingInvokeAndChangeBackToOriginal()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);

        await state.AssertNoTag();

        state.EditorOperations.Backspace();
        await state.AssertTag("Cats", "Cat");
    }

    [WpfFact]
    public async Task RenameTrackingUndoOnceAndStartNewSession()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("abc");
        await state.AssertTag("Cat", "Catabc", invokeAction: true);

        await state.AssertNoTag();

        // Back to original
        state.Undo();
        await state.AssertNoTag();

        state.EditorOperations.InsertText("xyz");
        await state.AssertTag("Cat", "Catxyz");
    }

    [WpfFact]
    public async Task RenameTrackingUndoTwiceAndContinueSession()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("abc");
        await state.AssertTag("Cat", "Catabc", invokeAction: true);

        await state.AssertNoTag();

        // Resume rename tracking session
        state.Undo(2);
        await state.AssertTag("Cat", "Catabc");

        state.EditorOperations.InsertText("xyz");
        await state.AssertTag("Cat", "Catabcxyz");
    }

    [WpfFact]
    public async Task RenameTrackingRedoAlwaysClearsState()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);

        await state.AssertNoTag();

        // Resume rename tracking session
        state.Undo(2);
        await state.AssertTag("Cat", "Cats");

        state.Redo();
        await state.AssertNoTag();

        state.Redo();
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingUndoTwiceRedoTwiceUndoStillWorks()
    {
        var code = """
            class Cat$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);

        await state.AssertNoTag();

        // Resume rename tracking session
        state.Undo(2);
        await state.AssertTag("Cat", "Cats");

        state.Redo(2);
        await state.AssertNoTag();

        // Back to original
        state.Undo();
        await state.AssertNoTag();

        // Resume rename tracking session
        state.Undo();
        await state.AssertTag("Cat", "Cats");
    }

    [WpfFact]
    public async Task RenameTrackingOnReference_ParameterAsArgument()
    {
        var code = """
            class C
            {
                void M(int x)
                {
                    M(x$$);
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("yz");
        await state.AssertTag("x", "xyz");
    }

    [WpfFact]
    public async Task RenameTrackingOnReference_ParameterAsNamedArgument()
    {
        var code = """
            class C
            {
                void M(int x)
                {
                    M(x$$: x);
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("yz");
        await state.AssertTag("x", "xyz");
    }

    [WpfFact]
    public async Task RenameTrackingOnReference_Namespace()
    {
        var code = """
            namespace NS
            {
                class C
                {
                    static void M()
                    {
                        NS$$.C.M();
                    }
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("A");
        await state.AssertTag("NS", "NSA");
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")]
    public async Task RenameTrackingOnReference_Attribute_CSharp()
    {
        var code = """
            using System;

            class [|$$ustom|]Attribute : Attribute
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("C");
        await state.AssertTag("ustomAttribute", "CustomAttribute", invokeAction: true);
        Assert.Equal("""
            using System;

            class CustomAttribute : Attribute
            {
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")]
    public async Task RenameTrackingOnReference_Attribute_VB()
    {
        var code = """
            Import System;

            Public Class [|$$ustom|]Attribute 
                    Inherits Attribute
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("C");
        await state.AssertTag("ustomAttribute", "CustomAttribute", invokeAction: true);
        Assert.Equal("""
            Import System;

            Public Class CustomAttribute 
                    Inherits Attribute
            End Class
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")]
    public async Task RenameTrackingOnReference_Capitalized_Attribute_VB()
    {
        var code = """
            Import System;

            Public Class [|$$ustom|]ATTRIBUTE 
                    Inherits Attribute
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("C");
        await state.AssertTag("ustomATTRIBUTE", "CustomATTRIBUTE", invokeAction: true);
        Assert.Equal("""
            Import System;

            Public Class CustomATTRIBUTE 
                    Inherits Attribute
            End Class
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")]
    public async Task RenameTrackingOnReference_Not_Capitalized_Attribute_VB()
    {
        var code = """
            Import System;

            Public Class [|$$ustom|]attribute 
                    Inherits Attribute
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("C");
        await state.AssertTag("ustomattribute", "Customattribute", invokeAction: true);
        Assert.Equal("""
            Import System;

            Public Class Customattribute 
                    Inherits Attribute
            End Class
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
    }

    [WpfFact]
    public async Task RenameTrackingNotifiesThirdPartiesOfRenameOperation()
    {
        var code = """
            class Cat$$
            {
                public Cat()
                {
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);
        Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount);
        Assert.Equal(1, state.RefactorNotifyService.OnAfterSymbolRenamedCount);
        Assert.Equal("""
            class Cats
            {
                public Cats()
                {
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());

        state.AssertNoNotificationMessage();
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingHonorsThirdPartyRequestsForCancellationBeforeRename()
    {
        var code = """
            class Cat$$
            {
                public Cat()
                {
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp, onBeforeGlobalSymbolRenamedReturnValue: false);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);
        Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount);

        // Make sure the rename didn't proceed
        Assert.Equal(0, state.RefactorNotifyService.OnAfterSymbolRenamedCount);
        await state.AssertNoTag();
        Assert.Equal("""
            class Cat
            {
                public Cat()
                {
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());

        state.AssertNotificationMessage();
    }

    [WpfFact]
    public async Task RenameTrackingAlertsAboutThirdPartyRequestsForCancellationAfterRename()
    {
        var code = """
            class Cat$$
            {
                public Cat()
                {
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp, onAfterGlobalSymbolRenamedReturnValue: false);
        state.EditorOperations.InsertText("s");
        await state.AssertTag("Cat", "Cats", invokeAction: true);

        Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount);
        Assert.Equal(1, state.RefactorNotifyService.OnAfterSymbolRenamedCount);
        state.AssertNotificationMessage();

        // Make sure the rename completed            
        Assert.Equal("""
            class Cats
            {
                public Cats()
                {
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530469")]
    public async Task RenameTrackingNotWhenStartedFromTextualWordInTrivia()
    {
        var code = """
            Module Program
                Sub Main()
                    Dim [x$$ = 1
                End Sub
            End Module
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("]");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530495")]
    public async Task RenameTrackingNotWhenCaseCorrectingReference()
    {
        var code = """
            Module Program
                Sub Main()
                    $$main()
                End Sub
            End Module
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Delete();
        await state.AssertTag("main", "ain");
        state.EditorOperations.InsertText("M");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/599508")]
    public async Task RenameTrackingNotWhenNewIdentifierReferenceBinds()
    {
        var code = """
            Module Program
                Sub Main()
                    $$[|main|]()
                End Sub
                Sub Goo()
                End Sub
            End Module
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        var textSpan = state.HostDocument.SelectedSpans.Single();
        state.EditorOperations.ReplaceText(new Span(textSpan.Start, textSpan.Length), "Go");
        await state.AssertTag("main", "Go");
        state.EditorOperations.InsertText("o");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530400")]
    public async Task RenameTrackingNotWhenDeclaringEnumMembers()
    {
        var code = """
            Enum E
            $$    
            End Enum
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("    a");
        state.EditorOperations.InsertText("b");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1028072")]
    public void RenameTrackingDoesNotThrowAggregateException()
    {
        var notRenamable = Task.FromResult(RenameTrackingTaggerProvider.TriggerIdentifierKind.NotRenamable);
        Assert.False(RenameTrackingTaggerProvider.IsRenamableIdentifierFastCheck(notRenamable, out _));

        var source = new TaskCompletionSource<RenameTrackingTaggerProvider.TriggerIdentifierKind>();
        Assert.False(RenameTrackingTaggerProvider.IsRenamableIdentifierFastCheck(source.Task, out _));
        source.TrySetResult(RenameTrackingTaggerProvider.TriggerIdentifierKind.RenamableReference);
        Assert.True(RenameTrackingTaggerProvider.IsRenamableIdentifierFastCheck(source.Task, out _));

        source = new TaskCompletionSource<RenameTrackingTaggerProvider.TriggerIdentifierKind>();
        source.TrySetCanceled();
        Assert.False(RenameTrackingTaggerProvider.IsRenamableIdentifierFastCheck(source.Task, out _));

        source = new TaskCompletionSource<RenameTrackingTaggerProvider.TriggerIdentifierKind>();
        source.TrySetException(new OperationCanceledException());
        Assert.False(RenameTrackingTaggerProvider.IsRenamableIdentifierFastCheck(source.Task, out _));
    }

    [WpfFact]
    [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1063943")]
    [WorkItem("https://github.com/dotnet/roslyn/issues/10914")]
    public async Task RenameTrackingOnReferenceWithWrongNumberOfArguments()
    {
        var code = """
            class C
            {
                void M(int x)
                {
                    M$$();
                }
            }
            """;

        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("eow");
        await state.AssertTag("M", "Meow");
    }

    [WpfFact]
    [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1063943")]
    [WorkItem("https://github.com/dotnet/roslyn/issues/10914")]
    public async Task RenameTrackingOnReferenceWithWrongNumberOfArguments_Overloads()
    {
        var code = """
            class C
            {
                void M(int x)
                {
                    M$$();
                }

                void M(bool x)
                {
                }
            }
            """;

        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("eow");
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task CancelRenameTracking()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");
        await state.AssertTag("C", "Cat");
        state.SendEscape();
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTrackingNotWhenDeclaringEnumMembersEvenAfterCancellation()
    {
        var code = """
            Enum E
            $$    
            End Enum
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("    a");
        state.EditorOperations.InsertText("b");
        await state.AssertNoTag();
        state.SendEscape();
        state.EditorOperations.InsertText("c");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/540")]
    public async Task RenameTrackingDoesNotProvideDiagnosticAfterCancellation()
    {
        var code = """
            class C$$
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");
        await state.AssertTag("C", "Cat");

        Assert.NotNull(await state.TryGetCodeActionAsync());

        state.SendEscape();
        await state.AssertNoTag();

        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact]
    public async Task RenameTracking_Nameof_FromMethodGroupReference()
    {
        var code = """
            class C
            {
                void M()
                {
                    nameof(M$$).ToString();
                }

                void M(int x)
                {
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");

        await state.AssertTag("M", "Mat", invokeAction: true);

        // Make sure the rename completed            
        Assert.Equal("""
            class C
            {
                void Mat()
                {
                    nameof(Mat).ToString();
                }

                void Mat(int x)
                {
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTracking_Nameof_FromMethodDefinition_NoOverloads()
    {
        var code = """
            class C
            {
                void M$$()
                {
                    nameof(M).ToString();
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");

        await state.AssertTag("M", "Mat", invokeAction: true);

        // Make sure the rename completed            
        Assert.Equal("""
            class C
            {
                void Mat()
                {
                    nameof(Mat).ToString();
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTracking_Nameof_FromMethodDefinition_WithOverloads()
    {
        var code = """
            class C
            {
                void M$$()
                {
                    nameof(M).ToString();
                }

                void M(int x)
                {
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("at");

        await state.AssertTag("M", "Mat", invokeAction: true);

        // Make sure the rename completed            
        Assert.Equal("""
            class C
            {
                void Mat()
                {
                    nameof(M).ToString();
                }

                void M(int x)
                {
                }
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTracking_Nameof_FromReferenceToMetadata_NoTag()
    {
        var code = """
            class C
            {
                void M()
                {
                    var x = nameof(ToString$$);
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("z");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/762964")]
    public async Task RenameTracking_NoTagWhenFirstEditChangesReferenceToAnotherSymbol()
    {
        var code = """
            class C
            {
                void M()
                {
                    int abc = 7;
                    int ab = 8;
                    int z = abc$$;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/2605")]
    public async Task RenameTracking_CannotRenameToVarInCSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    C$$ c;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.InsertText("va");

        await state.AssertTag("C", "va");
        Assert.NotNull(await state.TryGetCodeActionAsync());

        state.EditorOperations.InsertText("r");
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());

        state.EditorOperations.InsertText("p");
        await state.AssertTag("C", "varp");
        Assert.NotNull(await state.TryGetCodeActionAsync());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/2605")]
    public async Task RenameTracking_CannotRenameFromVarInCSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    var$$ c = new C();
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/2605")]
    public async Task RenameTracking_CanRenameToVarInVisualBasic()
    {
        var code = """
            Class C
                Sub M()
                    Dim x as C$$
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Backspace();
        state.EditorOperations.InsertText("var");

        await state.AssertTag("C", "var");
        Assert.NotNull(await state.TryGetCodeActionAsync());
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/2605")]
    public async Task RenameTracking_CannotRenameToDynamicInCSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    C$$ c;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.InsertText("dynami");

        await state.AssertTag("C", "dynami");
        Assert.NotNull(await state.TryGetCodeActionAsync());

        state.EditorOperations.InsertText("c");
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());

        state.EditorOperations.InsertText("s");
        await state.AssertTag("C", "dynamics");
        Assert.NotNull(await state.TryGetCodeActionAsync());
    }

    [WpfFact]
    public async Task RenameImplicitTupleField()
    {
        var code = """
            class C
            {
                void M()
                {
                    (int, int) x = (1, 2);
                    var y = x.Item1$$;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact]
    public async Task RenameImplicitTupleFieldVB()
    {
        var code = """
            class C
                Sub M()
                    Dim x as (Integer, Integer) = (1, 2)
                    Dim y = x.Item1$$
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact]
    public async Task RenameImplicitTupleFieldExtended()
    {
        var code = """
            class C
            {
                void M()
                {
                    (int, int, int, int, int, int, int, int, int, int) x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
                    var y = x.Item9$$;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact]
    public async Task RenameImplicitTupleFieldExtendedVB()
    {
        var code = """
            Class C
                Sub M()
                    Dim x as (Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer) = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                    Dim y = x.Item9$$
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();
        await state.AssertNoTag();
        Assert.Null(await state.TryGetCodeActionAsync());
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleReturnDeclaration_CSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    (int abc$$, int) x = (1, 2);
                    var y = x.abc;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();

        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleReturnDeclaration_VB()
    {
        var code = """
            class C
                Sub M()
                    Dim x as (abc$$ as integer, int Item2 as integer) = (1, 2)
                    Dim y = x.abc
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();

        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleFieldReference_CSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    (int abc, int) x = (1, 2);
                    var y = x.abc$$;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();

        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleFieldReference_VB()
    {
        var code = """
            class C
                Sub M()
                    Dim x as (abc as integer, int Item2 as integer) = (1, 2)
                    Dim y = x.abc$$
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.Backspace();
        state.EditorOperations.Backspace();

        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleElementsInDeclarations_CSharp()
    {
        var code = """
            class C
            {
                void M()
                {
                    var t = (x$$: 1, y: 2);
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("2");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=371205")]
    public async Task RenameTrackingNotOnExplicitTupleElementsInDeclarations_VB()
    {
        var code = """
            Class C
                Sub M()
                    Dim t = (x$$:=1, y:=2)
                End Sub
            End Class
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.VisualBasic);
        state.EditorOperations.InsertText("2");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/14159")]
    public async Task RenameTrackingNotOnWellKnownValueTupleType()
    {
        var workspaceXml = """
            <Workspace>
                <Project Language="C#" CommonReferences="true" LanguageVersion="7">
                    <Document>
            using System;

            class C
            {
                void M()
                {
                    var x = new ValueTuple$$&lt;int&gt;();
                }
            }

            namespace System
            {
                public struct ValueTuple&lt;T1&gt;
                {
                    public T1 Item1;
                }
            }
                    </Document>
                </Project>
            </Workspace>
            """;
        using var state = RenameTrackingTestState.CreateFromWorkspaceXml(workspaceXml, LanguageNames.CSharp);
        state.EditorOperations.InsertText("2");
        await state.AssertNoTag();
    }

    [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/14159")]
    public async Task RenameTrackingOnThingsCalledValueTupleThatAreNotTheWellKnownType()
    {
        var workspaceXml = """
            <Workspace>
                <Project Language="C#" CommonReferences="true" LanguageVersion="7">
                    <Document>
            class C
            {
                void M()
                {
                    var x = new ValueTuple$$&lt;int&gt;();
                }
            }

            public struct ValueTuple&lt;T1&gt;
            {
                public T1 Item1;
            }
                    </Document>
                </Project>
            </Workspace>
            """;
        using var state = RenameTrackingTestState.CreateFromWorkspaceXml(workspaceXml, LanguageNames.CSharp);
        state.EditorOperations.InsertText("2");
        await state.AssertTag("ValueTuple", "ValueTuple2");
    }

    [WpfFact]
    public async Task RenameTrackingOnDeconstruct()
    {
        var code = """
            class C
            {
                void Deconstruct$$(out int x1, out int x2) { x1 = 1; x2 = 2; }
                void M()
                {
                    var (y1, y2) = this;
                }
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("2");
        await state.AssertTag("Deconstruct", "Deconstruct2");
    }

    [WpfFact]
    public async Task RenameTracking_UnmanagedConstraint_Keyword()
    {
        var code = """
            class C&lt;T&gt; where T : $$unmanaged
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        await state.AssertNoTag();
    }

    [WpfFact]
    public async Task RenameTracking_UnmanagedConstraint_Type()
    {
        var code = """
            interface unmanaged
            {
            }
            class C&lt;T&gt; where T : $$unmanaged
            {
            }
            """;
        using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp);
        state.EditorOperations.InsertText("my");

        await state.AssertTag("unmanaged", "myunmanaged", invokeAction: true);

        // Make sure the rename completed            
        Assert.Equal("""
            interface myunmanaged
            {
            }
            class C<T> where T : myunmanaged
            {
            }
            """, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText());
        await state.AssertNoTag();
    }
}
