// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.ChangeSignature;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editor.UnitTests.ChangeSignature;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ChangeSignature
{
    [Trait(Traits.Feature, Traits.Features.ChangeSignature)]
    public partial class ChangeSignatureTests : AbstractChangeSignatureTests
    {
        protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
            => new ChangeSignatureCodeRefactoringProvider();

        protected internal override string GetLanguage()
            => LanguageNames.CSharp;

        [Fact]
        public async Task ChangeSignature_Delegates_ImplicitInvokeCalls()
        {
            var markup = """
                delegate void MyDelegate($$int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1(1, "Two", true);
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1(true, "Two");
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature,
                expectedUpdatedInvocationDocumentCode: expectedUpdatedCode, expectedSelectedIndex: 0);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_ExplicitInvokeCalls()
        {
            var markup = """
                delegate void MyDelegate(int x, string $$y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1.Invoke(1, "Two", true);
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1.Invoke(true, "Two");
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature,
                expectedUpdatedInvocationDocumentCode: expectedUpdatedCode, expectedSelectedIndex: 1);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_BeginInvokeCalls()
        {
            var markup = """
                delegate void MyDelegate(int x, string y, bool z$$);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1.BeginInvoke(1, "Two", true, null, null);
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1.BeginInvoke(true, "Two", null, null);
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature,
                expectedUpdatedInvocationDocumentCode: expectedUpdatedCode, expectedSelectedIndex: 2);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_AnonymousMethods()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = delegate (int e, string f, bool g) { var x = f.Length + (g ? 0 : 1); };
                        d1 = delegate { };
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = delegate (bool g, string f) { var x = f.Length + (g ? 0 : 1); };
                        d1 = delegate { };
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Lambdas()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = (r, s, t) => { var x = s.Length + (t ? 0 : 1); };
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = (t, s) => { var x = s.Length + (t ? 0 : 1); };
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Lambdas_RemovingOnlyParameterIntroducesParentheses()
        {
            var markup = """
                delegate void $$MyDelegate(int x);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = (r) => { System.Console.WriteLine("Test"); };
                        d1 = r => { System.Console.WriteLine("Test"); };
                        d1 = r => { System.Console.WriteLine("Test"); };
                    }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                delegate void MyDelegate();

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = () => { System.Console.WriteLine("Test"); };
                        d1 = () => { System.Console.WriteLine("Test"); };
                        d1 = () => { System.Console.WriteLine("Test"); };
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughMethodGroups_AssignedToVariable()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = Goo;
                        Goo(1, "Two", true);
                        Goo(1, false, false);
                    }

                    void Goo(int a, string b, bool c) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = null;
                        d1 = Goo;
                        Goo(true, "Two");
                        Goo(1, false, false);
                    }

                    void Goo(bool c, string b) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughMethodGroups_DelegateConstructor()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = new MyDelegate(Goo);
                        Goo(1, "Two", true);
                        Goo(1, false, false);
                    }

                    void Goo(int a, string b, bool c) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = new MyDelegate(Goo);
                        Goo(true, "Two");
                        Goo(1, false, false);
                    }

                    void Goo(bool c, string b) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughMethodGroups_PassedAsArgument()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        Target(Goo);
                        Goo(1, "Two", true);
                        Goo(1, false, false);
                    }

                    void Target(MyDelegate d) { }

                    void Goo(int a, string b, bool c) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        Target(Goo);
                        Goo(true, "Two");
                        Goo(1, false, false);
                    }

                    void Target(MyDelegate d) { }

                    void Goo(bool c, string b) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughMethodGroups_ReturnValue()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = Result();
                        Goo(1, "Two", true);
                    }

                    private MyDelegate Result()
                    {
                        return Goo;
                    }

                    void Goo(int a, string b, bool c) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = Result();
                        Goo(true, "Two");
                    }

                    private MyDelegate Result()
                    {
                        return Goo;
                    }

                    void Goo(bool c, string b) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughMethodGroups_YieldReturnValue()
        {
            var markup = """
                using System.Collections.Generic;

                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        Goo(1, "Two", true);
                    }

                    private IEnumerable<MyDelegate> Result()
                    {
                        yield return Goo;
                    }

                    void Goo(int a, string b, bool c) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                using System.Collections.Generic;

                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        Goo(true, "Two");
                    }

                    private IEnumerable<MyDelegate> Result()
                    {
                        yield return Goo;
                    }

                    void Goo(bool c, string b) { }
                    void Goo(int a, object b, bool c) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_ReferencingLambdas_MethodArgument()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M6()
                    {
                        Target((m, n, o) => { var x = n.Length + (o ? 0 : 1); });
                    }

                    void Target(MyDelegate d) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M6()
                    {
                        Target((o, n) => { var x = n.Length + (o ? 0 : 1); });
                    }

                    void Target(MyDelegate d) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_ReferencingLambdas_YieldReturn()
        {
            var markup = """
                using System.Collections.Generic;

                delegate void $$MyDelegate(int x, string y, bool z);
                class C
                {
                    private IEnumerable<MyDelegate> Result3()
                    {
                        yield return (g, h, i) => { var x = h.Length + (i ? 0 : 1); };
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                using System.Collections.Generic;

                delegate void MyDelegate(bool z, string y);
                class C
                {
                    private IEnumerable<MyDelegate> Result3()
                    {
                        yield return (i, h) => { var x = h.Length + (i ? 0 : 1); };
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Recursive()
        {
            var markup = """
                delegate RecursiveDelegate $$RecursiveDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        RecursiveDelegate rd = null;
                        rd(1, "Two", true)(1, "Two", true)(1, "Two", true)(1, "Two", true)(1, "Two", true);
                    }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate RecursiveDelegate RecursiveDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        RecursiveDelegate rd = null;
                        rd(true, "Two")(true, "Two")(true, "Two")(true, "Two")(true, "Two");
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_DocComments()
        {
            var markup = """
                /// <summary>
                /// This is <see cref="MyDelegate"/>, which has these methods:
                ///     <see cref="MyDelegate.MyDelegate(object, IntPtr)"/>
                ///     <see cref="MyDelegate.Invoke(int, string, bool)"/>
                ///     <see cref="MyDelegate.EndInvoke(IAsyncResult)"/>
                ///     <see cref="MyDelegate.BeginInvoke(int, string, bool, AsyncCallback, object)"/>
                /// </summary>
                /// <param name="x">x!</param>
                /// <param name="y">y!</param>
                /// <param name="z">z!</param>
                delegate void $$MyDelegate(int x, string y, bool z);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = Goo;
                        Goo(1, "Two", true);
                    }

                    /// <param name="a"></param>
                    /// <param name="b"></param>
                    /// <param name="c"></param>
                    void Goo(int a, string b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                /// <summary>
                /// This is <see cref="MyDelegate"/>, which has these methods:
                ///     <see cref="MyDelegate.MyDelegate(object, IntPtr)"/>
                ///     <see cref="MyDelegate.Invoke(bool, string)"/>
                ///     <see cref="MyDelegate.EndInvoke(IAsyncResult)"/>
                ///     <see cref="MyDelegate.BeginInvoke(int, string, bool, AsyncCallback, object)"/>
                /// </summary>
                /// <param name="z">z!</param>
                /// <param name="y">y!</param>
                /// 
                delegate void MyDelegate(bool z, string y);

                class C
                {
                    void M()
                    {
                        MyDelegate d1 = Goo;
                        Goo(true, "Two");
                    }

                    /// <param name="c"></param>
                    /// <param name="b"></param>
                    /// 
                    void Goo(bool c, string b) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_CascadeThroughEventAdd()
        {
            var markup = """
                delegate void $$MyDelegate(int x, string y, bool z);

                class Program
                {
                    void M()
                    {
                        MyEvent += Program_MyEvent;
                    }

                    event MyDelegate MyEvent;
                    void Program_MyEvent(int a, string b, bool c) { }
                }
                """;
            var updatedSignature = new[] { 2, 1 };
            var expectedUpdatedCode = """
                delegate void MyDelegate(bool z, string y);

                class Program
                {
                    void M()
                    {
                        MyEvent += Program_MyEvent;
                    }

                    event MyDelegate MyEvent;
                    void Program_MyEvent(bool c, string b) { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Generics1()
        {
            var markup = """
                public class DP16a
                {
                    public delegate void D<T>($$T t);
                    public event D<int> E1;
                    public event D<int> E2;

                    public void M1(int i) { }
                    public void M2(int i) { }
                    public void M3(int i) { }

                    void B()
                    {
                        D<int> d = new D<int>(M1);
                        E1 += new D<int>(M2);
                        E2 -= new D<int>(M3);
                    }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                public class DP16a
                {
                    public delegate void D<T>();
                    public event D<int> E1;
                    public event D<int> E2;

                    public void M1() { }
                    public void M2() { }
                    public void M3() { }

                    void B()
                    {
                        D<int> d = new D<int>(M1);
                        E1 += new D<int>(M2);
                        E2 -= new D<int>(M3);
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Generics2()
        {
            var markup = """
                public class D17<T>
                {
                    public delegate void $$D(T t);
                }
                public class D17Test
                {
                    void Test() { var x = new D17<string>.D(M17); }
                    internal void M17(string s) { }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                public class D17<T>
                {
                    public delegate void D();
                }
                public class D17Test
                {
                    void Test() { var x = new D17<string>.D(M17); }
                    internal void M17() { }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_GenericParams()
        {
            var markup = """
                class DA
                {
                    void M(params int[] i) { }
                    void B()
                    {
                        DP20<int>.D d = new DP20<int>.D(M);
                        d();
                        d(0);
                        d(0, 1);
                    }
                }
                public class DP20<T>
                {
                    public delegate void $$D(params T[] t);
                    public void M1(params T[] t) { }

                    void B()
                    {
                        D d = new D(M1);
                    }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                class DA
                {
                    void M() { }
                    void B()
                    {
                        DP20<int>.D d = new DP20<int>.D(M);
                        d();
                        d();
                        d();
                    }
                }
                public class DP20<T>
                {
                    public delegate void D();
                    public void M1() { }

                    void B()
                    {
                        D d = new D(M1);
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }

        [Fact]
        public async Task ChangeSignature_Delegates_Generic_RemoveArgumentAtReference()
        {
            var markup = """
                public class CD<T>
                {
                    public delegate void D(T t);
                }
                class Test
                {
                    public void M()
                    {
                        var dele = new CD<int>.$$D((int x) => { });
                    }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                public class CD<T>
                {
                    public delegate void D();
                }
                class Test
                {
                    public void M()
                    {
                        var dele = new CD<int>.D(() => { });
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature,
                expectedUpdatedInvocationDocumentCode: expectedUpdatedCode, expectedSelectedIndex: 0);
        }

        [Fact]
        public async Task ChangeSignature_Delegate_Generics_RemoveStaticArgument()
        {
            var markup = """
                public class C2<T>
                {
                    public delegate void D(T t);
                }

                public class D2
                {
                    public static D2 Instance = null;
                    void M(D2 m) { }

                    void B()
                    {
                        C2<D2>.D d = new C2<D2>.D(M);
                        $$d(D2.Instance);
                    }
                }
                """;
            var updatedSignature = Array.Empty<int>();
            var expectedUpdatedCode = """
                public class C2<T>
                {
                    public delegate void D();
                }

                public class D2
                {
                    public static D2 Instance = null;
                    void M() { }

                    void B()
                    {
                        C2<D2>.D d = new C2<D2>.D(M);
                        d();
                    }
                }
                """;
            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: expectedUpdatedCode);
        }
    }
}
