// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.Completion)]
    public class ExtensionMethodImportCompletionProviderTests : AbstractCSharpCompletionProviderTests
    {
        public ExtensionMethodImportCompletionProviderTests()
        {
            ShowImportCompletionItemsOptionValue = true;
            ForceExpandedCompletionIndexCreation = true;
        }

        internal override Type GetCompletionProviderType()
            => typeof(ExtensionMethodImportCompletionProvider);

        public enum ReferenceType
        {
            None,
            Project,
            Metadata
        }

        private static IEnumerable<object[]> CombineWithReferenceTypeData(IEnumerable<List<object>> data)
        {
            foreach (var refKind in Enum.GetValues(typeof(ReferenceType)))
            {
                foreach (var d in data)
                {
                    d.Add(refKind);
                    yield return d.ToArray();
                }
            }
        }

        public static IEnumerable<object[]> ReferenceTypeData
            => (new[] { ReferenceType.None, ReferenceType.Project, ReferenceType.Metadata }).Select(refType => new[] { (object)refType });

        public static IEnumerable<object[]> AllTypeKindsWithReferenceTypeData
            => CombineWithReferenceTypeData((new[] { "class", "struct", "interface", "enum", "abstract class" }).Select(kind => new List<object>() { kind }));

        private static IEnumerable<List<object>> BuiltInTypes
        {
            get
            {
                var predefinedTypes = new List<string>() { "string", "String", "System.String" };
                var arraySuffixes = new[] { "", "[]", "[,]" };

                foreach (var type1 in predefinedTypes)
                {
                    foreach (var type2 in predefinedTypes)
                    {
                        foreach (var suffix in arraySuffixes)
                        {
                            yield return new List<object>() { type1 + suffix, type2 + suffix };
                        }
                    }
                }
            }
        }

        private static string GetMarkup(string current, string referenced, ReferenceType refType,
                                        string currentLanguage = LanguageNames.CSharp,
                                        string referencedLanguage = LanguageNames.CSharp)
            => refType switch
            {
                ReferenceType.None => CreateMarkupForSingleProject(current, referenced, currentLanguage),
                ReferenceType.Project => GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, true),
                ReferenceType.Metadata => GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, false),
                _ => null,
            };

        public static IEnumerable<object[]> BuiltInTypesWithReferenceTypeData
            => CombineWithReferenceTypeData(BuiltInTypes);

        [MemberData(nameof(BuiltInTypesWithReferenceTypeData))]
        [Theory]
        public async Task TestPredefinedType(string type1, string type2, ReferenceType refType)
        {
            var file1 = $@"
using System;

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this {type1} x)
            => true;
    }}
}}";
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M({type2} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration(ReferenceType refType)
        {
            var file1 = """
                using System;
                using MyInt = System.Int32;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this MyInt x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration_PrimitiveType(ReferenceType refType)
        {
            var file1 = """
                using System;
                using MyInt = System.Int32;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this MyInt x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration_RegularType(ReferenceType refType)
        {
            var file1 = """
                using System;
                using MyAlias = System.Exception;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this MyAlias x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(Exception x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration_GenericType(ReferenceType refType)
        {
            var file1 = """
                using System;
                using MyAlias = System.Collections.Generic.List<int>;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this MyAlias x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(System.Collections.Generic.List<int> x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration_RegularTypeWithSameSimpleName(ReferenceType refType)
        {
            var file1 = """
                using DataTime = System.Exception;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this System.DateTime x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(DateTime x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInDeclaration_Namespace(ReferenceType refType)
        {
            var file1 = """
                using System;
                using GenericCollection = System.Collections.Generic;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod<T>(this GenericCollection.List<T> x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(System.Collections.Generic.List<int> x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 displayTextSuffix: "<>",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UsingAliasInUsage(ReferenceType refType)
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;
                using MyInt = System.Int32;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(MyInt x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(AllTypeKindsWithReferenceTypeData))]
        [Theory]
        public async Task RegularType(string typeKind, ReferenceType refType)
        {
            var file1 = $@"
using System;

public {typeKind} MyType {{ }}

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this MyType t)
            => true;
    }}
}}";
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(MyType x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(AllTypeKindsWithReferenceTypeData))]
        [Theory]
        public async Task ObjectType(string typeKind, ReferenceType refType)
        {
            var file1 = $@"
using System;

public {typeKind} MyType {{ }}

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this object t)
            => true;
    }}
}}";
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(MyType x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        public static IEnumerable<object[]> TupleWithRefTypeData => CombineWithReferenceTypeData(
            (new[]
            {
                "(int, int)",
                "(int, (int, int))",
                "(string a, string b)"
            }).Select(tuple => new List<object>() { tuple }));

        [MemberData(nameof(TupleWithRefTypeData))]
        [Theory]
        public async Task ValueTupleType(string tupleType, ReferenceType refType)
        {
            var file1 = $@"
using System;

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this {tupleType} t)
            => true;
    }}
}}";
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M({tupleType} x)
        {{
            x.$$
        }}
    }}
}}";
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        public static IEnumerable<object[]> DerivableTypeKindsWithReferenceTypeData
            => CombineWithReferenceTypeData((new[] { "class", "interface", "abstract class" }).Select(kind => new List<object>() { kind }));

        [MemberData(nameof(DerivableTypeKindsWithReferenceTypeData))]
        [Theory]
        public async Task RegularTypeAsBase(string baseType, ReferenceType refType)
        {
            var file1 = $@"
using System;

public {baseType} MyBase {{ }}

public class MyType : MyBase {{ }}

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod(this MyBase t)
            => true;
    }}
}}";
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(MyType x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        public static IEnumerable<object[]> BounedGenericTypeWithRefTypeData => CombineWithReferenceTypeData(
            (new[]
            {
                "IEnumerable<string>",
                "List<string>",
                "string[]"
            }).Select(tuple => new List<object>() { tuple }));

        [MemberData(nameof(BounedGenericTypeWithRefTypeData))]
        [Theory]
        public async Task BoundedGenericType(string type, ReferenceType refType)
        {
            var file1 = """
                using System;
                using System.Collections.Generic;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this IEnumerable<string> t)
                            => true;
                    }
                }
                """;
            var file2 = $@"
using System;
using System.Collections.Generic;

namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }
        public static IEnumerable<object[]> TypeParameterWithRefTypeData => CombineWithReferenceTypeData(
            (new[]
            {
                "IEnumerable<string>",
                "int",
                "Bat",
                "Bat"
            }).Select(tuple => new List<object>() { tuple }));

        [MemberData(nameof(TypeParameterWithRefTypeData))]
        [Theory]
        public async Task MatchingTypeParameter(string type, ReferenceType refType)
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod<T>(this T t)
                            => true;
                    }
                }
                """;
            var file2 = $@"
using System;
using System.Collections.Generic;

namespace Baz
{{
    public interface Bar {{}}

    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";
            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 displayTextSuffix: "<>",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory]
        public async Task TestInternalExtensionMethods_NoIVT_InReference(ReferenceType refType)
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemIsAbsentAsync(
                 markup,
                 "ExtentionMethod",
                 inlineDescription: "Foo");
        }

        [Fact]
        public async Task TestInternalExtensionMethods_NoIVT_InSameProject()
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        internal static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = GetMarkup(file2, file1, ReferenceType.None);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodInternal,     // This is based on declared accessibility
                 inlineDescription: "Foo");
        }

        // SymbolTreeInfo explicitly ignores non-public types from metadata(likely for perf reasons). So we don't need to test internals in PE reference
        [InlineData(ReferenceType.None)]
        [InlineData(ReferenceType.Project)]
        [Theory]
        public async Task TestInternalExtensionMethods_WithIVT(ReferenceType refType)
        {
            var file1 = """
                [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        internal static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = """
                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = GetMarkup(file2, file1, refType);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodInternal,
                 inlineDescription: "Foo");
        }

        [MemberData(nameof(ReferenceTypeData))]
        [Theory]
        public async Task UserDefinedGenericType(ReferenceType refType)
        {
            var file1 = """
                using System;

                public class MyGeneric<T>
                {
                }

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this MyGeneric<int> x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(MyGeneric<int> x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, refType);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData("(1 + 1)")]
        [InlineData("(new int())")]
        [Theory]
        public async Task MethodSymbolReceiver(string expression)
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M()
        {{
            {expression}.$$
        }}
    }}
}}";
            var markup = GetMarkup(file2, file1, ReferenceType.None);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        public static IEnumerable<object[]> VBBuiltInTypes
        {
            get
            {
                var predefinedTypes = new List<(string vbType, string csType)>
                {
                    ( "Boolean", "bool" ),
                    ( "Byte", "byte" ),
                    ( "Char", "char" ),
                    ( "Date", "DateTime" ),
                    ( "Integer", "int" ),
                    ( "String", "string" ),
                    ( "Object", "object" ),
                    ( "Short", "short" ),

                };

                var arraySuffixes = new (string vbSuffix, string csSuffix)[] { ("", ""), ("()", "[]"), ("(,)", "[,]") };

                foreach (var type in predefinedTypes)
                {
                    foreach (var suffix in arraySuffixes)
                    {
                        yield return new object[] { type.vbType + suffix.vbSuffix, type.csType + suffix.csSuffix };
                    }
                }
            }
        }

        [MemberData(nameof(VBBuiltInTypes))]
        [Theory]
        public async Task ExtensionMethodDelcaredInVBSource(string vbType, string csType)
        {
            var file1 = $@"
Imports System
Imports System.Runtime.CompilerServices

Namespace NS
    Public Module Foo
        <Extension>
        public Function ExtentionMethod(x As {vbType}) As Boolean
            Return True
        End Function
    End Module
End Namespace";
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M({csType} x)
        {{
            x.$$
        }}
    }}
}}";
            var markup = GetMarkup(file2, file1, ReferenceType.Project, currentLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.VisualBasic);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "NS");
        }

        [Fact]
        public async Task ExtensionMethodDelcaredInRootNamespaceVBSource()
        {
            var file1 = """
                Imports System
                Imports System.Runtime.CompilerServices

                Public Module Foo
                    <Extension>
                    public Function ExtentionMethod(x As Integer) As Boolean
                        Return True
                    End Function
                End Module
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Root");

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Root");
        }

        [Fact]
        public async Task ExtensionMethodDelcaredInGlobalNamespaceVBSource()
        {
            var file1 = """
                Imports System
                Imports System.Runtime.CompilerServices

                Public Module Foo
                    <Extension>
                    public Function ExtentionMethod(x As Integer) As Boolean
                        Return True
                    End Function
                End Module
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;
            var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp);

            await VerifyImportItemIsAbsentAsync(
                 markup,
                 "ExtentionMethod",
                 inlineDescription: "");
        }

        [Fact]
        public async Task TestTriggerLocation()
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        internal static bool ExtentionMethod(this int x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                            var z = 10;
                        }
                    }
                }
                """;

            var markup = GetMarkup(file2, file1, ReferenceType.None);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod",
                 glyph: (int)Glyph.ExtensionMethodInternal,     // This is based on declared accessibility
                 inlineDescription: "Foo");
        }

        [InlineData("int", "Int32Method", "Foo")]
        [InlineData("string", "StringMethod", "Bar")]
        [Theory]
        public async Task TestIdenticalAliases(string type, string expectedMethodname, string expectedNamespace)
        {
            var file1 = """
                using X = System.String;

                namespace Foo
                {
                    using X = System.Int32;

                    internal static class ExtensionClass
                    {
                        internal static bool Int32Method(this X x)
                            => true;
                    }
                }

                namespace Bar
                {
                    internal static class ExtensionClass
                    {
                        internal static bool StringMethod(this X x)
                            => true;
                    }
                }
                """;
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = GetMarkup(file2, file1, ReferenceType.None);
            await VerifyImportItemExistsAsync(
                 markup,
                 expectedMethodname,
                 glyph: (int)Glyph.ExtensionMethodInternal,
                 inlineDescription: expectedNamespace);
        }

        [InlineData("int")]
        [InlineData("Exception")]
        [Theory]
        public async Task TestIdenticalMethodName(string type)
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtMethod(this int x)
                            => true;

                        public static bool ExtMethod(this Exception x)
                            => true;
                    }
                }
                """;
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M({type} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = GetMarkup(file2, file1, ReferenceType.None);
            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtMethod",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [Fact]
        public async Task DoNotTriggerOnType()
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static class ExtensionClass
                    {
                        public static bool ExtMethod(this string x)
                            => true;
                    }
                }
                """;
            var file2 = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M()
                        {
                            string.$$
                        }
                    }
                }
                """;
            var markup = GetMarkup(file2, file1, ReferenceType.None);
            await VerifyImportItemIsAbsentAsync(
                 markup,
                 "ExtMethod",
                 inlineDescription: "Foo");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
        public async Task TestExtensionMethodInPartialClass()
        {
            var file1 = """
                using System;

                namespace Foo
                {
                    public static partial class ExtensionClass
                    {
                        public static bool ExtentionMethod1(this string x)
                            => true;
                    }
                }
                """;
            var currentFile = """
                using System;

                namespace Foo
                {
                    public static partial class ExtensionClass
                    {
                        public static bool ExtentionMethod2(this string x)
                            => true;
                    }
                }

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(string x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = CreateMarkupForSingleProject(currentFile, file1, LanguageNames.CSharp);

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod1",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod2",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData(ReferenceType.Project, "public")]
        [InlineData(ReferenceType.Project, "internal")]
        [InlineData(ReferenceType.Metadata, "public")]  // We don't support internal extension method from non-source references.
        [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
        public async Task TestExtensionMethodsInConflictingTypes(ReferenceType refType, string accessibility)
        {
            var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]

namespace Foo
{{
    {accessibility} static class ExtensionClass
    {{
        public static bool ExtentionMethod1(this int x)
            => true;
    }}
}}";
            var srcDoc = """
                using System;

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        public static bool ExtentionMethod2(this int x)
                            => true;
                    }
                }

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod1",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod2",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42325")]
        public async Task TestExtensionMethodsInConflictingTypesFromReferencedProjects()
        {
            var refDoc1 = """
                [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        public static bool ExtentionMethod1(this int x)
                            => true;
                    }
                }
                """;
            var refDoc2 = """
                [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Project1")]

                namespace Foo
                {
                    internal static class ExtensionClass
                    {
                        public static bool ExtentionMethod2(this int x)
                            => true;
                    }
                }
                """;
            var srcDoc = """
                using System;

                namespace Baz
                {
                    public class Bat
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = CreateMarkupForProjectWithMultupleProjectReferences(srcDoc, LanguageNames.CSharp, LanguageNames.CSharp, new[] { refDoc1, refDoc2 });

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod1",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");

            await VerifyImportItemExistsAsync(
                 markup,
                 "ExtentionMethod2",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData("", "", false)]
        [InlineData("", "public", true)]
        [InlineData("public", "", false)]
        [Theory]
        public async Task TestCSharpDefaultAccessibility(string containerAccessibility, string methodAccessibility, bool isAvailable)
        {
            var file1 = $@"
using System;

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]

namespace Foo
{{
    {containerAccessibility} static class ExtensionClass
    {{
        {methodAccessibility} static bool ExtentionMethod(this int x)
            => true;
    }}
}}";
            var file2 = $@"
using System;

namespace Baz
{{
    public class Bat
    {{
        public void M(int x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference: true);

            if (isAvailable)
            {
                await VerifyImportItemExistsAsync(
                     markup,
                     "ExtentionMethod",
                     glyph: (int)Glyph.ExtensionMethodPublic,
                     inlineDescription: "Foo");
            }
            else
            {
                await VerifyImportItemIsAbsentAsync(
                     markup,
                     "ExtentionMethod",
                     inlineDescription: "Foo");
            }
        }

        [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")]
        [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")]
        [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")]
        [InlineData(ReferenceType.Project, "[][,]", "ExtentionMethod5")]
        [InlineData(ReferenceType.Metadata, "[]", "ExtentionMethod2")]
        [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")]
        [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")]
        [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")]
        [Theory]
        public async Task TestExtensionMethodsForSimpleArrayType(ReferenceType refType, string rank, string expectedName)
        {
            var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod1(this int x)
            => true;

        public static bool ExtentionMethod2(this int[] x)
            => true;

        public static bool ExtentionMethod3(this int[][] x)
            => true;

        public static bool ExtentionMethod4(this int[,] x)
            => true;

        public static bool ExtentionMethod5(this int[][,] x)
            => true;
    }}
}}";
            var srcDoc = $@"
namespace Baz
{{
    public class Bat
    {{
        public void M(int{rank} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                 markup,
                 expectedName,
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData(ReferenceType.Project, "[]", "ExtentionMethod2")]
        [InlineData(ReferenceType.Project, "[][]", "ExtentionMethod3")]
        [InlineData(ReferenceType.Project, "[,]", "ExtentionMethod4")]
        [InlineData(ReferenceType.Project, "[][,]", "ExtentionMethod5")]
        [InlineData(ReferenceType.Metadata, "[]", "ExtentionMethod2")]
        [InlineData(ReferenceType.Metadata, "[][]", "ExtentionMethod3")]
        [InlineData(ReferenceType.Metadata, "[,]", "ExtentionMethod4")]
        [InlineData(ReferenceType.Metadata, "[][,]", "ExtentionMethod5")]
        [Theory]
        public async Task TestExtensionMethodsForGenericArrayType(ReferenceType refType, string rank, string expectedName)
        {
            var refDoc = $@"
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""Project1"")]

namespace Foo
{{
    public static class ExtensionClass
    {{
        public static bool ExtentionMethod1<T>(this T x)
            => true;

        public static bool ExtentionMethod2<T>(this T[] x)
            => true;

        public static bool ExtentionMethod3<T>(this T[][] x)
            => true;

        public static bool ExtentionMethod4<T>(this T[,] x)
            => true;

        public static bool ExtentionMethod5<T>(this T[][,] x)
            => true;
    }}
}}";
            var srcDoc = $@"
namespace Baz
{{
    public class Bat
    {{
        public void M(int{rank} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                 markup,
                 expectedName,
                 displayTextSuffix: "<>",
                 glyph: (int)Glyph.ExtensionMethodPublic,
                 inlineDescription: "Foo");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory]
        public async Task TestGenericReceiverTypeWithConstraint(ReferenceType refType)
        {
            var refDoc = """
                using System;

                namespace NS1
                {
                    public class C1 {}
                }

                namespace NS2
                {
                    public static class Extensions
                    {
                        public static bool ExtentionMethod(this NS1.C1 c) => false;
                    }
                }
                """;
            var srcDoc = """
                namespace NS1
                {
                    public class C2
                    {
                        public void M<T>(T x) where T : C1
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2");
        }

        [InlineData(ReferenceType.Project, "(int,int)")]
        [InlineData(ReferenceType.Project, "(int,int,int,int,int,int,int,int,int,int)")]    // more than 8 tuple elements
        [InlineData(ReferenceType.Metadata, "(int,int)")]
        [InlineData(ReferenceType.Metadata, "(int,int,int,int,int,int,int,int,int,int)")]   // more than 8 tuple elements
        [Theory]
        public async Task TestTupleArray(ReferenceType refType, string tupleType)
        {
            var refDoc = $@"
using System;

namespace NS2
{{
    public static class Extensions
    {{
        public static bool ExtentionMethod(this {tupleType}[] x) => false;
    }}
}}";
            var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M({tupleType}[] x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2");
        }

        [InlineData(ReferenceType.Project, "(int[],int[])")]
        [InlineData(ReferenceType.Project, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements
        [InlineData(ReferenceType.Metadata, "(int[],int[])")]
        [InlineData(ReferenceType.Metadata, "(int[],int[],int[],int[],int[],int[],int[],int[],int[],int[])")] // more than 8 tuple elements
        [Theory]
        public async Task TestArrayTuple(ReferenceType refType, string tupleType)
        {
            var refDoc = $@"
using System;

namespace NS2
{{
    public static class Extensions
    {{
        public static bool ExtentionMethod(this {tupleType} x) => false;
    }}
}}";
            var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M({tupleType} x)
        {{
            x.$$
        }}
    }}
}}";

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory]
        public async Task TestDescriptionOfGenericReceiverType(ReferenceType refType)
        {
            var refDoc = """
                using System;

                namespace NS2
                {
                    public static class Extensions
                    {
                        public static bool ExtentionMethod<T>(this T t) => false;
                    }
                }
                """;
            var srcDoc = """
                namespace NS1
                {
                    public class C
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                displayTextSuffix: "<>",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2",
                expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod<int>()");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory]
        public async Task TestDescriptionOfOverloads(ReferenceType refType)
        {
            var refDoc = """
                using System;

                namespace NS2
                {
                    public static class Extensions
                    {
                        public static bool ExtentionMethod(this int t) => false;
                        public static bool ExtentionMethod(this int t, int a) => false;
                        public static bool ExtentionMethod(this int t, int a, int b) => false;
                        public static bool ExtentionMethod<T>(this int t, T a) => false;
                        public static bool ExtentionMethod<T>(this int t, T a, T b) => false;
                        public static bool ExtentionMethod<T1, T2>(this int t, T1 a, T2 b) => false;
                    }
                }
                """;
            var srcDoc = """
                namespace NS1
                {
                    public class C
                    {
                        public void M(int x)
                        {
                            x.$$
                        }
                    }
                }
                """;

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2",
                expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod() (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})");

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                displayTextSuffix: "<>",
                glyph: (int)Glyph.ExtensionMethodPublic,
                inlineDescription: "NS2",
                expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod<T>(T a) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.generic_overloads})");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
        public async Task TestBrowsableAlways(ReferenceType refType)
        {
            var srcDoc = """
                class Program
                {
                    void M()
                    {
                        new Goo().$$
                    }
                }
                """;

            var refDoc = """
                public class Goo
                {
                }

                namespace Foo
                {
                    public static class GooExtensions
                    {
                        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Always)]
                        public static void Bar(this Goo goo, int x)
                        {
                        }
                    }
                }
                """;

            var markup = refType switch
            {
                ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp),
                _ => null,
            };

            await VerifyImportItemExistsAsync(
                    markup,
                    "Bar",
                    glyph: (int)Glyph.ExtensionMethodPublic,
                    inlineDescription: "Foo");
        }

        [InlineData(ReferenceType.Project)]
        [InlineData(ReferenceType.Metadata)]
        [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
        public async Task TestBrowsableNever(ReferenceType refType)
        {
            var srcDoc = """
                class Program
                {
                    void M()
                    {
                        new Goo().$$
                    }
                }
                """;

            var refDoc = """
                public class Goo
                {
                }

                namespace Foo
                {
                    public static class GooExtensions
                    {
                        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
                        public static void Bar(this Goo goo, int x)
                        {
                        }
                    }
                }
                """;

            var (markup, shouldContainItem) = refType switch
            {
                ReferenceType.Project => (CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
                ReferenceType.Metadata => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), false),
                _ => throw ExceptionUtilities.Unreachable(),
            };

            if (shouldContainItem)
            {
                await VerifyImportItemExistsAsync(
                        markup,
                        "Bar",
                        glyph: (int)Glyph.ExtensionMethodPublic,
                        inlineDescription: "Foo");
            }
            else
            {
                await VerifyImportItemIsAbsentAsync(
                        markup,
                        "Bar",
                        inlineDescription: "Foo");
            }
        }

        [InlineData(ReferenceType.Project, true)]
        [InlineData(ReferenceType.Project, false)]
        [InlineData(ReferenceType.Metadata, true)]
        [InlineData(ReferenceType.Metadata, false)]
        [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/47551")]
        public async Task TestBrowsableAdvanced(ReferenceType refType, bool hideAdvanced)
        {
            HideAdvancedMembers = hideAdvanced;

            var srcDoc = """
                class Program
                {
                    void M()
                    {
                        new Goo().$$
                    }
                }
                """;

            var refDoc = """
                public class Goo
                {
                }

                namespace Foo
                {
                    public static class GooExtensions
                    {
                        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
                        public static void Bar(this Goo goo, int x)
                        {
                        }
                    }
                }
                """;

            var (markup, shouldContainItem) = (refType, hideAdvanced) switch
            {
                (ReferenceType.Project, _) => (CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
                (ReferenceType.Metadata, true) => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), false),
                (ReferenceType.Metadata, false) => (CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), true),
                _ => throw ExceptionUtilities.Unreachable(),
            };

            if (shouldContainItem)
            {
                await VerifyImportItemExistsAsync(
                        markup,
                        "Bar",
                        glyph: (int)Glyph.ExtensionMethodPublic,
                        inlineDescription: "Foo");
            }
            else
            {
                await VerifyImportItemIsAbsentAsync(
                        markup,
                        "Bar",
                        inlineDescription: "Foo");
            }
        }

        [Theory]
        [InlineData('.')]
        [InlineData(';')]
        public async Task TestCommitWithCustomizedCharForMethod(char commitChar)
        {
            var markup = """
                public class C
                {
                }
                namespace AA
                {
                    public static class Ext
                    {
                        public static int ToInt(this C c)
                            => 1;
                    }
                }

                namespace BB
                {
                    public class B
                    {
                        public void M()
                        {
                            var c = new C();
                            c.$$
                        }
                    }
                }
                """;

            var expected = $$"""
            using AA;

            public class C
            {
            }
            namespace AA
            {
                public static class Ext
                {
                    public static int ToInt(this C c)
                        => 1;
                }
            }

            namespace BB
            {
                public class B
                {
                    public void M()
                    {
                        var c = new C();
                        c.ToInt(){{commitChar}}
                    }
                }
            }
            """;
            await VerifyProviderCommitAsync(markup, "ToInt", expected, commitChar: commitChar, sourceCodeKind: SourceCodeKind.Regular);
        }

        [InlineData("int", true, "int a")]
        [InlineData("int[]", true, "int a, int b")]
        [InlineData("bool", false, null)]
        [Theory]
        public async Task TestTargetTypedCompletion(string targetType, bool matchTargetType, string expectedParameterList)
        {
            var refDoc = """
                using System;

                namespace NS2
                {
                    public static class Extensions
                    {
                        public static int ExtentionMethod(this int t, int a) => 0;
                        public static int[] ExtentionMethod(this int t, int a, int b) => null;
                        public static string ExtentionMethod(this int t, int a, int b, int c) => false;
                    }
                }
                """;
            var srcDoc = $@"
namespace NS1
{{
    public class C
    {{
        public void M(int x)
        {{
            {targetType} y = x.$$
        }}
    }}
}}";

            ShowTargetTypedCompletionFilter = true;
            var markup = CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp);

            string expectedDescription = null;
            var expectedFilters = new List<CompletionFilter>()
            {
                FilterSet.ExtensionMethodFilter
            };

            if (matchTargetType)
            {
                expectedFilters.Add(FilterSet.TargetTypedFilter);
                expectedDescription = $"({CSharpFeaturesResources.extension}) {targetType} int.ExtentionMethod({expectedParameterList}) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})";
            }

            await VerifyImportItemExistsAsync(
                markup,
                "ExtentionMethod",
                expectedFilters: expectedFilters,
                inlineDescription: "NS2",
                expectedDescriptionOrNull: expectedDescription);
        }

        private Task VerifyImportItemExistsAsync(string markup, string expectedItem, string inlineDescription, int? glyph = null, string displayTextSuffix = null, string expectedDescriptionOrNull = null, List<CompletionFilter> expectedFilters = null)
            => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, isComplexTextEdit: true, matchingFilters: expectedFilters);

        private Task VerifyImportItemIsAbsentAsync(string markup, string expectedItem, string inlineDescription, string displayTextSuffix = null)
            => VerifyItemIsAbsentAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription);
    }
}
