// 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.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Test.Utilities.Formatting;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting
{
    public class CSharpNewDocumentFormattingServiceTests : AbstractNewDocumentFormattingServiceTests
    {
        public static IEnumerable<object[]> EndOfDocumentSequences
        {
            get
            {
                yield return new object[] { "" };
                yield return new object[] { """

                    """ };
            }
        }

        protected override string Language => LanguageNames.CSharp;
        protected override TestWorkspace CreateTestWorkspace(string testCode, ParseOptions? parseOptions)
            => TestWorkspace.CreateCSharp(testCode, parseOptions);

        [Fact]
        public async Task TestFileScopedNamespaces()
        {
            await TestAsync(testCode: """
                namespace Goo
                {
                    internal class C
                    {
                    }
                }
                """,
            expected: """
            namespace Goo;

            internal class C
            {
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2<NamespaceDeclarationPreference>(NamespaceDeclarationPreference.FileScoped, NotificationOption2.Error) }
            },
            parseOptions: new CSharpParseOptions(LanguageVersion.CSharp10));
        }

        [Fact]
        public async Task TestFileScopedNamespaces_Invalid_MultipleNamespaces()
        {
            var testCode = """
                namespace Goo
                {
                }

                namespace Bar
                {
                }
                """;

            await TestAsync(
                testCode: testCode,
                expected: testCode,
                options: new OptionsCollection(LanguageNames.CSharp)
                {
                    { CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2<NamespaceDeclarationPreference>(NamespaceDeclarationPreference.FileScoped, NotificationOption2.Error) }
                },
                parseOptions: new CSharpParseOptions(LanguageVersion.CSharp10));
        }

        [Fact]
        public async Task TestFileScopedNamespaces_Invalid_WrongLanguageVersion()
        {
            var testCode = """
                namespace Goo
                {
                    internal class C
                    {
                    }
                }
                """;

            await TestAsync(
                testCode: testCode,
                expected: testCode,
                options: new OptionsCollection(LanguageNames.CSharp)
                {
                    { CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2<NamespaceDeclarationPreference>(NamespaceDeclarationPreference.FileScoped, NotificationOption2.Error) }
                },
                parseOptions: new CSharpParseOptions(LanguageVersion.CSharp9));
        }

        [Theory]
        [MemberData(nameof(EndOfDocumentSequences))]
        public async Task TestBlockScopedNamespaces(string endOfDocumentSequence)
        {
            await TestAsync(testCode: $@"
namespace Goo;

internal class C
{{
}}{endOfDocumentSequence}",
            expected: $@"
namespace Goo
{{
    internal class C
    {{
    }}
}}{endOfDocumentSequence}",
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2<NamespaceDeclarationPreference>(NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Error) }
            });
        }

        [Fact]
        public async Task TestOrganizeUsingsWithNoUsings()
        {
            var testCode = """
                namespace Goo
                {
                }
                """;
            await TestAsync(
                testCode: testCode,
                expected: testCode,
                options: new OptionsCollection(LanguageNames.CSharp)
                {
                    { CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, new CodeStyleOption2<AddImportPlacement>(AddImportPlacement.OutsideNamespace, NotificationOption2.Error) }
                });
        }

        [Fact]
        public async Task TestFileBanners()
        {
            await TestAsync(testCode: """
                using System;

                namespace Goo
                {
                }
                """,
            expected: """
            // This is a banner.

            using System;

            namespace Goo
            {
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CodeStyleOptions2.FileHeaderTemplate, "This is a banner." }
            });
        }

        [Fact]
        public async Task TestAccessibilityModifiers()
        {
            await TestAsync(testCode: """
                using System;

                namespace Goo
                {
                    class C
                    {
                    }
                }
                """,
            expected: """
            using System;

            namespace Goo
            {
                internal class C
                {
                }
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CodeStyleOptions2.AccessibilityModifiersRequired, new CodeStyleOption2<AccessibilityModifiersRequired>(AccessibilityModifiersRequired.Always, NotificationOption2.Error) }
            });
        }

        [Fact]
        public async Task TestAccessibilityModifiers_FileScopedNamespace()
        {
            await TestAsync(testCode: """
                using System;

                namespace Goo
                {
                    class C
                    {
                    }
                }
                """,
            expected: """
            using System;

            namespace Goo;
            internal class C
            {
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2<NamespaceDeclarationPreference>(NamespaceDeclarationPreference.FileScoped, NotificationOption2.Error) },
                { CodeStyleOptions2.AccessibilityModifiersRequired, new CodeStyleOption2<AccessibilityModifiersRequired>(AccessibilityModifiersRequired.Always, NotificationOption2.Error) }
            });
        }

        [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/55703")]
        public async Task TestAccessibilityModifiers_IgnoresPartial()
        {
            await TestAsync(
                testCode: """
                using System;

                namespace Goo
                {
                    class E
                    {
                    }

                    partial class C
                    {
                    }

                    class D
                    {
                    }
                }
                """,
                expected: """
                using System;

                namespace Goo
                {
                    internal class E
                    {
                    }

                    partial class C
                    {
                    }

                    internal class D
                    {
                    }
                }
                """,
                options: new OptionsCollection(LanguageNames.CSharp)
                {
                    { CodeStyleOptions2.AccessibilityModifiersRequired, new CodeStyleOption2<AccessibilityModifiersRequired>(AccessibilityModifiersRequired.Always, NotificationOption2.Error) }
                });
        }

        [Fact]
        public async Task TestUsingDirectivePlacement()
        {
            await TestAsync(testCode: """
                using System;

                namespace Goo
                {
                }
                """,
            expected: """
            namespace Goo
            {
                using System;
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, new CodeStyleOption2<AddImportPlacement>(AddImportPlacement.InsideNamespace, NotificationOption2.Error) }
            });
        }

        [Fact]
        public async Task TestPreferTopLevelStatements()
        {
            await TestAsync(testCode: """
                using System;

                // See https://aka.ms/new-console-template for more information
                Console.WriteLine("Hello, World!");
                """,
            expected: """
            using System;

            // See https://aka.ms/new-console-template for more information
            Console.WriteLine("Hello, World!");
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.PreferTopLevelStatements, new CodeStyleOption2<bool>(value: true, notification: NotificationOption2.Suggestion) }
            });
        }

        [Fact]
        public async Task TestPreferProgramMain()
        {
            await TestAsync(testCode: """
                using System;

                // See https://aka.ms/new-console-template for more information
                Console.WriteLine("Hello, World!");
                """,
            expected: """
            using System;

            internal class Program
            {
                private static void Main(string[] args)
                {
                    Console.WriteLine("Hello, World!");
                }
            }
            """,
            options: new OptionsCollection(LanguageNames.CSharp)
            {
                { CSharpCodeStyleOptions.PreferTopLevelStatements, new CodeStyleOption2<bool>(value: false, notification: NotificationOption2.Suggestion) }
            });
        }
    }
}
