// 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.Threading.Tasks;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SplitOrMergeIfStatements
{
    public sealed partial class MergeConsecutiveIfStatementsTests
    {
        [Theory]
        [InlineData("[||]if (b)")]
        [InlineData("i[||]f (b)")]
        [InlineData("if[||] (b)")]
        [InlineData("if [||](b)")]
        [InlineData("if (b)[||]")]
        [InlineData("[|if|] (b)")]
        [InlineData("[|if (b)|]")]
        public async Task MergedIntoPreviousStatementOnIfSpans(string ifLine)
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        " + ifLine + @"
            return;
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementOnIfExtendedHeaderSelection()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
[|        if (b)
|]            return;
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementOnIfFullSelection()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [|if (b)
            return;|]
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementOnIfExtendedFullSelection()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
[|        if (b)
            return;
|]    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementOnIfFullSelectionWithoutElseClause()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [|if (b)
            return;|]
        else
        {
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
        else
        {
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementOnIfExtendedFullSelectionWithoutElseClause()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
[|        if (b)
            return;
|]        else
        {
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
        else
        {
        }
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementOnIfFullSelectionWithElseClause()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [|if (b)
            return;
        else
        {
        }|]
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementOnIfExtendedFullSelectionWithElseClause()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
[|        if (b)
            return;
        else
        {
        }
|]    }
}");
        }

        [Theory]
        [InlineData("if ([||]b)")]
        [InlineData("[|i|]f (b)")]
        [InlineData("[|if (|]b)")]
        [InlineData("if [|(|]b)")]
        [InlineData("if (b[|)|]")]
        [InlineData("if ([|b|])")]
        [InlineData("if [|(b)|]")]
        public async Task NotMergedIntoPreviousStatementOnIfSpans(string ifLine)
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        " + ifLine + @"
            return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementOnIfOverreachingSelection()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [|if (b)
          |]return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementOnIfBodySelection()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        if (b)
            [|return;|]
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuits1()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [||]if (b)
            return;
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuits2()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            throw new System.Exception();
        [||]if (b)
            throw new System.Exception();
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            throw new System.Exception();
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuits3()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a)
                continue;
            [||]if (b)
                continue;
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a || b)
                continue;
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuits4()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a)
            {
                if (a)
                    continue;
                else
                    break;
            }

            [||]if (b)
            {
                if (a)
                    continue;
                else
                    break;
            }
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a || b)
            {
                if (a)
                    continue;
                else
                    break;
            }
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuits5()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a)
                switch (a)
                {
                    default:
                        continue;
                }

            [||]if (b)
                switch (a)
                {
                    default:
                        continue;
                }
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a || b)
                switch (a)
                {
                    default:
                        continue;
                }
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuitsInSwitchSection()
        {
            // Switch sections are interesting in that they are blocks of statements that aren't BlockSyntax.
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        switch (a)
        {
            case true:
                if (a)
                    break;
                [||]if (b)
                    break;
                break;
        }
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        switch (a)
        {
            case true:
                if (a || b)
                    break;
                break;
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuitsWithDifferenceInBlocks()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
        {
            {
                return;
            }
        }

        [||]if (b)
            return;
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
        {
            {
                return;
            }
        }
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIncludingElseClauseIfControlFlowQuits()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [||]if (b)
            return;
        else
            System.Console.WriteLine();
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
        else
            System.Console.WriteLine();
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIncludingElseIfClauseIfControlFlowQuits()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [||]if (b)
            return;
        else if (a && b)
            System.Console.WriteLine();
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b)
            return;
        else if (a && b)
            System.Console.WriteLine();
    }
}");
        }

        [Fact]
        public async Task MergedIntoPreviousStatementIfControlFlowQuitsWithPreservedSingleLineFormatting()
        {
            await TestInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a) return;
        [||]if (b) return;
    }
}",
@"class C
{
    void M(bool a, bool b)
    {
        if (a || b) return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementIfControlFlowContinues1()
        {
            // Even though there are no statements inside, we still can't merge these into one statement
            // because it would change the semantics from always evaluating the second condition to short-circuiting.
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
        {
        }

        [||]if (b)
        {
        }
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementIfControlFlowContinues2()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            System.Console.WriteLine();
        [||]if (b)
            System.Console.WriteLine();
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementIfControlFlowContinues3()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
        {
            if (a)
                return;
        }

        [||]if (b)
        {
            if (a)
                return;
        }
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementIfControlFlowContinues4()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            while (a)
            {
                break;
            }

        [||]if (b)
            while (a)
            {
                break;
            }
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementIfControlFlowContinues5()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        while (true)
        {
            if (a)
                switch (a)
                {
                    default:
                        break;
                }

            [||]if (b)
                switch (a)
                {
                    default:
                        break;
                }
        }
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementWithUnmatchingStatementsIfControlFlowQuits()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        [||]if (b)
            throw new System.Exception();
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementThatHasElseClauseIfControlFlowQuits1()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        else
            return;

        [||]if (b)
            return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementThatHasElseClauseIfControlFlowQuits2()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;
        else
            return;

        [||]if (b)
            return;
        else
            return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementAsEmbeddedStatementIfControlFlowQuits1()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;

        while (a)
            [||]if (b)
                return;
    }
}");
        }

        [Fact]
        public async Task NotMergedIntoPreviousStatementAsEmbeddedStatementIfControlFlowQuits2()
        {
            await TestMissingInRegularAndScriptAsync(
@"class C
{
    void M(bool a, bool b)
    {
        if (a)
            return;

        if (a)
        {
        }
        else [||]if (b)
            return;
    }
}");
        }
    }
}
