// 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;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;

namespace Microsoft.CodeAnalysis.FileHeaders;

internal abstract class AbstractFileHeaderHelper(ISyntaxKinds syntaxKinds)
{
    /// <summary>
    /// Gets the text prefix indicating a single-line comment.
    /// </summary>
    public abstract string CommentPrefix { get; }

    protected abstract ReadOnlyMemory<char> GetTextContextOfComment(SyntaxTrivia commentTrivia);

    /// <inheritdoc cref="ISyntaxKinds.SingleLineCommentTrivia"/>
    private int SingleLineCommentTriviaKind { get; } = syntaxKinds.SingleLineCommentTrivia;

    /// <inheritdoc cref="ISyntaxKinds.MultiLineCommentTrivia"/>
    private int? MultiLineCommentTriviaKind { get; } = syntaxKinds.MultiLineCommentTrivia;

    /// <inheritdoc cref="ISyntaxKinds.WhitespaceTrivia"/>
    private int WhitespaceTriviaKind { get; } = syntaxKinds.WhitespaceTrivia;

    /// <inheritdoc cref="ISyntaxKinds.EndOfLineTrivia"/>
    private int EndOfLineTriviaKind { get; } = syntaxKinds.EndOfLineTrivia;

    public FileHeader ParseFileHeader(SyntaxNode root)
    {
        var firstToken = root.GetFirstToken(includeZeroWidth: true);
        var firstNonWhitespaceTrivia = IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia);

        if (firstNonWhitespaceTrivia == -1)
        {
            return FileHeader.MissingFileHeader(0);
        }

        using var _ = PooledStringBuilder.GetInstance(out var sb);
        var endOfLineCount = 0;
        var missingHeaderOffset = 0;
        var fileHeaderStart = int.MaxValue;
        var fileHeaderEnd = int.MinValue;

        for (var i = firstNonWhitespaceTrivia; i < firstToken.LeadingTrivia.Count; i++)
        {
            var trivia = firstToken.LeadingTrivia[i];

            if (trivia.RawKind == WhitespaceTriviaKind)
            {
                endOfLineCount = 0;
            }
            else if (trivia.RawKind == SingleLineCommentTriviaKind)
            {
                endOfLineCount = 0;

                var commentText = GetTextContextOfComment(trivia).Span.Trim();

                fileHeaderStart = Math.Min(trivia.FullSpan.Start, fileHeaderStart);
                fileHeaderEnd = trivia.FullSpan.End;

#if NET
                sb.Append(commentText).AppendLine();
#else
                sb.AppendLine(commentText.ToString());
#endif
            }
            else if (trivia.RawKind == MultiLineCommentTriviaKind)
            {
                // only process a MultiLineCommentTrivia if no SingleLineCommentTrivia have been processed
                if (sb.Length == 0)
                {
                    var commentText = GetTextContextOfComment(trivia);
                    var triviaStringParts = commentText.Span.Trim().ToString().Replace("\r\n", "\n").Split('\n');

                    foreach (var part in triviaStringParts)
                    {
                        var trimmedPart = part.TrimStart(' ', '*');
                        sb.AppendLine(trimmedPart);
                    }

                    fileHeaderStart = trivia.FullSpan.Start;
                    fileHeaderEnd = trivia.FullSpan.End;
                }

                break;
            }
            else if (trivia.RawKind == EndOfLineTriviaKind)
            {
                endOfLineCount++;
                if (endOfLineCount > 1)
                {
                    break;
                }
            }
            else
            {
                if (trivia.IsDirective)
                {
                    missingHeaderOffset = trivia.FullSpan.End;
                }

                if ((fileHeaderStart < fileHeaderEnd) || !trivia.IsDirective)
                {
                    break;
                }
            }
        }

        if (fileHeaderStart > fileHeaderEnd)
        {
            return FileHeader.MissingFileHeader(missingHeaderOffset);
        }

        if (sb.Length > 0)
        {
            // remove the final newline
            var eolLength = Environment.NewLine.Length;
            sb.Remove(sb.Length - eolLength, eolLength);
        }

        return new FileHeader(sb.ToString(), fileHeaderStart, fileHeaderEnd, CommentPrefix.Length);
    }

    /// <summary>
    /// Returns the index of the first non-whitespace trivia in the given trivia list.
    /// </summary>
    /// <param name="triviaList">The trivia list to process.</param>
    /// <typeparam name="T">The type of the trivia list.</typeparam>
    /// <returns>The index where the non-whitespace starts, or -1 if there is no non-whitespace trivia.</returns>
    private int IndexOfFirstNonWhitespaceTrivia<T>(T triviaList)
        where T : IReadOnlyList<SyntaxTrivia>
    {
        for (var index = 0; index < triviaList.Count; index++)
        {
            var currentTrivia = triviaList[index];
            if (currentTrivia.RawKind != EndOfLineTriviaKind
                && currentTrivia.RawKind != WhitespaceTriviaKind)
            {
                // encountered non-whitespace trivia -> the search is done.
                return index;
            }
        }

        return -1;
    }
}
