﻿// 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 Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseImplicitType;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.UseExplicitOrImplicitType
{
    [Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
    public class UseImplicitTypeRefactoringTests : AbstractCSharpCodeActionTest
    {
        protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
            => new UseImplicitTypeCodeRefactoringProvider();

        [Fact]
        public async Task TestIntLocalDeclaration()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        int[||] i = 0;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        var i = 0;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35180")]
        public async Task TestSelection1()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        [|int i = 0;|]
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        var i = 0;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35180")]
        public async Task TestSelection2()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        [|int|] i = 0;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        var i = 0;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35180")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/35525")]
        public async Task TestSelectionNotType()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        int [|i|] = 0;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        var i = 0;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestForeachInsideLocalDeclaration()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        System.Action notThisLocal = () => { foreach (int[||] i in new int[0]) { } };
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        System.Action notThisLocal = () => { foreach (var[||] i in new int[0]) { } };
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestInIntPattern()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        _ = 0 is int[||] i;
                    }
                }
                """;

            await TestMissingInRegularAndScriptAsync(code);
        }

        [Fact]
        public async Task TestIntLocalDeclaration_Multiple()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        int[||] i = 0, j = j;
                    }
                }
                """;

            await TestMissingInRegularAndScriptAsync(code);
        }

        [Fact]
        public async Task TestIntLocalDeclaration_NoInitializer()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        int[||] i;
                    }
                }
                """;

            await TestMissingInRegularAndScriptAsync(code);
        }

        [Fact]
        public async Task TestIntForLoop()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        for (int[||] i = 0;;) { }
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        for (var i = 0;;) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestInDispose()
        {
            var code = """
                class C : System.IDisposable
                {
                    static void Main()
                    {
                        using (C[||] c = new C()) { }
                    }
                }
                """;

            var expected = """
                class C : System.IDisposable
                {
                    static void Main()
                    {
                        using (var c = new C()) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestIntForeachLoop()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        foreach (int[||] i in new[] { 0 }) { }
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        foreach (var i in new[] { 0 }) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")]
        public async Task TestIntForeachLoop2()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        foreach ([|int|] i in new[] { 0 }) { }
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        foreach (var i in new[] { 0 }) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")]
        public async Task TestIntForeachLoop3()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        foreach (int [|i|] in new[] { 0 }) { }
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        foreach (var i in new[] { 0 }) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35525")]
        public async Task TestIntForeachLoop4()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        foreach ([|object|] i in new[] { new object() }) { }
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        foreach (var i in new[] { new object() }) { }
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestIntDeconstruction()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        (int[||] i, var j) = (0, 1);
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        (var i, var j) = (0, 1);
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact]
        public async Task TestIntDeconstruction2()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        (int[||] i, var j) = (0, 1);
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        (var i, var j) = (0, 1);
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/26923")]
        public async Task NoSuggestionOnForeachCollectionExpression()
        {
            var code = """
                using System;
                using System.Collections.Generic;

                class C
                {
                    static void Main(string[] args)
                    {
                        foreach (string arg in [|args|])
                        {

                        }
                    }
                }
                """;

            // We never want to get offered here under any circumstances.
            await TestMissingInRegularAndScriptAsync(code);
        }

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

                class C
                {
                    static void Main(string[] args)
                    {
                        int a = 40 [||]+ 2;
                    }
                }
                """;

            // We never want to get offered here under any circumstances.
            await TestMissingInRegularAndScriptAsync(code);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42880")]
        public async Task TestRefLocal1()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        [||]ref string rStr1 = ref str;
                    }
                }
                """;
            var expected = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref var rStr1 = ref str;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42880")]
        public async Task TestRefLocal2()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref [||]string rStr1 = ref str;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref var rStr1 = ref str;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42880")]
        public async Task TestRefLocal3()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref string [||]rStr1 = ref str;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref var rStr1 = ref str;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42880")]
        public async Task TestRefReadonlyLocal1()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref readonly [||]string rStr1 = ref str;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref readonly var rStr1 = ref str;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/42880")]
        public async Task TestRefReadonlyLocal2()
        {
            var code = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref readonly string[||] rStr1 = ref str;
                    }
                }
                """;

            var expected = """
                class C
                {
                    static void Main()
                    {
                        string str = "";

                        ref readonly var rStr1 = ref str;
                    }
                }
                """;

            await TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(code, expected);
        }

        private async Task TestInRegularAndScriptWhenDiagnosticNotAppliedAsync(string initialMarkup, string expectedMarkup)
        {
            // Enabled because the diagnostic is disabled
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferImplicitTypeWithNone());

            // Enabled because the diagnostic is checking for the other direction
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferExplicitTypeWithNone());
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferExplicitTypeWithSilent());
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferExplicitTypeWithInfo());

            // Disabled because the diagnostic will report it instead
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithSilent()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithInfo()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithWarning()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithError()));

            // Currently this refactoring is still enabled in cases where it would cause a warning or error
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferExplicitTypeWithWarning());
            await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup, options: this.PreferExplicitTypeWithError());
        }

        private async Task TestMissingInRegularAndScriptAsync(string initialMarkup)
        {
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithNone()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferExplicitTypeWithNone()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithSilent()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferExplicitTypeWithSilent()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithInfo()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferExplicitTypeWithInfo()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithWarning()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferExplicitTypeWithWarning()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferImplicitTypeWithError()));
            await TestMissingInRegularAndScriptAsync(initialMarkup, parameters: new TestParameters(options: this.PreferExplicitTypeWithError()));
        }
    }
}
