﻿// 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.Tasks;
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 class WhenKeywordRecommenderTests : KeywordRecommenderTests
    {
        [Fact]
        public async Task TestForCatchClause_AfterCatch()
        {
            await VerifyKeywordAsync(AddInsideMethod(
@"try {} catch $$"));
        }

        [Fact]
        public async Task TestForCatchClause_AfterCatchDeclaration1()
        {
            await VerifyKeywordAsync(AddInsideMethod(
@"try {} catch (Exception) $$"));
        }

        [Fact]
        public async Task TestForCatchClause_AfterCatchDeclaration2()
        {
            await VerifyKeywordAsync(AddInsideMethod(
@"try {} catch (Exception e) $$"));
        }

        [Fact]
        public async Task TestForCatchClause_AfterCatchDeclarationEmpty()
        {
            await VerifyKeywordAsync(AddInsideMethod(
@"try {} catch () $$"));
        }

        [Fact]
        public async Task TestForCatchClause_NotAfterTryBlock()
        {
            await VerifyAbsenceAsync(AddInsideMethod(
@"try {} $$"));
        }

        [Fact]
        public async Task TestForCatchClause_NotAfterFilter1()
        {
            await VerifyAbsenceAsync(AddInsideMethod(
@"try {} catch (Exception e) when $$"));
        }

        [Fact]
        public async Task TestForCatchClause_NotAfterFilter2()
        {
            await VerifyAbsenceAsync(AddInsideMethod(
@"try {} catch (Exception e) when ($$"));
        }

        [Fact]
        public async Task TestForCatchClause_NotAfterFilter3()
        {
            await VerifyAbsenceAsync(AddInsideMethod(
@"try {} catch (Exception e) when (true) $$"));
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24113")]
        public async Task TestForSwitchCase_AfterDeclarationPattern()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (1) { case int i $$ }"));

        [Fact]
        public async Task TestForSwitchCase_AfterDeclarationPattern_BeforeBreak()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (1) { case int i $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_AfterDeclarationPattern_BeforeWhen()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (1) { case int i $$ when }"));

        [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/25084")]
        [InlineData("int.MinValue")]
        [InlineData("1")]
        [InlineData("1 + 1")]
        [InlineData("true ? 1 : 1")]
        [InlineData("(1 + )")]
        public async Task TestForSwitchCase_AfterExpression(string expression)
            => await VerifyKeywordAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ }}"));

        [Theory]
        [InlineData("int.MinValue")]
        [InlineData("1")]
        [InlineData("1 + 1")]
        [InlineData("true ? 1 : 1")]
        [InlineData("(1 + )")]
        public async Task TestForSwitchCase_AfterExpression_BeforeBreak(string expression)
            => await VerifyKeywordAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ break; }}"));

        [Theory]
        [InlineData("int.MinValue")]
        [InlineData("1")]
        [InlineData("1 + 1")]
        [InlineData("true ? 1 : 1")]
        [InlineData("(1 + )")]
        public async Task TestForSwitchCase_AfterExpression_BeforeWhen(string expression)
            => await VerifyKeywordAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ when }}"));

        [Theory]
        [InlineData("int.")]
        [InlineData("1 +")]
        [InlineData("true ?")]
        [InlineData("true ? 1")]
        [InlineData("true ? 1 :")]
        [InlineData("(1")]
        [InlineData("(1 + 1")]
        public async Task TestForSwitchCase_NotAfterIncompleteExpression(string expression)
            => await VerifyAbsenceAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ }}"));

        [Theory]
        [InlineData("int.")]
        [InlineData("1 +")]
        [InlineData("true ?")]
        [InlineData("true ? 1")]
        [InlineData("true ? 1 :")]
        [InlineData("(1")]
        [InlineData("(1 + 1")]
        public async Task TestForSwitchCase_NotAfterIncompleteExpression_BeforeBreak(string expression)
            => await VerifyAbsenceAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ break; }}"));

        [Theory]
        [InlineData("int.")]
        [InlineData("1 +")]
        [InlineData("true ?")]
        [InlineData("true ? 1")]
        [InlineData("true ? 1 :")]
        [InlineData("(1")]
        [InlineData("(1 + 1")]
        public async Task TestForSwitchCase_NotAfterIncompleteExpression_BeforeWhen(string expression)
            => await VerifyAbsenceAsync(AddInsideMethod($@"switch (1) {{ case {expression} $$ when }}"));

        [Fact]
        public async Task TestForSwitchCase_NotInsideExpression()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case (1 + 1 $$) }"));

        [Fact]
        public async Task TestForSwitchCase_NotInsideExpression_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case (1 + 1 $$) break; }"));

        [Fact]
        public async Task TestForSwitchCase_NotInsideExpression_BeforeWhen()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case (1 + 1 $$) when }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterCase()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case $$ }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterCase_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterCase_BeforeWhen()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterDefault()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { default $$ }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterDefault_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { default $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterWhen()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case 1 when $$ }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterWhen_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case 1 when $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterColon()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case 1: $$ }"));

        [Fact]
        public async Task TestForSwitchCase_NotAfterColon_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { case 1: $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_NotInEmptySwitchStatement()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType_BeforeBreak()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType_BeforeWhen()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary<string, int> $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType_BeforeBreak()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary<string, int> $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType_BeforeWhen()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary<string, int> $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType()
            => await VerifyKeywordAsync(@"
class SyntaxNode { }
class C
{
    void M() { switch (new object()) { case SyntaxNode $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType_BeforeBreak()
            => await VerifyKeywordAsync(@"
class SyntaxNode { }
class C
{
    void M() { switch (new object()) { case SyntaxNode $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType_BeforeWhen()
            => await VerifyKeywordAsync(@"
class SyntaxNode { }
class C
{
    void M() { switch (new object()) { case SyntaxNode $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias()
            => await VerifyKeywordAsync(@"
using Type = System.String;
class C
{
    void M() { switch (new object()) { case Type $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias_BeforeBreak()
            => await VerifyKeywordAsync(@"
using Type = System.String;
class C
{
    void M() { switch (new object()) { case Type $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias_BeforeWhen()
            => await VerifyKeywordAsync(@"
using Type = System.String;
class C
{
    void M() { switch (new object()) { case Type $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName()
    => await VerifyKeywordAsync(@"
class ValueTuple { }
class ValueTuple<T> { }
class C
{
    void M() { switch (new object()) { case ValueTuple $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName_BeforeBreak()
            => await VerifyKeywordAsync(@"
class ValueTuple { }
class ValueTuple<T> { }
class C
{
    void M() { switch (new object()) { case ValueTuple $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName_BeforeWhen()
            => await VerifyKeywordAsync(@"
class ValueTuple { }
class ValueTuple<T> { }
class C
{
    void M() { switch (new object()) { case ValueTuple $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterColorColor()
            => await VerifyKeywordAsync(@"
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterColorColor_BeforeBreak()
            => await VerifyKeywordAsync(@"
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterColorColor_BeforeWhen()
            => await VerifyKeywordAsync(@"
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterOverloadedTypeNameColorColor()
    => await VerifyKeywordAsync(@"
class Color<T> { }
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterOverloadedTypeNameColorColor_BeforeBreak()
            => await VerifyKeywordAsync(@"
class Color<T> { }
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterOverloadedTypeNameColorColor_BeforeWhen()
            => await VerifyKeywordAsync(@"
class Color<T> { }
class Color { }
class C
{
    const Color Color = null;
    void M() { switch (new object()) { case Color $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstant()
            => await VerifyKeywordAsync(AddInsideMethod(@"const object c = null; switch (new object()) { case c $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstant_BeforeBreak()
            => await VerifyKeywordAsync(AddInsideMethod(@"const object c = null; switch (new object()) { case c $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstant_BeforeWhen()
            => await VerifyKeywordAsync(AddInsideMethod(@"const object c = null; switch (new object()) { case c $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterUnknownName()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case unknown $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterUnknownName_BeforeBreak()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case unknown $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterUnknownName_BeforeWhen()
            => await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case unknown $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterVar()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case var $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterVar_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case var $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterVar_BeforeWhen()
            => await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case var $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterClassVar()
            => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { switch (new object()) { case var $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterClassVar_BeforeBreak()
            => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { switch (new object()) { case var $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterClassVar_BeforeWhen()
            => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { switch (new object()) { case var $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAliasVar()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    void M() { switch (new object()) { case var $$ } }
}");
        }

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAliasVar_BeforeBreak()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    void M() { switch (new object()) { case var $$ break; } }
}");
        }

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAliasVar_BeforeWhen()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    void M() { switch (new object()) { case var $$ when } }
}");
        }

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar()
            => await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar_BeforeBreak()
            => await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ break; }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar_BeforeWhen()
            => await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ when }"));

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar()
            => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { const object var = null; switch (new object()) { case var $$ } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar_BeforeBreak()
    => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { const object var = null; switch (new object()) { case var $$ break; } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar_BeforeWhen()
    => await VerifyAbsenceAsync(@"
class var { }
class C
{
    void M() { const object var = null; switch (new object()) { case var $$ when } }
}");

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    const object var = null;
    void M() { switch (new object()) { case var $$ } }
}");
        }

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar_BeforeBreak()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    const object var = null;
    void M() { switch (new object()) { case var $$ break; } }
}");
        }

        [Fact]
        public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar_BeforeWhen()
        {
            await VerifyAbsenceAsync(@"
using var = System.String;
class C
{
    const object var = null;
    void M() { switch (new object()) { case var $$ when } }
}");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/44480")]
        public async Task TestAfterSwitchExpressionPattern1()
        {
            await VerifyKeywordAsync(@"
using var = System.String;
class C
{
    void M(int i)
    {
        _ = i switch
        {
            < 0 $$ => 1,
        };
    }
}");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/44480")]
        public async Task TestAfterSwitchExpressionPattern2()
        {
            await VerifyKeywordAsync(@"
using var = System.String;
class C
{
    void M(int i)
    {
        _ = i switch
        {
            4 $$ => 1,
        };
    }
}");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/44480")]
        public async Task TestAfterSwitchExpressionPattern3()
        {
            await VerifyKeywordAsync(@"
using var = System.String;
class C
{
    void M(int i)
    {
        _ = i switch
        {
            int $$ => 1,
        };
    }
}");
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/44480")]
        public async Task TestAfterSwitchExpressionPattern4()
        {
            await VerifyKeywordAsync(@"
using var = System.String;
class C
{
    void M(int i)
    {
        _ = i switch
        {
            int $$ or 1 => 1,
        };
    }
}");
        }
    }
}
