// 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.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders
{
    [Trait(Traits.Feature, Traits.Features.Completion)]
    public class PartialMethodCompletionProviderTests : AbstractCSharpCompletionProviderTests
    {
        internal override Type GetCompletionProviderType()
            => typeof(PartialMethodCompletionProvider);

        [Fact]
        public async Task NoPartialMethods1()
        {
            var text = """
                class c
                {
                    $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task NoPartialMethods2()
        {
            var text = """
                class c
                {
                    private void goo() { };

                    partial void $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task NoExtendedPartialMethods2()
        {
            var text = """
                class c
                {
                    public void goo() { };

                    public void $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task PartialMethodInPartialClass()
        {
            var text = """
                partial class c
                {
                    partial void goo();

                    partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task ExtendedPartialMethodInPartialClass()
        {
            var text = """
                partial class c
                {
                    public partial void goo();

                    public partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task PartialMethodInPartialGenericClass()
        {
            var text = """
                partial class c<T>
                {
                    partial void goo(T bar);

                    partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo(T bar)");
        }

        [Fact]
        public async Task ExtendedPartialMethodInPartialGenericClass()
        {
            var text = """
                partial class c<T>
                {
                    public partial void goo(T bar);

                    public partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo(T bar)");
        }

        [Fact]
        public async Task PartialMethodInPartialStruct()
        {
            var text = """
                partial struct c
                {
                    partial void goo();

                    partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task ExtendedPartialMethodInPartialStruct()
        {
            var text = """
                partial struct c
                {
                    public partial void goo();

                    public partial void $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task CompletionOnPartial1()
        {
            var text = """
                partial class c
                {
                    partial void goo();

                    partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task CompletionOnExtendedPartial1()
        {
            var text = """
                partial class c
                {
                    public partial void goo();

                    partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task CompletionOnPartial2()
        {
            var text = """
                partial class c
                {
                    partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task CompletionOnExtendedPartial2()
        {
            var text = """
                partial class c
                {
                    public partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task StaticUnsafePartial()
        {
            var text = """
                partial class c
                {
                    partial static unsafe void goo();

                    void static unsafe partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task StaticUnsafeExtendedPartial()
        {
            var text = """
                partial class c
                {
                    public partial static unsafe void goo();

                    void static unsafe partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task PartialCompletionWithPrivate()
        {
            var text = """
                partial class c
                {
                    partial static unsafe void goo();

                    private partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task PartialCompletionWithPublic()
        {
            var text = """
                partial class c
                {
                    public partial static unsafe void goo();

                    public partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task NotCompletionDespiteValidModifier()
        {
            var text = """
                partial class c
                {
                    partial void goo();

                    void partial unsafe $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task NoExtendedCompletionDespiteValidModifier()
        {
            var text = """
                partial class c
                {
                    public partial void goo();

                    void partial unsafe $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task YesIfPublic()
        {
            var text = """
                partial class c
                {
                    public partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfInternal()
        {
            var text = """
                partial class c
                {
                    internal partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfProtected()
        {
            var text = """
                partial class c
                {
                    protected partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfProtectedInternal()
        {
            var text = """
                partial class c
                {
                    protected internal partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfExtern()
        {
            var text = """
                partial class c
                {
                    partial void goo();

                    extern void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfVirtual()
        {
            var text = """
                partial class c
                {
                    virtual partial void goo();

                    void partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task YesIfNonVoidReturnType()
        {
            var text = """
                partial class c
                {
                    partial int goo();

                    partial $$
                }
                """;
            await VerifyItemExistsAsync(text, "goo()");
        }

        [Fact]
        public async Task NotInsideInterface()
        {
            var text = """
                partial interface i
                {
                    partial void goo();

                    partial $$
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [WpfFact]
        public async Task CommitInPartialClass()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    partial void goo();

                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    partial void goo();

                    partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitInExtendedPartialClass()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    public partial void goo();

                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    public partial void goo();

                    public partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitGenericPartialMethod()
        {
            var markupBeforeCommit = """
                partial class c<T>
                {
                    partial void goo(T bar);

                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c<T>
                {
                    partial void goo(T bar);

                    partial void goo(T bar)
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(T bar)", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitGenericExtendedPartialMethod()
        {
            var markupBeforeCommit = """
                partial class c<T>
                {
                    public partial void goo(T bar);

                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c<T>
                {
                    public partial void goo(T bar);

                    public partial void goo(T bar)
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo(T bar)", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitMethodErasesPrivate()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    partial void goo();

                    private partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    partial void goo();

                    partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitMethodKeepsExtendedPrivate()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    private partial void goo();

                    private partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    private partial void goo();

                    private partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitInPartialClassPart()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    partial void goo();
                }

                partial class c
                {
                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    partial void goo();
                }

                partial class c
                {
                    partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitInExtendedPartialClassPart()
        {
            var markupBeforeCommit = """
                partial class c
                {
                    public partial void goo();
                }

                partial class c
                {
                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial class c
                {
                    public partial void goo();
                }

                partial class c
                {
                    public partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [WpfFact]
        public async Task CommitInPartialStruct()
        {
            var markupBeforeCommit = """
                partial struct c
                {
                    partial void goo();

                    partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                partial struct c
                {
                    partial void goo();

                    partial void goo()
                    {
                        throw new System.NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "goo()", expectedCodeAfterCommit);
        }

        [Fact]
        public async Task NotIfNoPartialKeyword()
        {
            var text = """
                partial class C
                    {
                        partial void Goo();
                    }

                    partial class C
                    {
                        void $$
                    }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact]
        public async Task NotIfNoExtendedPartialKeyword()
        {
            var text = """
                partial class C
                    {
                        public partial void Goo();
                    }

                    partial class C
                    {
                        void $$
                    }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578757")]
        public async Task DoNotConsiderFollowingDeclarationPartial()
        {
            var text = """
                class Program
                {
                    partial $$

                    void Goo()
                    {

                    }
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578757")]
        public async Task DoNotConsiderFollowingDeclarationExtendedPartial()
        {
            var text = """
                class Program
                {
                    public partial $$

                    void Goo()
                    {

                    }
                }
                """;
            await VerifyNoItemsExistAsync(text);
        }

        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578078")]
        [WpfFact]
        public async Task CommitAsync()
        {
            var markupBeforeCommit = """
                using System;

                partial class Bar
                {
                    partial void Goo();

                    async partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                using System;

                partial class Bar
                {
                    partial void Goo();

                    async partial void Goo()
                    {
                        throw new NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo()", expectedCodeAfterCommit);
        }

        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578078")]
        [WpfFact]
        public async Task CommitAsyncExtended()
        {
            var markupBeforeCommit = """
                using System;

                partial class Bar
                {
                    public partial void Goo();

                    async partial $$
                }
                """;

            var expectedCodeAfterCommit = """
                using System;

                partial class Bar
                {
                    public partial void Goo();

                    public async partial void Goo()
                    {
                        throw new NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo()", expectedCodeAfterCommit);
        }

        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578078")]
        [WpfFact]
        public async Task AmbiguityCommittingWithParen()
        {
            var markupBeforeCommit = """
                using System;

                partial class Bar
                {
                    partial void Goo();

                    partial Goo$$
                }
                """;

            var expectedCodeAfterCommit = """
                using System;

                partial class Bar
                {
                    partial void Goo();

                    partial void Goo()
                    {
                        throw new NotImplementedException();$$
                    }
                }
                """;

            await VerifyCustomCommitProviderAsync(markupBeforeCommit, "Goo()", expectedCodeAfterCommit, commitChar: '(');
        }

        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/965677")]
        [WpfFact]
        public async Task NoDefaultParameterValues()
        {
            var text = """
                namespace PartialClass
                {
                    partial class PClass
                    {
                        partial void PMethod(int i = 0);

                        partial $$
                    }
                }
                """;

            var expected = """
                namespace PartialClass
                {
                    partial class PClass
                    {
                        partial void PMethod(int i = 0);

                        partial void PMethod(int i)
                        {
                            throw new System.NotImplementedException();$$
                        }
                    }
                }
                """;
            await VerifyCustomCommitProviderAsync(text, "PMethod(int i)", expected);
        }

        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/965677")]
        [WpfFact]
        public async Task NoDefaultParameterValuesExtended()
        {
            var text = """
                namespace PartialClass
                {
                    partial class PClass
                    {
                        public partial void PMethod(int i = 0);

                        partial $$
                    }
                }
                """;

            var expected = """
                namespace PartialClass
                {
                    partial class PClass
                    {
                        public partial void PMethod(int i = 0);

                        public partial void PMethod(int i)
                        {
                            throw new System.NotImplementedException();$$
                        }
                    }
                }
                """;
            await VerifyCustomCommitProviderAsync(text, "PMethod(int i)", expected);
        }

        [WorkItem("https://github.com/dotnet/roslyn/issues/26388")]
        [WpfFact]
        public async Task ExpressionBodyMethod()
        {
            using var workspaceFixture = GetOrCreateWorkspaceFixture();

            var workspace = workspaceFixture.Target.GetWorkspace(GetComposition());
            workspace.GlobalOptions.SetGlobalOption(
                CSharpCodeStyleOptions.PreferExpressionBodiedMethods,
                new CodeStyleOption2<ExpressionBodyPreference>(ExpressionBodyPreference.WhenPossible, NotificationOption2.Silent));

            var text = """
                using System;
                partial class Bar
                {
                    partial void Foo();
                    partial $$
                }
                """
;

            var expected = """
                using System;
                partial class Bar
                {
                    partial void Foo();
                    partial void Foo() => throw new NotImplementedException();$$
                }
                """
;

            await VerifyCustomCommitProviderAsync(text, "Foo()", expected);
        }

        [WpfFact]
        public async Task ExpressionBodyMethodExtended()
        {
            using var workspaceFixture = GetOrCreateWorkspaceFixture();

            var workspace = workspaceFixture.Target.GetWorkspace(GetComposition());
            workspace.GlobalOptions.SetGlobalOption(
                CSharpCodeStyleOptions.PreferExpressionBodiedMethods,
                new CodeStyleOption2<ExpressionBodyPreference>(ExpressionBodyPreference.WhenPossible, NotificationOption2.Silent));

            var text = """
                using System;
                partial class Bar
                {
                    public partial void Foo();
                    partial $$
                }
                """
;

            var expected = """
                using System;
                partial class Bar
                {
                    public partial void Foo();
                    public partial void Foo() => throw new NotImplementedException();$$
                }
                """
;

            await VerifyCustomCommitProviderAsync(text, "Foo()", expected);
        }

        private Task VerifyItemExistsAsync(string markup, string expectedItem)
        {
            return VerifyItemExistsAsync(markup, expectedItem, isComplexTextEdit: true);
        }
    }
}
