﻿// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal static class SourceDocumentationCommentUtils
    {
        internal static string GetAndCacheDocumentationComment(Symbol symbol, bool expandIncludes, ref string lazyXmlText)
        {
            // NOTE: For xml doc comments from source, the culture is ignored (we just return the
            // doc comment as it appears in source), so it won't affect what we cache.
            if (lazyXmlText == null)
            {
                string xmlText = DocumentationCommentCompiler.GetDocumentationCommentXml(symbol, expandIncludes, default(CancellationToken));
                Interlocked.CompareExchange(ref lazyXmlText, xmlText, null);
            }

            return lazyXmlText;
        }

        internal static ImmutableArray<DocumentationCommentTriviaSyntax> GetDocumentationCommentTriviaFromSyntaxNode(CSharpSyntaxNode syntaxNode, DiagnosticBag diagnostics)
        {
            if (syntaxNode.SyntaxTree.Options.DocumentationMode < DocumentationMode.Parse)
            {
                return ImmutableArray<DocumentationCommentTriviaSyntax>.Empty;
            }

            // All declarators in a declaration get the same doc comment.
            // (As a consequence, the same duplicate diagnostics are produced for each declarator.)
            if (syntaxNode.Kind() == SyntaxKind.VariableDeclarator)
            {
                CSharpSyntaxNode curr = syntaxNode;
                while ((object)curr != null)
                {
                    SyntaxKind kind = curr.Kind();
                    if (kind == SyntaxKind.FieldDeclaration || kind == SyntaxKind.EventFieldDeclaration)
                    {
                        break;
                    }

                    curr = curr.Parent;
                }

                if ((object)curr != null)
                {
                    syntaxNode = curr;
                }
            }

            ArrayBuilder<DocumentationCommentTriviaSyntax> builder = null;
            bool seenOtherTrivia = false;
            foreach (var trivia in syntaxNode.GetLeadingTrivia().Reverse())
            {
                switch (trivia.Kind())
                {
                    case SyntaxKind.SingleLineDocumentationCommentTrivia:
                    case SyntaxKind.MultiLineDocumentationCommentTrivia:
                        {
                            if (seenOtherTrivia)
                            {
                                // In most cases, unprocessed doc comments are reported by UnprocessedDocumentationCommentFinder.
                                // However, in places where doc comments *are* allowed, it's easier to determine which will
                                // be unprocessed here.
                                var tree = (SyntaxTree)trivia.SyntaxTree;
                                if (tree.ReportDocumentationCommentDiagnostics())
                                {
                                    int start = trivia.Position; // FullSpan start to include /** or ///
                                    const int length = 1; //Match dev11: span is just one character
                                    diagnostics.Add(ErrorCode.WRN_UnprocessedXMLComment, new SourceLocation(tree, new TextSpan(start, length)));
                                }
                            }
                            else
                            {
                                if (builder == null)
                                {
                                    builder = ArrayBuilder<DocumentationCommentTriviaSyntax>.GetInstance();
                                }

                                builder.Add((DocumentationCommentTriviaSyntax)trivia.GetStructure());
                            }
                            break;
                        }
                    case SyntaxKind.WhitespaceTrivia:
                    case SyntaxKind.EndOfLineTrivia:
                        // These can legally appear between doc comments.
                        break;
                    default:
                        // For some reason, dev11 ignores trivia between the last doc comment and the
                        // symbol declaration.  (e.g. can have regular comment between doc comment and decl).
                        if (builder != null)
                        {
                            seenOtherTrivia = true;
                        }
                        break;
                }
            }

            if (builder == null)
            {
                return ImmutableArray<DocumentationCommentTriviaSyntax>.Empty;
            }

            builder.ReverseContents();
            return builder.ToImmutableAndFree();
        }
    }
}
