// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeAnalysis.Tools.Utilities
{
    internal static class GeneratedCodeUtilities
    {
        private static readonly string[] s_autoGeneratedStrings = new[] { "<autogenerated", "<auto-generated" };

        private static readonly Func<SyntaxTrivia, bool> s_isCSharpCommentTrivia =
            (syntaxTrivia) => syntaxTrivia.IsKind(CSharp.SyntaxKind.SingleLineCommentTrivia)
                || syntaxTrivia.IsKind(CSharp.SyntaxKind.MultiLineCommentTrivia)
                || syntaxTrivia.IsKind(CSharp.SyntaxKind.SingleLineDocumentationCommentTrivia)
                || syntaxTrivia.IsKind(CSharp.SyntaxKind.MultiLineDocumentationCommentTrivia);

        private static readonly Func<SyntaxTrivia, bool> s_isVisualBasicCommentTrivia =
            (syntaxTrivia) => syntaxTrivia.IsKind(VisualBasic.SyntaxKind.CommentTrivia)
                || syntaxTrivia.IsKind(VisualBasic.SyntaxKind.DocumentationCommentTrivia);

        internal static async Task<bool> IsGeneratedCodeAsync(SyntaxTree syntaxTree, CancellationToken cancellationToken)
        {
            if (IsGeneratedCodeFileName(syntaxTree.FilePath))
            {
                return true;
            }

            var isCommentTrivia = syntaxTree.Options.Language == LanguageNames.CSharp
                ? s_isCSharpCommentTrivia
                : s_isVisualBasicCommentTrivia;

            var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            return BeginsWithAutoGeneratedComment(syntaxRoot, isCommentTrivia);
        }

        private static bool IsGeneratedCodeFileName(string? filePath)
        {
            if (!string.IsNullOrEmpty(filePath))
            {
                var fileName = Path.GetFileName(filePath);
                if (fileName.StartsWith("TemporaryGeneratedFile_", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }

                var extension = Path.GetExtension(fileName);
                if (!string.IsNullOrEmpty(extension))
                {
                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
                    if (fileNameWithoutExtension.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) ||
                        fileNameWithoutExtension.EndsWith(".generated", StringComparison.OrdinalIgnoreCase) ||
                        fileNameWithoutExtension.EndsWith(".g", StringComparison.OrdinalIgnoreCase) ||
                        fileNameWithoutExtension.EndsWith(".g.i", StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static bool BeginsWithAutoGeneratedComment(SyntaxNode syntaxRoot, Func<SyntaxTrivia, bool> isComment)
        {
            if (syntaxRoot.HasLeadingTrivia)
            {
                var leadingTrivia = syntaxRoot.GetLeadingTrivia();

                foreach (var trivia in leadingTrivia)
                {
                    if (!isComment(trivia))
                    {
                        continue;
                    }

                    var text = trivia.ToString();

                    // Check to see if the text of the comment contains an auto generated comment.
                    foreach (var autoGenerated in s_autoGeneratedStrings)
                    {
                        if (text.Contains(autoGenerated))
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        internal static bool? GetIsGeneratedCodeFromOptions(AnalyzerConfigOptions options)
        {
            // Check for explicit user configuration for generated code.
            //     generated_code = true | false
            if (options.TryGetValue("generated_code", out var optionValue) &&
                bool.TryParse(optionValue, out var boolValue))
            {
                return boolValue;
            }

            // Either no explicit user configuration or we don't recognize the option value.
            return null;
        }
    }
}
