// 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 System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using InternalSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax;

namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// The parsed representation of a C# source document.
    /// </summary>
    public abstract partial class CSharpSyntaxTree : SyntaxTree
    {
        internal static readonly SyntaxTree Dummy = new DummySyntaxTree();

        private InternalSyntax.DirectiveStack _lazyDirectives;

        /// <summary>
        /// Stores positions where preprocessor state changes. Sorted by position.
        /// The updated state can be found in <see cref="_preprocessorStates"/> array at the same index.
        /// </summary>
        private ImmutableArray<int> _preprocessorStateChangePositions;

        /// <summary>
        /// Preprocessor states corresponding to positions in <see cref="_preprocessorStateChangePositions"/>.
        /// </summary>
        private ImmutableArray<InternalSyntax.DirectiveStack> _preprocessorStates;

        public CSharpSyntaxTree()
            : this(directives: default)
        {
        }

        internal CSharpSyntaxTree(InternalSyntax.DirectiveStack directives)
        {
            _lazyDirectives = directives;
        }

        /// <summary>
        /// The options used by the parser to produce the syntax tree.
        /// </summary>
        public new abstract CSharpParseOptions Options { get; }

        // REVIEW: I would prefer to not expose CloneAsRoot and make the functionality
        // internal to CaaS layer, to ensure that for a given SyntaxTree there can not
        // be multiple trees claiming to be its children.
        //
        // However, as long as we provide GetRoot extensibility point on SyntaxTree
        // the guarantee above cannot be implemented and we have to provide some way for
        // creating root nodes.
        //
        // Therefore I place CloneAsRoot API on SyntaxTree and make it protected to
        // at least limit its visibility to SyntaxTree extenders.

        /// <summary>
        /// Produces a clone of a <see cref="CSharpSyntaxNode"/> which will have current syntax tree as its parent.
        ///
        /// Caller must guarantee that if the same instance of <see cref="CSharpSyntaxNode"/> makes multiple calls
        /// to this function, only one result is observable.
        /// </summary>
        /// <typeparam name="T">Type of the syntax node.</typeparam>
        /// <param name="node">The original syntax node.</param>
        /// <returns>A clone of the original syntax node that has current <see cref="CSharpSyntaxTree"/> as its parent.</returns>
        protected T CloneNodeAsRoot<T>(T node) where T : CSharpSyntaxNode
        {
            return CSharpSyntaxNode.CloneNodeAsRoot(node, this);
        }

        /// <summary>
        /// Gets the root node of the syntax tree.
        /// </summary>
        public new abstract CSharpSyntaxNode GetRoot(CancellationToken cancellationToken = default);

        /// <summary>
        /// Gets the root node of the syntax tree if it is already available.
        /// </summary>
        public abstract bool TryGetRoot([NotNullWhen(true)] out CSharpSyntaxNode? root);

        /// <summary>
        /// Gets the root node of the syntax tree asynchronously.
        /// </summary>
        /// <remarks>
        /// By default, the work associated with this method will be executed immediately on the current thread.
        /// Implementations that wish to schedule this work differently should override <see cref="GetRootAsync(CancellationToken)"/>.
        /// </remarks>
        public new virtual Task<CSharpSyntaxNode> GetRootAsync(CancellationToken cancellationToken = default)
        {
            return Task.FromResult(this.TryGetRoot(out CSharpSyntaxNode? node) ? node : this.GetRoot(cancellationToken));
        }

        /// <summary>
        /// Gets the root of the syntax tree statically typed as <see cref="CompilationUnitSyntax"/>.
        /// </summary>
        /// <remarks>
        /// Ensure that <see cref="SyntaxTree.HasCompilationUnitRoot"/> is true for this tree prior to invoking this method.
        /// </remarks>
        /// <exception cref="InvalidCastException">Throws this exception if <see cref="SyntaxTree.HasCompilationUnitRoot"/> is false.</exception>
        public CompilationUnitSyntax GetCompilationUnitRoot(CancellationToken cancellationToken = default)
        {
            return (CompilationUnitSyntax)this.GetRoot(cancellationToken);
        }

        /// <summary>
        /// Determines if two trees are the same, disregarding trivia differences.
        /// </summary>
        /// <param name="tree">The tree to compare against.</param>
        /// <param name="topLevel">
        /// If true then the trees are equivalent if the contained nodes and tokens declaring metadata visible symbolic information are equivalent,
        /// ignoring any differences of nodes inside method bodies or initializer expressions, otherwise all nodes and tokens must be equivalent.
        /// </param>
        public override bool IsEquivalentTo(SyntaxTree tree, bool topLevel = false)
        {
            return SyntaxFactory.AreEquivalent(this, tree, topLevel);
        }

        internal bool HasReferenceDirectives
        {
            get
            {
                Debug.Assert(HasCompilationUnitRoot);

                return Options.Kind == SourceCodeKind.Script && GetCompilationUnitRoot().HasReferenceDirectives;
            }
        }

        internal bool HasReferenceOrLoadDirectives
        {
            get
            {
                Debug.Assert(HasCompilationUnitRoot);

                if (Options.Kind == SourceCodeKind.Script)
                {
                    var compilationUnitRoot = GetCompilationUnitRoot();
                    return compilationUnitRoot.HasReferenceDirectives || compilationUnitRoot.HasLoadDirectives;
                }

                return false;
            }
        }

        #region Preprocessor Symbols

        internal InternalSyntax.DirectiveStack GetDirectives()
        {
            if (_lazyDirectives.IsNull)
            {
                InternalSyntax.DirectiveStack.InterlockedInitialize(ref _lazyDirectives, GetRoot().CsGreen.ApplyDirectives(InternalSyntax.DirectiveStack.Empty));
            }

            Debug.Assert(!_lazyDirectives.IsNull);

            return _lazyDirectives;
        }

        internal bool IsAnyPreprocessorSymbolDefined(ImmutableArray<string> conditionalSymbols)
        {
            var directives = GetDirectives();

            foreach (string conditionalSymbol in conditionalSymbols)
            {
                if (IsPreprocessorSymbolDefined(directives, conditionalSymbol))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsPreprocessorSymbolDefined(InternalSyntax.DirectiveStack directives, string symbolName)
        {
            switch (directives.IsDefined(symbolName))
            {
                case InternalSyntax.DefineState.Defined:
                    return true;
                case InternalSyntax.DefineState.Undefined:
                    return false;
                default:
                    return this.Options.PreprocessorSymbols.Contains(symbolName);
            }
        }

        internal bool IsPreprocessorSymbolDefined(string symbolName, int position)
        {
            if (_preprocessorStateChangePositions.IsDefault)
            {
                BuildPreprocessorStateChangeMap();
            }

            int searchResult = _preprocessorStateChangePositions.BinarySearch(position);
            InternalSyntax.DirectiveStack directives;

            if (searchResult < 0)
            {
                searchResult = (~searchResult) - 1;

                if (searchResult >= 0)
                {
                    directives = _preprocessorStates[searchResult];
                }
                else
                {
                    directives = InternalSyntax.DirectiveStack.Empty;
                }
            }
            else
            {
                directives = _preprocessorStates[searchResult];
            }

            return IsPreprocessorSymbolDefined(directives, symbolName);
        }

        private void BuildPreprocessorStateChangeMap()
        {
            InternalSyntax.DirectiveStack currentState = InternalSyntax.DirectiveStack.Empty;
            var positions = ArrayBuilder<int>.GetInstance();
            var states = ArrayBuilder<InternalSyntax.DirectiveStack>.GetInstance();

            foreach (DirectiveTriviaSyntax directive in this.GetRoot().GetDirectives(d =>
                                                                        {
                                                                            switch (d.Kind())
                                                                            {
                                                                                case SyntaxKind.IfDirectiveTrivia:
                                                                                case SyntaxKind.ElifDirectiveTrivia:
                                                                                case SyntaxKind.ElseDirectiveTrivia:
                                                                                case SyntaxKind.EndIfDirectiveTrivia:
                                                                                case SyntaxKind.DefineDirectiveTrivia:
                                                                                case SyntaxKind.UndefDirectiveTrivia:
                                                                                    return true;
                                                                                default:
                                                                                    return false;
                                                                            }
                                                                        }))
            {
                currentState = directive.ApplyDirectives(currentState);

                switch (directive.Kind())
                {
                    case SyntaxKind.IfDirectiveTrivia:
                        // #if directive doesn't affect the set of defined/undefined symbols
                        break;

                    case SyntaxKind.ElifDirectiveTrivia:
                        states.Add(currentState);
                        positions.Add(((ElifDirectiveTriviaSyntax)directive).ElifKeyword.SpanStart);
                        break;

                    case SyntaxKind.ElseDirectiveTrivia:
                        states.Add(currentState);
                        positions.Add(((ElseDirectiveTriviaSyntax)directive).ElseKeyword.SpanStart);
                        break;

                    case SyntaxKind.EndIfDirectiveTrivia:
                        states.Add(currentState);
                        positions.Add(((EndIfDirectiveTriviaSyntax)directive).EndIfKeyword.SpanStart);
                        break;

                    case SyntaxKind.DefineDirectiveTrivia:
                        states.Add(currentState);
                        positions.Add(((DefineDirectiveTriviaSyntax)directive).Name.SpanStart);
                        break;

                    case SyntaxKind.UndefDirectiveTrivia:
                        states.Add(currentState);
                        positions.Add(((UndefDirectiveTriviaSyntax)directive).Name.SpanStart);
                        break;

                    default:
                        throw ExceptionUtilities.UnexpectedValue(directive.Kind());
                }
            }

#if DEBUG
            int currentPos = -1;
            foreach (int pos in positions)
            {
                Debug.Assert(currentPos < pos);
                currentPos = pos;
            }
#endif

            ImmutableInterlocked.InterlockedInitialize(ref _preprocessorStates, states.ToImmutableAndFree());
            ImmutableInterlocked.InterlockedInitialize(ref _preprocessorStateChangePositions, positions.ToImmutableAndFree());
        }

        #endregion

        #region Factories

        // The overload that has more parameters is itself obsolete, as an intentional break to allow future
        // expansion
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads.

        /// <summary>
        /// Creates a new syntax tree from a syntax node.
        /// </summary>
        public static SyntaxTree Create(CSharpSyntaxNode root, CSharpParseOptions? options = null, string? path = "", Encoding? encoding = null)
        {
#pragma warning disable CS0618 // We are calling into the obsolete member as that's the one that still does the real work
            return Create(root, options, path, encoding, diagnosticOptions: null);
#pragma warning restore CS0618
        }

#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads.

        /// <summary>
        /// Creates a new syntax tree from a syntax node.
        /// </summary>
        /// <param name="diagnosticOptions">An obsolete parameter. Diagnostic options should now be passed with <see cref="CompilationOptions.SyntaxTreeOptionsProvider"/></param>
        /// <param name="isGeneratedCode">An obsolete parameter. It is unused.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions and isGeneratedCode parameters are obsolete due to performance problems, if you are using them use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree Create(
            CSharpSyntaxNode root,
            CSharpParseOptions? options,
            string? path,
            Encoding? encoding,
            // obsolete parameter -- unused
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions,
            // obsolete parameter -- unused
            bool? isGeneratedCode)
        {
            if (root == null)
            {
                throw new ArgumentNullException(nameof(root));
            }

            return new ParsedSyntaxTree(
                textOpt: null,
                encodingOpt: encoding,
                checksumAlgorithm: SourceHashAlgorithm.Sha1,
                path: path,
                options: options ?? CSharpParseOptions.Default,
                root: root,
                directives: default,
                diagnosticOptions,
                cloneRoot: true);
        }

        internal static SyntaxTree Create(
            CSharpSyntaxNode root,
            CSharpParseOptions options,
            string? path,
            Encoding? encoding,
            SourceHashAlgorithm checksumAlgorithm)
        {
            return new ParsedSyntaxTree(
                textOpt: null,
                encodingOpt: encoding,
                checksumAlgorithm: checksumAlgorithm,
                path: path,
                options: options,
                root: root,
                directives: default,
                diagnosticOptions: null,
                cloneRoot: true);
        }

        /// <summary>
        /// Creates a new syntax tree from a syntax node with text that should correspond to the syntax node.
        /// </summary>
        /// <remarks>This is used by the ExpressionEvaluator.</remarks>
        internal static SyntaxTree CreateForDebugger(CSharpSyntaxNode root, SourceText text, CSharpParseOptions options)
        {
            Debug.Assert(root != null);

            return new DebuggerSyntaxTree(root, text, options);
        }

        /// <summary>
        /// <para>
        /// Internal helper for <see cref="CSharpSyntaxNode"/> class to create a new syntax tree rooted at the given root node.
        /// This method does not create a clone of the given root, but instead preserves it's reference identity.
        /// </para>
        /// <para>NOTE: This method is only intended to be used from <see cref="CSharpSyntaxNode.SyntaxTree"/> property and <c>SyntaxFactory.Parse*</c> methods.</para>
        /// <para>NOTE: Do not use this method elsewhere, instead use <see cref="Create(CSharpSyntaxNode, CSharpParseOptions, string, Encoding)"/> method for creating a syntax tree.</para>
        /// </summary>
        internal static SyntaxTree CreateWithoutClone(CSharpSyntaxNode root, CSharpParseOptions options)
        {
            Debug.Assert(root != null);

            return new ParsedSyntaxTree(
                textOpt: null,
                encodingOpt: null,
                checksumAlgorithm: SourceHashAlgorithm.Sha1,
                path: "",
                options: options,
                root: root,
                directives: default,
                diagnosticOptions: null,
                cloneRoot: false);
        }

        /// <summary>
        /// Produces a syntax tree by parsing the source text lazily. The syntax tree is realized when
        /// <see cref="CSharpSyntaxTree.GetRoot(CancellationToken)"/> is called.
        /// </summary>
        internal static SyntaxTree ParseTextLazy(
            SourceText text,
            CSharpParseOptions? options = null,
            string path = "")
        {
            return new LazySyntaxTree(text, options ?? CSharpParseOptions.Default, path, diagnosticOptions: null);
        }

        // The overload that has more parameters is itself obsolete, as an intentional break to allow future
        // expansion
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads.

        /// <summary>
        /// Produces a syntax tree by parsing the source text.
        /// </summary>
        public static SyntaxTree ParseText(
            string text,
            CSharpParseOptions? options = null,
            string path = "",
            Encoding? encoding = null,
            CancellationToken cancellationToken = default)
        {
#pragma warning disable CS0618 // We are calling into the obsolete member as that's the one that still does the real work
            return ParseText(text, options, path, encoding, diagnosticOptions: null, cancellationToken);
#pragma warning restore CS0618
        }

#pragma warning restore RS0027

        /// <summary>
        /// Produces a syntax tree by parsing the source text.
        /// </summary>
        /// <param name="diagnosticOptions">An obsolete parameter. Diagnostic options should now be passed with <see cref="CompilationOptions.SyntaxTreeOptionsProvider"/></param>
        /// <param name="isGeneratedCode">An obsolete parameter. It is unused.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions and isGeneratedCode parameters are obsolete due to performance problems, if you are using them use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree ParseText(
            string text,
            CSharpParseOptions? options,
            string path,
            Encoding? encoding,
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions,
            bool? isGeneratedCode,
            CancellationToken cancellationToken)
        {
            return ParseText(SourceText.From(text, encoding, SourceHashAlgorithm.Sha1), options, path, diagnosticOptions, isGeneratedCode, cancellationToken);
        }

        // The overload that has more parameters is itself obsolete, as an intentional break to allow future
        // expansion
#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads.

        /// <summary>
        /// Produces a syntax tree by parsing the source text.
        /// </summary>
        public static SyntaxTree ParseText(
            SourceText text,
            CSharpParseOptions? options = null,
            string path = "",
            CancellationToken cancellationToken = default)
        {
#pragma warning disable CS0618 // We are calling into the obsolete member as that's the one that still does the real work
            return ParseText(text, options, path, diagnosticOptions: null, cancellationToken);
#pragma warning restore CS0618
        }

#pragma warning restore RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads.

        /// <summary>
        /// Produces a syntax tree by parsing the source text.
        /// </summary>
        /// <param name="diagnosticOptions">An obsolete parameter. Diagnostic options should now be passed with <see cref="CompilationOptions.SyntaxTreeOptionsProvider"/></param>
        /// <param name="isGeneratedCode">An obsolete parameter. It is unused.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions and isGeneratedCode parameters are obsolete due to performance problems, if you are using them use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree ParseText(
            SourceText text,
            CSharpParseOptions? options,
            string path,
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions,
            bool? isGeneratedCode,
            CancellationToken cancellationToken)
        {
            if (text == null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            options = options ?? CSharpParseOptions.Default;

            using var lexer = new InternalSyntax.Lexer(text, options);
            using var parser = new InternalSyntax.LanguageParser(lexer, oldTree: null, changes: null, cancellationToken: cancellationToken);
            var compilationUnit = (CompilationUnitSyntax)parser.ParseCompilationUnit().CreateRed();
            var tree = new ParsedSyntaxTree(
                text,
                text.Encoding,
                text.ChecksumAlgorithm,
                path,
                options,
                compilationUnit,
                parser.Directives,
                diagnosticOptions: diagnosticOptions,
                cloneRoot: false);
            Debug.Assert(compilationUnit._syntaxTree == null);
            compilationUnit._syntaxTree = tree;
            tree.VerifySource();
            return tree;
        }

        #endregion

        #region Changes

        /// <summary>
        /// Creates a new syntax based off this tree using a new source text.
        /// </summary>
        /// <remarks>
        /// If the new source text is a minor change from the current source text an incremental parse will occur
        /// reusing most of the current syntax tree internal data.  Otherwise, a full parse will occur using the new
        /// source text.
        /// </remarks>
        public override SyntaxTree WithChangedText(SourceText newText)
        {
            // try to find the changes between the old text and the new text.
            if (this.TryGetText(out SourceText? oldText))
            {
                var changes = newText.GetChangeRanges(oldText);

                if (changes.Count == 0 && newText == oldText)
                {
                    return this;
                }

                return this.WithChanges(newText, changes);
            }

            // if we do not easily know the old text, then specify entire text as changed so we do a full reparse.
            return this.WithChanges(newText, new[] { new TextChangeRange(new TextSpan(0, this.Length), newText.Length) });
        }

        private SyntaxTree WithChanges(SourceText newText, IReadOnlyList<TextChangeRange> changes)
        {
            if (changes == null)
            {
                throw new ArgumentNullException(nameof(changes));
            }

            IReadOnlyList<TextChangeRange>? workingChanges = changes;
            CSharpSyntaxTree? oldTree = this;

            // if changes is entire text do a full reparse
            if (workingChanges.Count == 1 && workingChanges[0].Span == new TextSpan(0, this.Length) && workingChanges[0].NewLength == newText.Length)
            {
                // parser will do a full parse if we give it no changes
                workingChanges = null;
                oldTree = null;
            }

            using var lexer = new InternalSyntax.Lexer(newText, this.Options);
            using var parser = new InternalSyntax.LanguageParser(lexer, oldTree?.GetRoot(), workingChanges);

            var compilationUnit = (CompilationUnitSyntax)parser.ParseCompilationUnit().CreateRed();
            var tree = new ParsedSyntaxTree(
                newText,
                newText.Encoding,
                newText.ChecksumAlgorithm,
                FilePath,
                Options,
                compilationUnit,
                parser.Directives,
#pragma warning disable CS0618
                DiagnosticOptions,
#pragma warning restore CS0618
                cloneRoot: true);
            tree.VerifySource(changes);
            return tree;
        }

        /// <summary>
        /// Produces a pessimistic list of spans that denote the regions of text in this tree that
        /// are changed from the text of the old tree.
        /// </summary>
        /// <param name="oldTree">The old tree. Cannot be <c>null</c>.</param>
        /// <remarks>The list is pessimistic because it may claim more or larger regions than actually changed.</remarks>
        public override IList<TextSpan> GetChangedSpans(SyntaxTree oldTree)
        {
            if (oldTree == null)
            {
                throw new ArgumentNullException(nameof(oldTree));
            }

            return SyntaxDiffer.GetPossiblyDifferentTextSpans(oldTree, this);
        }

        /// <summary>
        /// Gets a list of text changes that when applied to the old tree produce this tree.
        /// </summary>
        /// <param name="oldTree">The old tree. Cannot be <c>null</c>.</param>
        /// <remarks>The list of changes may be different than the original changes that produced this tree.</remarks>
        public override IList<TextChange> GetChanges(SyntaxTree oldTree)
        {
            if (oldTree == null)
            {
                throw new ArgumentNullException(nameof(oldTree));
            }

            return SyntaxDiffer.GetTextChanges(oldTree, this);
        }

        #endregion

        #region LinePositions and Locations

        private CSharpLineDirectiveMap GetDirectiveMap()
        {
            if (_lazyLineDirectiveMap == null)
            {
                // Create the line directive map on demand.
                Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
            }

            return _lazyLineDirectiveMap;
        }

        /// <summary>
        /// Gets the location in terms of path, line and column for a given span.
        /// </summary>
        /// <param name="span">Span within the tree.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>
        /// <see cref="FileLinePositionSpan"/> that contains path, line and column information.
        /// </returns>
        /// <remarks>The values are not affected by line mapping directives (<c>#line</c>).</remarks>
        public override FileLinePositionSpan GetLineSpan(TextSpan span, CancellationToken cancellationToken = default)
            => new(FilePath, GetLinePosition(span.Start, cancellationToken), GetLinePosition(span.End, cancellationToken));

        /// <summary>
        /// Gets the location in terms of path, line and column after applying source line mapping directives (<c>#line</c>).
        /// </summary>
        /// <param name="span">Span within the tree.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>
        /// <para>A valid <see cref="FileLinePositionSpan"/> that contains path, line and column information.</para>
        /// <para>
        /// If the location path is mapped the resulting path is the path specified in the corresponding <c>#line</c>,
        /// otherwise it's <see cref="SyntaxTree.FilePath"/>.
        /// </para>
        /// <para>
        /// A location path is considered mapped if the first <c>#line</c> directive that precedes it and that
        /// either specifies an explicit file path or is <c>#line default</c> exists and specifies an explicit path.
        /// </para>
        /// </returns>
        public override FileLinePositionSpan GetMappedLineSpan(TextSpan span, CancellationToken cancellationToken = default)
            => GetDirectiveMap().TranslateSpan(GetText(cancellationToken), this.FilePath, span);

        /// <inheritdoc/>
        public override LineVisibility GetLineVisibility(int position, CancellationToken cancellationToken = default)
            => GetDirectiveMap().GetLineVisibility(GetText(cancellationToken), position);

        /// <inheritdoc/>
        public override IEnumerable<LineMapping> GetLineMappings(CancellationToken cancellationToken = default)
        {
            var map = GetDirectiveMap();
            Debug.Assert(map.Entries.Length >= 1);
            return (map.Entries.Length == 1) ? Array.Empty<LineMapping>() : map.GetLineMappings(GetText(cancellationToken).Lines);
        }

        /// <summary>
        /// Gets a <see cref="FileLinePositionSpan"/> for a <see cref="TextSpan"/>. FileLinePositionSpans are used
        /// primarily for diagnostics and source locations.
        /// </summary>
        /// <param name="span">The source <see cref="TextSpan" /> to convert.</param>
        /// <param name="isHiddenPosition">When the method returns, contains a boolean value indicating whether this span is considered hidden or not.</param>
        /// <returns>A resulting <see cref="FileLinePositionSpan"/>.</returns>
        internal override FileLinePositionSpan GetMappedLineSpanAndVisibility(TextSpan span, out bool isHiddenPosition)
            => GetDirectiveMap().TranslateSpanAndVisibility(GetText(), FilePath, span, out isHiddenPosition);

        /// <summary>
        /// Gets a boolean value indicating whether there are any hidden regions in the tree.
        /// </summary>
        /// <returns>True if there is at least one hidden region.</returns>
        public override bool HasHiddenRegions()
            => GetDirectiveMap().HasAnyHiddenRegions();

        /// <summary>
        /// Given the error code and the source location, get the warning state based on <c>#pragma warning</c> directives.
        /// </summary>
        /// <param name="id">Error code.</param>
        /// <param name="position">Source location.</param>
        internal PragmaWarningState GetPragmaDirectiveWarningState(string id, int position)
        {
            if (_lazyPragmaWarningStateMap == null)
            {
                // Create the warning state map on demand.
                Interlocked.CompareExchange(ref _lazyPragmaWarningStateMap, new CSharpPragmaWarningStateMap(this), null);
            }

            return _lazyPragmaWarningStateMap.GetWarningState(id, position);
        }

        private NullableContextStateMap GetNullableContextStateMap()
            // Create the #nullable directive map on demand.
            => _lazyNullableContextStateMap.Initialize(static @this => NullableContextStateMap.Create(@this), this);

        internal NullableContextState GetNullableContextState(int position)
            => GetNullableContextStateMap().GetContextState(position);

        internal bool? IsNullableAnalysisEnabled(TextSpan span) => GetNullableContextStateMap().IsNullableAnalysisEnabled(span);

        internal bool IsGeneratedCode(SyntaxTreeOptionsProvider? provider, CancellationToken cancellationToken)
        {
            return provider?.IsGenerated(this, cancellationToken) switch
            {
                null or GeneratedKind.Unknown => isGeneratedHeuristic(),
                GeneratedKind kind => kind != GeneratedKind.NotGenerated
            };

            bool isGeneratedHeuristic()
            {
                if (_lazyIsGeneratedCode == GeneratedKind.Unknown)
                {
                    // Create the generated code status on demand
                    bool isGenerated = GeneratedCodeUtilities.IsGeneratedCode(
                            this,
                            isComment: trivia => trivia.Kind() == SyntaxKind.SingleLineCommentTrivia || trivia.Kind() == SyntaxKind.MultiLineCommentTrivia,
                            cancellationToken: default);
                    _lazyIsGeneratedCode = isGenerated ? GeneratedKind.MarkedGenerated : GeneratedKind.NotGenerated;
                }

                return _lazyIsGeneratedCode == GeneratedKind.MarkedGenerated;
            }
        }

        private CSharpLineDirectiveMap? _lazyLineDirectiveMap;
        private CSharpPragmaWarningStateMap? _lazyPragmaWarningStateMap;
        private SingleInitNullable<NullableContextStateMap> _lazyNullableContextStateMap;

        private GeneratedKind _lazyIsGeneratedCode = GeneratedKind.Unknown;

        private LinePosition GetLinePosition(int position, CancellationToken cancellationToken)
            => GetText(cancellationToken).Lines.GetLinePosition(position);

        /// <summary>
        /// Gets a <see cref="Location"/> for the specified text <paramref name="span"/>.
        /// </summary>
        public override Location GetLocation(TextSpan span)
        {
            return new SourceLocation(this, span);
        }

        #endregion

        #region Diagnostics

        /// <summary>
        /// Gets a list of all the diagnostics in the sub tree that has the specified node as its root.
        /// </summary>
        /// <remarks>
        /// This method does not filter diagnostics based on <c>#pragma</c>s and compiler options
        /// like /nowarn, /warnaserror etc.
        /// </remarks>
        public override IEnumerable<Diagnostic> GetDiagnostics(SyntaxNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            return GetDiagnostics(node.Green, node.Position);
        }

        private IEnumerable<Diagnostic> GetDiagnostics(GreenNode greenNode, int position)
        {
            if (greenNode == null)
            {
                throw new InvalidOperationException();
            }

            if (greenNode.ContainsDiagnostics)
            {
                return SyntaxTreeDiagnosticEnumerator.EnumerateDiagnostics(this, greenNode, position);
            }

            return SpecializedCollections.EmptyEnumerable<Diagnostic>();
        }

        /// <summary>
        /// Gets a list of all the diagnostics associated with the token and any related trivia.
        /// </summary>
        /// <remarks>
        /// This method does not filter diagnostics based on <c>#pragma</c>s and compiler options
        /// like /nowarn, /warnaserror etc.
        /// </remarks>
        public override IEnumerable<Diagnostic> GetDiagnostics(SyntaxToken token)
        {
            if (token.Node == null)
            {
                throw new InvalidOperationException();
            }
            return GetDiagnostics(token.Node, token.Position);
        }

        /// <summary>
        /// Gets a list of all the diagnostics associated with the trivia.
        /// </summary>
        /// <remarks>
        /// This method does not filter diagnostics based on <c>#pragma</c>s and compiler options
        /// like /nowarn, /warnaserror etc.
        /// </remarks>
        public override IEnumerable<Diagnostic> GetDiagnostics(SyntaxTrivia trivia)
        {
            if (trivia.UnderlyingNode == null)
            {
                throw new InvalidOperationException();
            }
            return GetDiagnostics(trivia.UnderlyingNode, trivia.Position);
        }

        /// <summary>
        /// Gets a list of all the diagnostics in either the sub tree that has the specified node as its root or
        /// associated with the token and its related trivia.
        /// </summary>
        /// <remarks>
        /// This method does not filter diagnostics based on <c>#pragma</c>s and compiler options
        /// like /nowarn, /warnaserror etc.
        /// </remarks>
        public override IEnumerable<Diagnostic> GetDiagnostics(SyntaxNodeOrToken nodeOrToken)
        {
            if (nodeOrToken.UnderlyingNode == null)
            {
                throw new InvalidOperationException();
            }
            return GetDiagnostics(nodeOrToken.UnderlyingNode, nodeOrToken.Position);
        }

        /// <summary>
        /// Gets a list of all the diagnostics in the syntax tree.
        /// </summary>
        /// <remarks>
        /// This method does not filter diagnostics based on <c>#pragma</c>s and compiler options
        /// like /nowarn, /warnaserror etc.
        /// </remarks>
        public override IEnumerable<Diagnostic> GetDiagnostics(CancellationToken cancellationToken = default)
        {
            return this.GetDiagnostics(this.GetRoot(cancellationToken));
        }

        #endregion

        #region SyntaxTree

        protected override SyntaxNode GetRootCore(CancellationToken cancellationToken)
        {
            return this.GetRoot(cancellationToken);
        }

        protected override async Task<SyntaxNode> GetRootAsyncCore(CancellationToken cancellationToken)
        {
            return await this.GetRootAsync(cancellationToken).ConfigureAwait(false);
        }

        protected override bool TryGetRootCore([NotNullWhen(true)] out SyntaxNode? root)
        {
            if (this.TryGetRoot(out CSharpSyntaxNode? node))
            {
                root = node;
                return true;
            }
            else
            {
                root = null;
                return false;
            }
        }

        protected override ParseOptions OptionsCore
        {
            get
            {
                return this.Options;
            }
        }

        #endregion

        // 3.3 BACK COMPAT OVERLOAD -- DO NOT MODIFY
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions parameter is obsolete due to performance problems, if you are passing non-null use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree ParseText(
            SourceText text,
            CSharpParseOptions? options,
            string path,
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions,
            CancellationToken cancellationToken)
            => ParseText(text, options, path, diagnosticOptions, isGeneratedCode: null, cancellationToken);

        // 3.3 BACK COMPAT OVERLOAD -- DO NOT MODIFY
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions parameter is obsolete due to performance problems, if you are passing non-null use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree ParseText(
            string text,
            CSharpParseOptions? options,
            string path,
            Encoding? encoding,
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions,
            CancellationToken cancellationToken)
            => ParseText(SourceText.From(text, encoding, SourceHashAlgorithm.Sha1), options, path, diagnosticOptions, isGeneratedCode: null, cancellationToken);

        // 3.3 BACK COMPAT OVERLOAD -- DO NOT MODIFY
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("The diagnosticOptions parameter is obsolete due to performance problems, if you are passing non-null use CompilationOptions.SyntaxTreeOptionsProvider instead", error: false)]
        public static SyntaxTree Create(
            CSharpSyntaxNode root,
            CSharpParseOptions? options,
            string? path,
            Encoding? encoding,
            ImmutableDictionary<string, ReportDiagnostic>? diagnosticOptions)
            => Create(root, options, path, encoding, diagnosticOptions, isGeneratedCode: null);

        /// <summary>
        /// This is ONLY used for debugging purpose
        /// </summary>
        internal string Dump()
        {
            return this.GetRoot().Dump();
        }
    }
}
