// 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.Linq;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.AddImport
{
    internal static class AddImportHelpers
    {
        public static (TRootSyntax root, bool addBlankLine) MoveTrivia<TRootSyntax, TImportDirectiveSyntax>(
            ISyntaxFacts syntaxFacts,
            TRootSyntax root,
            SyntaxList<TImportDirectiveSyntax> existingImports,
            List<TImportDirectiveSyntax> newImports)
            where TRootSyntax : SyntaxNode
            where TImportDirectiveSyntax : SyntaxNode
        {
            var addBlankLine = false;
            if (existingImports.Count == 0)
            {
                // We add a blank line after a brand new using group.
                addBlankLine = newImports.Count > 0;

                // We don't have any existing usings. Move any trivia on the first token 
                // of the file to the first using.
                // 
                // Don't do this for doc comments, as they belong to the first element
                // already in the file (like a class) and we don't want it to move to
                // the using.
                var firstToken = root.GetFirstToken();
                var endOfLine = root.DescendantTrivia().FirstOrNull((trivia, syntaxFacts) => syntaxFacts.IsEndOfLineTrivia(trivia), syntaxFacts) ?? syntaxFacts.ElasticCarriageReturnLineFeed;

                // Remove the leading directives from the first token.
                var newFirstToken = firstToken.WithLeadingTrivia(
                    firstToken.LeadingTrivia.Where(t => IsDocCommentOrElastic(syntaxFacts, t)));

                root = root.ReplaceToken(firstToken, newFirstToken);

                // Move the leading trivia from the first token to the first using.
                var newFirstUsing = newImports[0].WithLeadingTrivia(
                    firstToken.LeadingTrivia.Where(t => !IsDocCommentOrElastic(syntaxFacts, t)));
                newImports[0] = newFirstUsing;

                for (var i = 0; i < newImports.Count; i++)
                {
                    var trailingTrivia = newImports[i].GetTrailingTrivia();
                    if (!trailingTrivia.Any() || !syntaxFacts.IsEndOfLineTrivia(trailingTrivia[^1]))
                    {
                        newImports[i] = newImports[i].WithAppendedTrailingTrivia(endOfLine);
                    }
                }
            }
            else
            {
                var originalFirstUsing = existingImports[0];
                var originalFirstUsingCurrentIndex = newImports.IndexOf(originalFirstUsing);
                var originalLastUsing = existingImports[^1];
                var originalLastUsingCurrentIndex = newImports.IndexOf(originalLastUsing);

                var originalFirstUsingTrailingTrivia = originalFirstUsing.GetTrailingTrivia();
                var originalFirstUsingLineEnding = originalFirstUsingTrailingTrivia.Any() && syntaxFacts.IsEndOfLineTrivia(originalFirstUsingTrailingTrivia[^1])
                    ? originalFirstUsingTrailingTrivia[^1]
                    : syntaxFacts.ElasticCarriageReturnLineFeed;

                if (originalFirstUsingCurrentIndex != 0)
                {
                    // We added a new first-using.  Take the trivia on the existing first using
                    // And move it to the new using.
                    newImports[0] = newImports[0].WithLeadingTrivia(originalFirstUsing.GetLeadingTrivia());
                    newImports[originalFirstUsingCurrentIndex] = originalFirstUsing.WithoutLeadingTrivia();
                }

                if (originalLastUsingCurrentIndex != newImports.Count - 1)
                {
                    // We added a new last-using.  Take the trailing trivia on the existing last using
                    // And move it to the new using.
                    newImports[^1] = newImports[^1].WithTrailingTrivia(originalLastUsing.GetTrailingTrivia());
                    newImports[originalLastUsingCurrentIndex] = originalLastUsing.WithoutTrailingTrivia();
                }

                for (var i = 0; i < newImports.Count; i++)
                {
                    var trailingTrivia = newImports[i].GetTrailingTrivia();
                    if (!trailingTrivia.Any() || !syntaxFacts.IsEndOfLineTrivia(trailingTrivia[^1]))
                    {
                        newImports[i] = newImports[i].WithAppendedTrailingTrivia(originalFirstUsingLineEnding);
                    }
                }
            }

            return (root, addBlankLine);
        }

        private static bool IsDocCommentOrElastic(ISyntaxFacts syntaxFacts, SyntaxTrivia t)
            => syntaxFacts.IsDocumentationComment(t) || syntaxFacts.IsElastic(t);
    }
}
