// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations;

[Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
public sealed class VarKeywordRecommenderTests : RecommenderTests
{
    protected override string KeywordText => "var";

    private readonly VarKeywordRecommender _recommender = new();

    public VarKeywordRecommenderTests()
    {
        this.RecommendKeywordsAsync = async (position, context) => _recommender.RecommendKeywords(position, context, CancellationToken.None);
    }

    [Fact]
    public Task TestAtRoot_Interactive()
        => VerifyKeywordAsync(SourceCodeKind.Script,
@"$$");

    [Fact]
    public Task TestAfterClass_Interactive()
        => VerifyKeywordAsync(SourceCodeKind.Script,
            """
            class C { }
            $$
            """);

    [Fact]
    public Task TestAfterGlobalStatement()
        => VerifyKeywordAsync(
            """
            System.Console.WriteLine();
            $$
            """);

    [Fact]
    public Task TestAfterGlobalVariableDeclaration_Interactive()
        => VerifyKeywordAsync(SourceCodeKind.Script,
            """
            int i = 0;
            $$
            """);

    [Fact]
    public Task TestNotInUsingAlias()
        => VerifyAbsenceAsync(
@"using Goo = $$");

    [Fact]
    public Task TestNotInGlobalUsingAlias()
        => VerifyAbsenceAsync(
@"global using Goo = $$");

    [Fact]
    public Task TestNotAfterStackAlloc()
        => VerifyAbsenceAsync(
            """
            class C {
                 int* goo = stackalloc $$
            """);

    [Fact]
    public Task TestNotInFixedStatement()
        => VerifyAbsenceAsync(AddInsideMethod(
@"fixed ($$"));

    [Fact]
    public Task TestNotInDelegateReturnType()
        => VerifyAbsenceAsync(
@"public delegate $$");

    [Fact]
    public Task TestInCastType()
        => VerifyKeywordAsync(AddInsideMethod(
@"var str = (($$"));

    [Fact]
    public Task TestInCastType2()
        => VerifyKeywordAsync(AddInsideMethod(
@"var str = (($$)items) as string;"));

    [Fact]
    public Task TestEmptyStatement()
        => VerifyKeywordAsync(AddInsideMethod(
@"$$"));

    [Fact]
    public Task TestBeforeStatement()
        => VerifyKeywordAsync(AddInsideMethod(
            """
            $$
            return true;
            """));

    [Fact]
    public Task TestAfterStatement()
        => VerifyKeywordAsync(AddInsideMethod(
            """
            return true;
            $$
            """));

    [Fact]
    public Task TestAfterBlock()
        => VerifyKeywordAsync(AddInsideMethod(
            """
            if (true) {
            }
            $$
            """));

    [Fact]
    public Task TestNotAfterLock()
        => VerifyAbsenceAsync(AddInsideMethod(
@"lock $$"));

    [Fact]
    public Task TestNotAfterLock2()
        => VerifyAbsenceAsync(AddInsideMethod(
@"lock ($$"));

    [Fact]
    public Task TestNotAfterLock3()
        => VerifyAbsenceAsync(AddInsideMethod(
@"lock (l$$"));

    [Fact]
    public Task TestNotInClass()
        => VerifyAbsenceAsync("""
            class C
            {
              $$
            }
            """);

    [Fact]
    public Task TestInFor()
        => VerifyKeywordAsync(AddInsideMethod(
@"for ($$"));

    [Fact]
    public Task TestNotInFor()
        => VerifyAbsenceAsync(AddInsideMethod(
@"for (var $$"));

    [Fact]
    public Task TestInFor2()
        => VerifyKeywordAsync(AddInsideMethod(
@"for ($$;"));

    [Fact]
    public Task TestInFor3()
        => VerifyKeywordAsync(AddInsideMethod(
@"for ($$;;"));

    [Fact]
    public Task TestNotAfterVar()
        => VerifyAbsenceAsync(AddInsideMethod(
@"var $$"));

    [Fact]
    public Task TestInForEach()
        => VerifyKeywordAsync(AddInsideMethod(
@"foreach ($$"));

    [Fact]
    public Task TestNotInForEach()
        => VerifyAbsenceAsync(AddInsideMethod(
@"foreach (var $$"));

    [Fact]
    public Task TestInAwaitForEach()
        => VerifyKeywordAsync(AddInsideMethod(
@"await foreach ($$"));

    [Fact]
    public Task TestNotInAwaitForEach()
        => VerifyAbsenceAsync(AddInsideMethod(
@"await foreach (var $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForEachRefLoop0()
        => VerifyKeywordAsync(AddInsideMethod(
@"foreach (ref $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForEachRefLoop1()
        => VerifyKeywordAsync(AddInsideMethod(
@"foreach (ref $$ x"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForEachRefLoop2()
        => VerifyKeywordAsync(AddInsideMethod(
@"foreach (ref v$$ x"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForEachRefReadonlyLoop0()
        => VerifyKeywordAsync(AddInsideMethod(
@"foreach (ref readonly $$ x"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForRefLoop0()
        => VerifyKeywordAsync(AddInsideMethod(
@"for (ref $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForRefLoop1()
        => VerifyKeywordAsync(AddInsideMethod(
@"for (ref v$$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForRefReadonlyLoop0()
        => VerifyKeywordAsync(AddInsideMethod(
@"for (ref readonly $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37223")]
    public Task TestInForRefReadonlyLoop1()
        => VerifyKeywordAsync(AddInsideMethod(
@"for (ref readonly v$$"));

    [Fact]
    public Task TestInUsing()
        => VerifyKeywordAsync(AddInsideMethod(
@"using ($$"));

    [Fact]
    public Task TestNotInUsing()
        => VerifyAbsenceAsync(AddInsideMethod(
@"using (var $$"));

    [Fact]
    public Task TestInAwaitUsing()
        => VerifyKeywordAsync(AddInsideMethod(
@"await using ($$"));

    [Fact]
    public Task TestNotInAwaitUsing()
        => VerifyAbsenceAsync(AddInsideMethod(
@"await using (var $$"));

    [Fact]
    public Task TestAfterConstLocal()
        => VerifyKeywordAsync(AddInsideMethod(
@"const $$"));

    [Fact]
    public Task TestNotAfterConstField()
        => VerifyAbsenceAsync(
            """
            class C {
                const $$
            """);

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/12121")]
    public Task TestAfterOutKeywordInArgument()
        => VerifyKeywordAsync(AddInsideMethod(
@"M(out $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/12121")]
    public Task TestAfterOutKeywordInParameter()
        => VerifyAbsenceAsync(
            """
            class C {
                 void M1(out $$
            """);

    [Fact]
    public Task TestVarPatternInSwitch()
        => VerifyKeywordAsync(AddInsideMethod(
            """
            switch(o)
                {
                    case $$
                }
            """));

    [Fact]
    public async Task TestVarPatternInIs()
        => await VerifyKeywordAsync(AddInsideMethod("var b = o is $$ "));

    [Fact]
    public Task TestNotAfterRefInMemberContext()
        => VerifyAbsenceAsync(
            """
            class C {
                ref $$
            """);

    [Fact]
    public Task TestNotAfterRefReadonlyInMemberContext()
        => VerifyAbsenceAsync(
            """
            class C {
                ref readonly $$
            """);

    [Fact]
    public Task TestAfterRefInStatementContext()
        => VerifyKeywordAsync(AddInsideMethod(
@"ref $$"));

    [Fact]
    public Task TestAfterRefReadonlyInStatementContext()
        => VerifyKeywordAsync(AddInsideMethod(
@"ref readonly $$"));

    [Fact]
    public Task TestAfterRefLocalDeclaration()
        => VerifyKeywordAsync(AddInsideMethod(
@"ref $$ int local;"));

    [Fact]
    public Task TestAfterRefReadonlyLocalDeclaration()
        => VerifyKeywordAsync(AddInsideMethod(
@"ref readonly $$ int local;"));

    // For a local function, we can't add any tests - sometimes the keyword is offered and sometimes it's not,
    // depending on whether the keyword is partially written or not. This is because a partially written keyword
    // causes this to be parsed as a local declaration instead. We can't add either test because
    // VerifyKeywordAsync & VerifyAbsenceAsync check for both cases - with the keyword partially written and without.

    [Fact]
    public Task TestNotAfterRefExpression()
        => VerifyAbsenceAsync(AddInsideMethod(
@"ref int x = ref $$"));

    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/10170")]
    public Task TestInPropertyPattern()
        => VerifyKeywordAsync(
            """
            using System;

            class Person { public string Name; }

            class Program
            {
                void Goo(object o)
                {
                    if (o is Person { Name: $$ })
                    {
                        Console.WriteLine(n);
                    }
                }
            }
            """);

    [Fact]
    public Task TestNotInDeclarationDeconstruction()
        => VerifyAbsenceAsync(AddInsideMethod(
@"var (x, $$) = (0, 0);"));

    [Fact]
    public Task TestInMixedDeclarationAndAssignmentInDeconstruction()
        => VerifyKeywordAsync(AddInsideMethod(
@"(x, $$) = (0, 0);"));

    [Fact]
    public async Task TestAfterScoped()
    {
        await VerifyKeywordAsync(AddInsideMethod("scoped $$"));
        await VerifyKeywordAsync("scoped $$");
    }

    [Fact]
    public Task TestWithinExtension()
        => VerifyAbsenceAsync(
            """
            static class C
            {
                extension(string s)
                {
                    $$
                }
            }
            """,
            CSharpNextParseOptions,
            CSharpNextScriptParseOptions);
}
