// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel.Extenders;
using Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel.MethodXml;
using Microsoft.VisualStudio.LanguageServices.CSharp.Utilities;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel;

using static CSharpSyntaxTokens;

internal sealed partial class CSharpCodeModelService : AbstractCodeModelService
{
    private static readonly SyntaxTree s_emptyTree = SyntaxFactory.ParseSyntaxTree(SourceText.From("", encoding: null, SourceHashAlgorithms.Default));

    internal CSharpCodeModelService(
        HostLanguageServices languageServiceProvider,
        EditorOptionsService editorOptionsService,
        IEnumerable<IRefactorNotifyService> refactorNotifyServices,
        IThreadingContext threadingContext)
        : base(languageServiceProvider,
               editorOptionsService,
               refactorNotifyServices,
               BlankLineInGeneratedMethodFormattingRule.Instance,
               EndRegionFormattingRule.Instance,
               threadingContext)
    {
    }

    private static readonly SymbolDisplayFormat s_codeTypeRefAsFullNameFormat =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.ExpandNullable);

    private static readonly SymbolDisplayFormat s_codeTypeRefAsStringFormat =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

    private static readonly SymbolDisplayFormat s_externalNameFormat =
        new(
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers,
            parameterOptions: SymbolDisplayParameterOptions.IncludeName);

    private static readonly SymbolDisplayFormat s_externalFullNameFormat =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            memberOptions: SymbolDisplayMemberOptions.IncludeContainingType | SymbolDisplayMemberOptions.IncludeExplicitInterface,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers,
            parameterOptions: SymbolDisplayParameterOptions.IncludeName);

    private static readonly SymbolDisplayFormat s_setTypeFormat =
        new(
            globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

    private static bool IsNameableNode(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.DelegateDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.EnumDeclaration:
            case SyntaxKind.EnumMemberDeclaration:
            case SyntaxKind.EventDeclaration:
            case SyntaxKind.IndexerDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.PropertyDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.RecordStructDeclaration:
                return true;

            case SyntaxKind.VariableDeclarator:
                // Could be a regular field or an event field.
                return node.FirstAncestorOrSelf<BaseFieldDeclarationSyntax>() != null;

            default:
                return false;
        }
    }

    public override EnvDTE.vsCMElement GetElementKind(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
                return EnvDTE.vsCMElement.vsCMElementClass;

            default:
                Debug.Fail("Unsupported element kind: " + node.Kind());
                throw Exceptions.ThrowEInvalidArg();
        }
    }

    public override bool MatchesScope(SyntaxNode node, EnvDTE.vsCMElement scope)
    {
        switch (node.Kind())
        {
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementNamespace &&
                    node.Parent != null)
                {
                    return true;
                }

                break;

            case SyntaxKind.ClassDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementClass)
                {
                    return true;
                }

                break;

            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementFunction)
                {
                    return true;
                }

                break;

            case SyntaxKind.EnumMemberDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementVariable)
                {
                    return true;
                }

                break;

            case SyntaxKind.FieldDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementVariable)
                {
                    return true;
                }

                break;

            case SyntaxKind.EventDeclaration:
            case SyntaxKind.EventFieldDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementEvent)
                {
                    return true;
                }

                break;

            case SyntaxKind.PropertyDeclaration:
            case SyntaxKind.IndexerDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementProperty)
                {
                    return true;
                }

                break;

            case SyntaxKind.Attribute:
                if (scope == EnvDTE.vsCMElement.vsCMElementAttribute)
                {
                    return true;
                }

                break;

            case SyntaxKind.InterfaceDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementInterface)
                {
                    return true;
                }

                break;

            case SyntaxKind.DelegateDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementDelegate)
                {
                    return true;
                }

                break;

            case SyntaxKind.EnumDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementEnum)
                {
                    return true;
                }

                break;

            case SyntaxKind.StructDeclaration:
                if (scope == EnvDTE.vsCMElement.vsCMElementStruct)
                {
                    return true;
                }

                break;

            case SyntaxKind.UsingDirective:
                if (scope == EnvDTE.vsCMElement.vsCMElementImportStmt &&
                    ((UsingDirectiveSyntax)node).Name != null)
                {
                    return true;
                }

                break;

            case SyntaxKind.VariableDeclaration:
            case SyntaxKind.VariableDeclarator:
                // The parent of a VariableDeclarator might be an event or
                // a field declaration. If the parent matches the desired
                // scope, then this node matches the scope as well.
                return MatchesScope(node.Parent!, scope);

            case SyntaxKind.Parameter:
                if (scope == EnvDTE.vsCMElement.vsCMElementParameter)
                {
                    return true;
                }

                break;

            default:
                return false;
        }

        return false;
    }

    public override IEnumerable<SyntaxNode> GetOptionNodes(SyntaxNode parent)
        // Only VB has Option statements
        => [];

    public override IEnumerable<SyntaxNode> GetImportNodes(SyntaxNode parent)
        => parent switch
        {
            CompilationUnitSyntax compilationUnit => compilationUnit.Usings,
            BaseNamespaceDeclarationSyntax baseNamespace => baseNamespace.Usings,
            _ => [],
        };

    private static IEnumerable<SyntaxNode> GetAttributeNodes(SyntaxList<AttributeListSyntax> attributeDeclarationList)
    {
        foreach (var attributeDeclaration in attributeDeclarationList)
        {
            foreach (var attribute in attributeDeclaration.Attributes)
            {
                yield return attribute;
            }
        }
    }

    public override IEnumerable<SyntaxNode> GetAttributeNodes(SyntaxNode parent)
    {
        if (parent is CompilationUnitSyntax compilationUnit)
        {
            return GetAttributeNodes(compilationUnit.AttributeLists);
        }
        else if (parent is BaseTypeDeclarationSyntax baseType)
        {
            return GetAttributeNodes(baseType.AttributeLists);
        }
        else if (parent is BaseMethodDeclarationSyntax baseMethod)
        {
            return GetAttributeNodes(baseMethod.AttributeLists);
        }
        else if (parent is BasePropertyDeclarationSyntax baseProperty)
        {
            return GetAttributeNodes(baseProperty.AttributeLists);
        }
        else if (parent is BaseFieldDeclarationSyntax baseField)
        {
            return GetAttributeNodes(baseField.AttributeLists);
        }
        else if (parent is DelegateDeclarationSyntax delegateDecl)
        {
            return GetAttributeNodes(delegateDecl.AttributeLists);
        }
        else if (parent is EnumMemberDeclarationSyntax enumMember)
        {
            return GetAttributeNodes(enumMember.AttributeLists);
        }
        else if (parent is ParameterSyntax parameter)
        {
            return GetAttributeNodes(parameter.AttributeLists);
        }
        else if (parent is VariableDeclaratorSyntax or
                 VariableDeclarationSyntax)
        {
            return GetAttributeNodes(parent.Parent!);
        }
        else if (parent is AccessorDeclarationSyntax accessor)
        {
            return GetAttributeNodes(accessor.AttributeLists);
        }

        return [];
    }

    public override IEnumerable<SyntaxNode> GetAttributeArgumentNodes(SyntaxNode parent)
    {
        if (parent is AttributeSyntax attribute)
        {
            if (attribute.ArgumentList == null)
                return [];

            return attribute.ArgumentList.Arguments;
        }

        return [];
    }

    public override IEnumerable<SyntaxNode> GetInheritsNodes(SyntaxNode parent)
    {
        // Only VB has Inherits statements
        return [];
    }

    public override IEnumerable<SyntaxNode> GetImplementsNodes(SyntaxNode parent)
    {
        // Only VB has Implements statements
        return [];
    }

    private static bool IsContainerNode(SyntaxNode container)
        => container is CompilationUnitSyntax or
        BaseNamespaceDeclarationSyntax or
        BaseTypeDeclarationSyntax;

    private static bool IsNamespaceOrTypeDeclaration(SyntaxNode node)
        => node is BaseNamespaceDeclarationSyntax or
        BaseTypeDeclarationSyntax or
        DelegateDeclarationSyntax;

    private static IEnumerable<MemberDeclarationSyntax> GetChildMemberNodes(SyntaxNode container)
    {
        if (container is CompilationUnitSyntax compilationUnit)
        {
            foreach (var member in compilationUnit.Members)
            {
                if (IsNamespaceOrTypeDeclaration(member))
                {
                    yield return member;
                }
            }
        }
        else if (container is BaseNamespaceDeclarationSyntax namespaceDecl)
        {
            foreach (var member in namespaceDecl.Members)
            {
                if (IsNamespaceOrTypeDeclaration(member))
                {
                    yield return member;
                }
            }
        }
        else if (container is TypeDeclarationSyntax typeDecl)
        {
            foreach (var member in typeDecl.Members)
            {
                if (member.Kind() != SyntaxKind.NamespaceDeclaration)
                {
                    yield return member;
                }
            }
        }
        else if (container is EnumDeclarationSyntax enumDecl)
        {
            foreach (var member in enumDecl.Members)
            {
                yield return member;
            }
        }
    }

    private static bool NodeIsSupported(bool test, SyntaxNode node)
        => !test || IsNameableNode(node);

    /// <summary>
    /// Retrieves the members of a specified <paramref name="container"/> node. The members that are
    /// returned can be controlled by passing various parameters.
    /// </summary>
    /// <param name="container">The <see cref="SyntaxNode"/> from which to retrieve members.</param>
    /// <param name="includeSelf">If true, the container is returned as well.</param>
    /// <param name="recursive">If true, members are recursed to return descendant members as well
    /// as immediate children. For example, a namespace would return the namespaces and types within.
    /// However, if <paramref name="recursive"/> is true, members with the namespaces and types would
    /// also be returned.</param>
    /// <param name="logicalFields">If true, field declarations are broken into their respective declarators.
    /// For example, the field "int x, y" would return two declarators, one for x and one for y in place
    /// of the field.</param>
    /// <param name="onlySupportedNodes">If true, only members supported by Code Model are returned.</param>
    public override IEnumerable<SyntaxNode> GetMemberNodes(SyntaxNode container, bool includeSelf, bool recursive, bool logicalFields, bool onlySupportedNodes)
    {
        // Filter out all records from code model, they are not supported at all.
        return GetMemberNodesWorker(container, includeSelf, recursive, logicalFields, onlySupportedNodes).Where(t => t is not RecordDeclarationSyntax);
    }

    private IEnumerable<SyntaxNode> GetMemberNodesWorker(SyntaxNode container, bool includeSelf, bool recursive, bool logicalFields, bool onlySupportedNodes)
    {
        if (!IsContainerNode(container))
        {
            yield break;
        }

        if (includeSelf && NodeIsSupported(onlySupportedNodes, container))
        {
            yield return container;
        }

        foreach (var member in GetChildMemberNodes(container))
        {
            if (member is BaseFieldDeclarationSyntax baseField)
            {
                // For fields, the 'logical' and 'supported' flags are intrinsically tied.
                //   * If 'logical' is true, only declarators should be returned, regardless of the value of 'supported'.
                //   * If 'logical' is false, the field should only be returned if 'supported' is also false.

                if (logicalFields)
                {
                    foreach (var declarator in baseField.Declaration.Variables)
                    {
                        // We know that variable declarators are supported, so there's no need to check them here.
                        yield return declarator;
                    }
                }
                else if (!onlySupportedNodes)
                {
                    // Only return field declarations if the supported flag is false.
                    yield return member;
                }
            }
            else if (NodeIsSupported(onlySupportedNodes, member))
            {
                yield return member;
            }

            if (recursive && IsContainerNode(member))
            {
                foreach (var innerMember in GetMemberNodes(member, includeSelf: false, recursive: true, logicalFields: logicalFields, onlySupportedNodes: onlySupportedNodes))
                {
                    yield return innerMember;
                }
            }
        }
    }

    public override string Language
    {
        get { return EnvDTE.CodeModelLanguageConstants.vsCMLanguageCSharp; }
    }

    public override string AssemblyAttributeString
    {
        get
        {
            return "assembly";
        }
    }

    /// <summary>
    /// Do not use this method directly! Instead, go through <see cref="FileCodeModel.GetOrCreateCodeElement{T}(SyntaxNode)"/>
    /// </summary>
    public override EnvDTE.CodeElement CreateInternalCodeElement(
        CodeModelState state,
        FileCodeModel fileCodeModel,
        SyntaxNode node)
    {
        // Attributes, attribute arguments, parameters, imports directives, and
        // accessor functions do not have their own node keys. Rather, they are
        // based off their parents node keys and other data.
        switch (node.Kind())
        {
            case SyntaxKind.Attribute:
                return (EnvDTE.CodeElement)CreateInternalCodeAttribute(state, fileCodeModel, node);

            case SyntaxKind.AttributeArgument:
                return (EnvDTE.CodeElement)CreateInternalCodeAttributeArgument(state, fileCodeModel, (AttributeArgumentSyntax)node);

            case SyntaxKind.Parameter:
                return (EnvDTE.CodeElement)CreateInternalCodeParameter(state, fileCodeModel, (ParameterSyntax)node);

            case SyntaxKind.UsingDirective:
                return CreateInternalCodeImport(state, fileCodeModel, (UsingDirectiveSyntax)node);
        }

        if (IsAccessorNode(node))
        {
            return (EnvDTE.CodeElement)CreateInternalCodeAccessorFunction(state, fileCodeModel, node);
        }

        var nodeKey = GetNodeKey(node);

        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
                return (EnvDTE.CodeElement)CodeClass.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.InterfaceDeclaration:
                return (EnvDTE.CodeElement)CodeInterface.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.StructDeclaration:
                return (EnvDTE.CodeElement)CodeStruct.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.EnumDeclaration:
                return (EnvDTE.CodeElement)CodeEnum.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.EnumMemberDeclaration:
                return (EnvDTE.CodeElement)CodeVariable.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.DelegateDeclaration:
                return (EnvDTE.CodeElement)CodeDelegate.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
                return (EnvDTE.CodeElement)CodeFunction.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return (EnvDTE.CodeElement)CodeNamespace.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.PropertyDeclaration:
            case SyntaxKind.IndexerDeclaration:
                return (EnvDTE.CodeElement)CodeProperty.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.EventDeclaration:
                return (EnvDTE.CodeElement)CodeEvent.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
            case SyntaxKind.VariableDeclarator:
                var baseFieldDeclaration = node.FirstAncestorOrSelf<BaseFieldDeclarationSyntax>();
                if (baseFieldDeclaration != null)
                {
                    if (baseFieldDeclaration.Kind() == SyntaxKind.FieldDeclaration)
                    {
                        return (EnvDTE.CodeElement)CodeVariable.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
                    }
                    else if (baseFieldDeclaration.Kind() == SyntaxKind.EventFieldDeclaration)
                    {
                        return (EnvDTE.CodeElement)CodeEvent.Create(state, fileCodeModel, nodeKey, (int)node.Kind());
                    }
                }

                throw Exceptions.ThrowEUnexpected();
            default:
                throw new InvalidOperationException();
        }
    }

    public override EnvDTE.CodeElement CreateUnknownCodeElement(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return (EnvDTE.CodeElement)CodeNamespace.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            case SyntaxKind.ClassDeclaration:
                return (EnvDTE.CodeElement)CodeClass.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));
            case SyntaxKind.InterfaceDeclaration:
                return (EnvDTE.CodeElement)CodeInterface.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));
            case SyntaxKind.StructDeclaration:
                return (EnvDTE.CodeElement)CodeStruct.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));
            case SyntaxKind.EnumDeclaration:
                return (EnvDTE.CodeElement)CodeEnum.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));
            case SyntaxKind.DelegateDeclaration:
                return (EnvDTE.CodeElement)CodeDelegate.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
                return (EnvDTE.CodeElement)CodeFunction.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            case SyntaxKind.PropertyDeclaration:
            case SyntaxKind.IndexerDeclaration:
                return (EnvDTE.CodeElement)CodeProperty.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            case SyntaxKind.EventDeclaration:
                return (EnvDTE.CodeElement)CodeEvent.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            case SyntaxKind.VariableDeclarator:
                var eventFieldDeclaration = node.FirstAncestorOrSelf<EventFieldDeclarationSyntax>();
                if (eventFieldDeclaration != null)
                {
                    return (EnvDTE.CodeElement)CodeEvent.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));
                }

                goto case SyntaxKind.EnumMemberDeclaration;

            case SyntaxKind.EnumMemberDeclaration:
                return (EnvDTE.CodeElement)CodeVariable.CreateUnknown(state, fileCodeModel, node.RawKind, GetName(node));

            default:
                throw Exceptions.ThrowEUnexpected();
        }
    }

    public override EnvDTE.CodeElement CreateUnknownRootNamespaceCodeElement(CodeModelState state, FileCodeModel fileCodeModel)
        => (EnvDTE.CodeElement)CodeNamespace.CreateUnknown(state, fileCodeModel, (int)SyntaxKind.NamespaceDeclaration, string.Empty);

    public override EnvDTE.CodeTypeRef CreateCodeTypeRef(CodeModelState state, ProjectId projectId, object type)
    {
        var project = state.Workspace.CurrentSolution.GetProject(projectId);
        if (project == null)
        {
            throw Exceptions.ThrowEFail();
        }

        var compilation = project.GetRequiredCompilationAsync(CancellationToken.None).Result;

        if (type is EnvDTE.vsCMTypeRef or int)
        {
            var specialType = GetSpecialType((EnvDTE.vsCMTypeRef)type);
            return CodeTypeRef.Create(state, null, projectId, compilation.GetSpecialType(specialType));
        }

        string typeName;
        object? parent = null;

        if (type is EnvDTE.CodeType codeType)
        {
            typeName = codeType.FullName;
            parent = codeType;
        }
        else if (type is string str)
        {
            typeName = str;
        }
        else
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var typeSymbol = GetTypeSymbolFromFullName(typeName, compilation);
        if (typeSymbol == null)
        {
            throw Exceptions.ThrowEFail();
        }

        if (typeSymbol.TypeKind == TypeKind.Unknown)
        {
            if (SyntaxFactsService.IsValidIdentifier(typeSymbol.Name))
            {
                throw Exceptions.ThrowEFail();
            }
        }

        return CodeTypeRef.Create(state, parent, projectId, typeSymbol);
    }

    public override EnvDTE.vsCMTypeRef GetTypeKindForCodeTypeRef(ITypeSymbol typeSymbol)
    {
        if (typeSymbol.TypeKind == TypeKind.Array)
        {
            return EnvDTE.vsCMTypeRef.vsCMTypeRefArray;
        }
        else if (typeSymbol.TypeKind == TypeKind.Pointer)
        {
            return EnvDTE.vsCMTypeRef.vsCMTypeRefPointer;
        }
        else if (
            typeSymbol.TypeKind is TypeKind.Dynamic or
            TypeKind.Unknown)
        {
            return EnvDTE.vsCMTypeRef.vsCMTypeRefOther;
        }
        else
        {
            switch (typeSymbol.SpecialType)
            {
                case SpecialType.System_Void:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefVoid;
                case SpecialType.System_String:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefString;
                case SpecialType.System_Object:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefObject;
                case SpecialType.System_Char:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefChar;
                case SpecialType.System_Byte:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefByte;
                case SpecialType.System_Boolean:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefBool;
                case SpecialType.System_Int16:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefShort;
                case SpecialType.System_Int32:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefInt;
                case SpecialType.System_Int64:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefLong;
                case SpecialType.System_Single:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefFloat;
                case SpecialType.System_Double:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefDouble;
                case SpecialType.System_Decimal:
                    return EnvDTE.vsCMTypeRef.vsCMTypeRefDecimal;
                case SpecialType.System_UInt16:
                    return (EnvDTE.vsCMTypeRef)EnvDTE80.vsCMTypeRef2.vsCMTypeRefUnsignedShort;
                case SpecialType.System_UInt32:
                    return (EnvDTE.vsCMTypeRef)EnvDTE80.vsCMTypeRef2.vsCMTypeRefUnsignedInt;
                case SpecialType.System_UInt64:
                    return (EnvDTE.vsCMTypeRef)EnvDTE80.vsCMTypeRef2.vsCMTypeRefUnsignedLong;
                case SpecialType.System_SByte:
                    return (EnvDTE.vsCMTypeRef)EnvDTE80.vsCMTypeRef2.vsCMTypeRefSByte;
            }

            // Comment below is from native code
            // The following are not supported
            // vsCMTypeRefUnsignedChar - PT_UCHAR not in C# ??
            // vsCMTypeRefReference - C++ specific
            // vsCMTypeRefMCBoxedReference - C++ specific
        }

        if (typeSymbol.TypeKind is TypeKind.Class or
            TypeKind.Interface or
            TypeKind.Enum or
            TypeKind.Struct or
            TypeKind.Delegate)
        {
            return EnvDTE.vsCMTypeRef.vsCMTypeRefCodeType;
        }

        return EnvDTE.vsCMTypeRef.vsCMTypeRefOther;
    }

    public override string GetAsFullNameForCodeTypeRef(ITypeSymbol typeSymbol)
        => typeSymbol.ToDisplayString(s_codeTypeRefAsFullNameFormat);

    public override string GetAsStringForCodeTypeRef(ITypeSymbol typeSymbol)
        => typeSymbol.ToDisplayString(s_codeTypeRefAsStringFormat);

    public override bool IsParameterNode(SyntaxNode node)
        => node is ParameterSyntax;

    public override bool IsAttributeNode(SyntaxNode node)
        => node is AttributeSyntax;

    public override bool IsAttributeArgumentNode(SyntaxNode node)
        => node is AttributeArgumentSyntax;

    public override bool IsOptionNode(SyntaxNode node)
    {
        // Only VB implementation has Option statements
        return false;
    }

    public override bool IsImportNode(SyntaxNode node)
        => node is UsingDirectiveSyntax;

    [return: NotNullIfNotNull(nameof(name))]
    public override string? GetUnescapedName(string? name)
        => name is ['@', .. var rest] ? rest : name;

    public override string GetName(SyntaxNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.EnumDeclaration:
                return ((BaseTypeDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.DelegateDeclaration:
                return ((DelegateDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.MethodDeclaration:
                return ((MethodDeclarationSyntax)node).ExplicitInterfaceSpecifier?.ToString() +
                    ((MethodDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.ConstructorDeclaration:
                return ((ConstructorDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.DestructorDeclaration:
                return "~" + ((DestructorDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.PropertyDeclaration:
                return ((PropertyDeclarationSyntax)node).ExplicitInterfaceSpecifier?.ToString() +
                    ((PropertyDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.IndexerDeclaration:
                return ((IndexerDeclarationSyntax)node).ExplicitInterfaceSpecifier?.ToString() +
                    ((IndexerDeclarationSyntax)node).ThisKeyword.ToString();
            case SyntaxKind.EventDeclaration:
                return ((EventDeclarationSyntax)node).ExplicitInterfaceSpecifier?.ToString() +
                    ((EventDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.Parameter:
                return GetParameterName(node);
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return ((BaseNamespaceDeclarationSyntax)node).Name.ToString();
            case SyntaxKind.OperatorDeclaration:
                return "operator " + ((OperatorDeclarationSyntax)node).OperatorToken.ToString();
            case SyntaxKind.ConversionOperatorDeclaration:
                var conversionOperator = (ConversionOperatorDeclarationSyntax)node;
                return (conversionOperator.ImplicitOrExplicitKeyword.Kind() == SyntaxKind.ImplicitKeyword ? "implicit " : "explicit ")
                    + "operator "
                    + conversionOperator.Type.ToString();
            case SyntaxKind.EnumMemberDeclaration:
                return ((EnumMemberDeclarationSyntax)node).Identifier.ToString();
            case SyntaxKind.VariableDeclarator:
                return ((VariableDeclaratorSyntax)node).Identifier.ToString();
            case SyntaxKind.Attribute:
                return ((AttributeSyntax)node).Name.ToString();
            case SyntaxKind.AttributeArgument:
                var attributeArgument = (AttributeArgumentSyntax)node;
                return attributeArgument.NameEquals != null
                    ? attributeArgument.NameEquals.Name.ToString()
                    : string.Empty;
            case SyntaxKind.UsingDirective:
                throw Exceptions.ThrowEFail();
            default:
                Debug.Fail("Invalid node kind: " + node.Kind());
                throw new ArgumentException();
        }
    }

    public override SyntaxNode GetNodeWithName(SyntaxNode node)
    {
        var kind = node.Kind();
        if (kind is SyntaxKind.OperatorDeclaration or
            SyntaxKind.ConversionOperatorDeclaration)
        {
            throw Exceptions.ThrowEFail();
        }

        return node;
    }

    public override SyntaxNode SetName(SyntaxNode node, string name)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        // In all cases, the resulting syntax for the new name has elastic trivia attached,
        // whether via this call to SyntaxFactory.Identifier or via explicitly added elastic
        // markers.
        var newIdentifier = SyntaxFactory.Identifier(name);
        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.StructDeclaration:
                return SetNameOfClassOrStruct(node, newIdentifier);
            case SyntaxKind.InterfaceDeclaration:
                return ((InterfaceDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.EnumDeclaration:
                return ((EnumDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.DelegateDeclaration:
                return ((DelegateDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.MethodDeclaration:
                return ((MethodDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.ConstructorDeclaration:
                return ((ConstructorDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.DestructorDeclaration:
                return ((DestructorDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.PropertyDeclaration:
                return ((PropertyDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.EventDeclaration:
                return ((EventDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.Parameter:
                return ((ParameterSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return ((BaseNamespaceDeclarationSyntax)node).WithName(
                    SyntaxFactory.ParseName(name)
                        .WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker))
                        .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)));
            case SyntaxKind.EnumMemberDeclaration:
                return ((EnumMemberDeclarationSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.VariableDeclarator:
                return ((VariableDeclaratorSyntax)node).WithIdentifier(newIdentifier);
            case SyntaxKind.Attribute:
                return ((AttributeSyntax)node).WithName(
                    SyntaxFactory.ParseName(name)
                        .WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker))
                        .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)));
            case SyntaxKind.AttributeArgument:
                return ((AttributeArgumentSyntax)node).WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(name)));
            default:
                Debug.Fail("Invalid node kind: " + node.Kind());
                throw new ArgumentException();
        }
    }

    private static SyntaxNode SetNameOfClassOrStruct(SyntaxNode node, SyntaxToken newIdentifier)
    {
        var typeNode = (TypeDeclarationSyntax)node;

        foreach (var member in typeNode.Members)
        {
            if (member.Kind() == SyntaxKind.ConstructorDeclaration)
            {
                var constructor = ((ConstructorDeclarationSyntax)member).WithIdentifier(newIdentifier);
                typeNode = typeNode.ReplaceNode(member, constructor);
            }
            else if (member.Kind() == SyntaxKind.DestructorDeclaration)
            {
                var destructor = ((DestructorDeclarationSyntax)member).WithIdentifier(newIdentifier);
                typeNode = typeNode.ReplaceNode(member, destructor);
            }
        }

        if (typeNode.Kind() == SyntaxKind.StructDeclaration)
        {
            return ((StructDeclarationSyntax)typeNode).WithIdentifier(newIdentifier);
        }
        else
        {
            return ((ClassDeclarationSyntax)typeNode).WithIdentifier(newIdentifier);
        }
    }

    public override string GetFullName(SyntaxNode node, SemanticModel semanticModel)
    {
        if (node.Kind() == SyntaxKind.UsingDirective)
        {
            throw Exceptions.ThrowEFail();
        }

        var symbol = node is AttributeSyntax
            ? semanticModel.GetTypeInfo(node).Type
            : semanticModel.GetDeclaredSymbol(node);

        Contract.ThrowIfNull(symbol);
        return GetExternalSymbolFullName(symbol);
    }

    public override string GetFullyQualifiedName(string name, int position, SemanticModel semanticModel)
    {
        var typeName = SyntaxFactory.ParseTypeName(name);
        if (typeName is PredefinedTypeSyntax predefinedTypeNode)
        {
            if (SyntaxFactsService.TryGetPredefinedType(predefinedTypeNode.Keyword, out var predefinedType))
            {
                var specialType = predefinedType.ToSpecialType();
                return semanticModel.Compilation.GetSpecialType(specialType).GetEscapedFullName();
            }
        }
        else
        {
            var symbols = semanticModel.LookupNamespacesAndTypes(position, name: name);
            if (symbols.Length > 0)
            {
                return symbols[0].GetEscapedFullName();
            }
        }

        return name;
    }

    public override bool IsValidExternalSymbol(ISymbol symbol)
    {
        if (symbol is IMethodSymbol methodSymbol)
        {
            if (methodSymbol.MethodKind is MethodKind.PropertyGet or
                MethodKind.PropertySet or
                MethodKind.EventAdd or
                MethodKind.EventRemove or
                MethodKind.EventRaise)
            {
                return false;
            }
        }

        return symbol.DeclaredAccessibility is Accessibility.Public
            or Accessibility.Protected
            or Accessibility.ProtectedOrInternal;
    }

    public override string GetExternalSymbolName(ISymbol symbol)
    {
        if (symbol == null)
        {
            throw Exceptions.ThrowEFail();
        }

        return symbol.ToDisplayString(s_externalNameFormat);
    }

    public override string GetExternalSymbolFullName(ISymbol symbol)
    {
        if (symbol == null)
        {
            throw Exceptions.ThrowEFail();
        }

        return symbol.ToDisplayString(s_externalFullNameFormat);
    }

    public override EnvDTE.vsCMAccess GetAccess(ISymbol symbol)
        => symbol.DeclaredAccessibility switch
        {
            Accessibility.Public => EnvDTE.vsCMAccess.vsCMAccessPublic,
            Accessibility.Private => EnvDTE.vsCMAccess.vsCMAccessPrivate,
            Accessibility.Internal => EnvDTE.vsCMAccess.vsCMAccessProject,
            Accessibility.Protected => EnvDTE.vsCMAccess.vsCMAccessProtected,
            Accessibility.ProtectedOrInternal => EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected,
            Accessibility.ProtectedAndInternal =>
                // there is no appropriate mapping for private protected in EnvDTE.vsCMAccess
                // See https://github.com/dotnet/roslyn/issues/22406
                EnvDTE.vsCMAccess.vsCMAccessProtected,
            _ => throw Exceptions.ThrowEFail(),
        };

    public override EnvDTE.vsCMAccess GetAccess(SyntaxNode node)
    {
        var member = GetNodeWithModifiers(node);

        if (member == null)
        {
            throw Exceptions.ThrowEFail();
        }

        var modifiers = member.GetModifiers();

        if (modifiers.Any(SyntaxKind.PublicKeyword))
        {
            return EnvDTE.vsCMAccess.vsCMAccessPublic;
        }
        else if (modifiers.Any(SyntaxKind.ProtectedKeyword) && modifiers.Any(SyntaxKind.InternalKeyword))
        {
            return EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected;
        }
        else if (modifiers.Any(SyntaxKind.InternalKeyword))
        {
            return EnvDTE.vsCMAccess.vsCMAccessProject;
        }
        else if (modifiers.Any(SyntaxKind.ProtectedKeyword))
        {
            return EnvDTE.vsCMAccess.vsCMAccessProtected;
        }
        else if (modifiers.Any(SyntaxKind.PrivateKeyword))
        {
            return EnvDTE.vsCMAccess.vsCMAccessPrivate;
        }
        else
        {
            // The code does not specify the accessibility, so we need to
            // determine the default accessibility.
            return GetDefaultAccessibility(member);
        }
    }

#nullable disable

    public override SyntaxNode GetNodeWithModifiers(SyntaxNode node)
    {
        return node is VariableDeclaratorSyntax
               ? node.GetAncestor<MemberDeclarationSyntax>()
               : node;
    }

    public override SyntaxNode GetNodeWithType(SyntaxNode node)
    {
        return node is VariableDeclaratorSyntax
               ? node.GetAncestor<MemberDeclarationSyntax>()
               : node;
    }

#nullable restore

    public override SyntaxNode GetNodeWithInitializer(SyntaxNode node)
        => node;

    private EnvDTE.vsCMAccess GetDefaultAccessibility(SyntaxNode node)
    {
        if (node is EnumMemberDeclarationSyntax)
        {
            return EnvDTE.vsCMAccess.vsCMAccessPublic;
        }

        if (node is BaseFieldDeclarationSyntax or
            BaseMethodDeclarationSyntax or
            BasePropertyDeclarationSyntax)
        {
            // Members of interfaces and enums are public, while all other
            // members are private.
            if (node.HasAncestor<InterfaceDeclarationSyntax>() ||
                node.HasAncestor<EnumDeclarationSyntax>())
            {
                return EnvDTE.vsCMAccess.vsCMAccessPublic;
            }
            else
            {
                return EnvDTE.vsCMAccess.vsCMAccessPrivate;
            }
        }

        if (node is BaseTypeDeclarationSyntax or
            DelegateDeclarationSyntax)
        {
            // Types declared within types are private by default,
            // otherwise internal.
            if (node.HasAncestor<BaseTypeDeclarationSyntax>())
            {
                return EnvDTE.vsCMAccess.vsCMAccessPrivate;
            }
            else
            {
                return EnvDTE.vsCMAccess.vsCMAccessProject;
            }
        }

        if (node is AccessorDeclarationSyntax or
            ArrowExpressionClauseSyntax)
        {
            return GetAccess(node.GetAncestors<BasePropertyDeclarationSyntax>().First());
        }

        throw Exceptions.ThrowEFail();
    }

    public override SyntaxNode SetAccess(SyntaxNode node, EnvDTE.vsCMAccess newAccess)
    {
        if (node is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (member.Parent is (kind: SyntaxKind.InterfaceDeclaration or SyntaxKind.EnumDeclaration))
        {
            if (newAccess is EnvDTE.vsCMAccess.vsCMAccessDefault or
                EnvDTE.vsCMAccess.vsCMAccessPublic)
            {
                return member;
            }
            else
            {
                throw Exceptions.ThrowEInvalidArg();
            }
        }

        if (member is BaseTypeDeclarationSyntax or
            EnumDeclarationSyntax)
        {
            if (!(member.Parent is BaseTypeDeclarationSyntax) &&
                (newAccess == EnvDTE.vsCMAccess.vsCMAccessPrivate ||
                 newAccess == EnvDTE.vsCMAccess.vsCMAccessProtected ||
                 newAccess == EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected))
            {
                throw Exceptions.ThrowEInvalidArg();
            }
        }

        var modifierFlags = member.GetModifierFlags() & ~ModifierFlags.AccessModifierMask;

        switch (newAccess)
        {
            case EnvDTE.vsCMAccess.vsCMAccessPrivate:
                modifierFlags |= ModifierFlags.Private;
                break;

            case EnvDTE.vsCMAccess.vsCMAccessProtected:
                modifierFlags |= ModifierFlags.Protected;
                break;

            case EnvDTE.vsCMAccess.vsCMAccessPublic:
                modifierFlags |= ModifierFlags.Public;
                break;

            case EnvDTE.vsCMAccess.vsCMAccessProject:
                modifierFlags |= ModifierFlags.Internal;
                break;

            case EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected:
                modifierFlags |= ModifierFlags.Protected | ModifierFlags.Internal;
                break;

            case EnvDTE.vsCMAccess.vsCMAccessDefault:
                break; // No change

            default:
                throw Exceptions.ThrowEUnexpected();
        }

        return member.UpdateModifiers(modifierFlags);
    }

    private static IList<SyntaxTrivia> CollectComments(IList<SyntaxTrivia> triviaList)
    {
        var commentList = new List<SyntaxTrivia>();

        for (var i = triviaList.Count - 1; i >= 0; i--)
        {
            var trivia = triviaList[i];
            if (trivia.IsRegularComment())
            {
                commentList.Add(trivia);
            }
            else if (trivia.Kind() is not SyntaxKind.WhitespaceTrivia and
                not SyntaxKind.EndOfLineTrivia)
            {
                break;
            }
        }

        commentList.Reverse();

        return commentList;
    }

    public override string GetComment(SyntaxNode node)
    {
        var firstToken = node.GetFirstToken();
        var commentList = CollectComments(firstToken.LeadingTrivia.ToArray());

        if (commentList.Count == 0)
        {
            return string.Empty;
        }

        var textBuilder = new StringBuilder();
        foreach (var trivia in commentList)
        {
            if (trivia.IsRegularComment())
            {
                textBuilder.AppendLine(trivia.GetCommentText());
            }
            else
            {
                throw Exceptions.ThrowEFail();
            }
        }

        return textBuilder.ToString();
    }

    public override SyntaxNode SetComment(SyntaxNode node, string value)
    {
        Debug.Assert(node is MemberDeclarationSyntax);

        var memberDeclaration = (MemberDeclarationSyntax)node;
        var text = memberDeclaration.SyntaxTree.GetText(CancellationToken.None);
        var newLine = GetNewLineCharacter(text);

        var commentText = string.Empty;

        if (value != null)
        {
            var builder = new StringBuilder();

            foreach (var line in value.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries))
            {
                builder.Append("// ");
                builder.Append(line);
                builder.Append(newLine);
            }

            commentText = builder.ToString();
        }

        var newTriviaList = SyntaxFactory.ParseLeadingTrivia(commentText);
        var leadingTriviaList = memberDeclaration.GetLeadingTrivia().ToList();

        var commentList = CollectComments(leadingTriviaList);
        if (commentList.Count > 0)
        {
            // In this case, we're going to replace the existing comment.
            var firstIndex = leadingTriviaList.FindIndex(t => t == commentList[0]);
            var lastIndex = leadingTriviaList.FindIndex(t => t == commentList[commentList.Count - 1]);
            var count = lastIndex - firstIndex + 1;

            leadingTriviaList.RemoveRange(firstIndex, count);

            // Note: single line comments have a trailing new-line but that won't be
            // returned by CollectComments. So, we may need to remove an additional new line below.
            if (firstIndex < leadingTriviaList.Count &&
                leadingTriviaList[firstIndex].Kind() == SyntaxKind.EndOfLineTrivia)
            {
                leadingTriviaList.RemoveAt(firstIndex);
            }

            foreach (var trivia in newTriviaList.Reverse())
            {
                leadingTriviaList.Insert(firstIndex, trivia);
            }
        }
        else
        {
            // Otherwise, just add the comment to the end of the leading trivia.
            leadingTriviaList.AddRange(newTriviaList);
        }

        return memberDeclaration.WithLeadingTrivia(leadingTriviaList);
    }

    private static DocumentationCommentTriviaSyntax? GetDocCommentNode(MemberDeclarationSyntax memberDeclaration)
    {
        var docCommentTrivia = memberDeclaration
            .GetLeadingTrivia()
            .Reverse()
            .FirstOrDefault(t => t.IsDocComment());

        if (!docCommentTrivia.IsDocComment())
        {
            return null;
        }

        return (DocumentationCommentTriviaSyntax?)docCommentTrivia.GetStructure();
    }

    public override string GetDocComment(SyntaxNode node)
    {
        Debug.Assert(node is MemberDeclarationSyntax);

        var memberDeclaration = (MemberDeclarationSyntax)node;
        var documentationComment = GetDocCommentNode(memberDeclaration);
        if (documentationComment == null)
        {
            return string.Empty;
        }

        var text = memberDeclaration.SyntaxTree.GetText(CancellationToken.None);
        var newLine = GetNewLineCharacter(text);

        var lines = documentationComment.ToString().Split([newLine], StringSplitOptions.None);

        // trim off leading whitespace and exterior trivia.
        var lengthToStrip = lines[0].GetLeadingWhitespace().Length;

        for (var i = 1; i < lines.Length; i++)
        {
            var line = lines[i].TrimStart();
            if (line.StartsWith("///", StringComparison.Ordinal))
            {
                line = line[3..];
            }

            if (line.Length > 0)
            {
                lengthToStrip = Math.Min(lengthToStrip, line.GetLeadingWhitespace().Length);
            }

            lines[i] = line;
        }

        for (var i = 0; i < lines.Length; i++)
        {
            var line = lines[i];
            if (line.Length > lengthToStrip)
            {
                lines[i] = line[lengthToStrip..];
            }
        }

        return "<doc>\r\n" + lines.Join(newLine) + "</doc>";
    }

    public override SyntaxNode SetDocComment(SyntaxNode node, string value)
    {
        Debug.Assert(node is MemberDeclarationSyntax);

        XDocument xmlDocument;
        try
        {
            using var reader = new StringReader(value);
            xmlDocument = XDocument.Load(reader);
        }
        catch
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        if (xmlDocument.FirstNode is not XElement docElement ||
            docElement.Name.ToString().ToLower() != "doc")
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var memberDeclaration = (MemberDeclarationSyntax)node;
        var text = memberDeclaration.SyntaxTree.GetText(CancellationToken.None);
        var newLine = GetNewLineCharacter(text);
        var builder = new StringBuilder();

        foreach (var child in docElement.Elements())
        {
            foreach (var line in child.ToString().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries))
            {
                builder.Append("/// ");
                builder.Append(line);
                builder.Append(newLine);
            }
        }

        var newTriviaList = SyntaxFactory.ParseLeadingTrivia(builder.ToString());
        var leadingTriviaList = memberDeclaration.GetLeadingTrivia().ToList();
        var documentationComment = GetDocCommentNode(memberDeclaration);

        if (documentationComment != null)
        {
            // In this case, we're going to replace the existing XML doc comment.
            var index = leadingTriviaList.FindIndex(t => t == documentationComment.ParentTrivia);
            leadingTriviaList.RemoveAt(index);

            foreach (var triviaElement in newTriviaList.Reverse())
            {
                leadingTriviaList.Insert(index, triviaElement);
            }
        }
        else
        {
            // Otherwise, just add the XML doc comment to the end of the leading trivia.
            leadingTriviaList.AddRange(newTriviaList);
        }

        return memberDeclaration.WithLeadingTrivia(leadingTriviaList);
    }

    public override IEnumerable<SyntaxNode> GetParameterNodes(SyntaxNode parentNode)
    {
        if (parentNode is BaseMethodDeclarationSyntax baseMethod)
        {
            return baseMethod.ParameterList.Parameters;
        }
        else if (parentNode is IndexerDeclarationSyntax indexer)
        {
            return indexer.ParameterList.Parameters;
        }
        else if (parentNode is DelegateDeclarationSyntax delegateDecl)
        {
            return delegateDecl.ParameterList.Parameters;
        }

        return [];
    }

    public override bool IsExpressionBodiedProperty(SyntaxNode node)
        => (node as PropertyDeclarationSyntax)?.ExpressionBody != null;

    public override bool TryGetAutoPropertyExpressionBody(SyntaxNode parentNode, [NotNullWhen(true)] out SyntaxNode? accessorNode)
    {
        accessorNode = (parentNode as PropertyDeclarationSyntax)?.ExpressionBody;
        return accessorNode != null;
    }

    public override bool IsAccessorNode(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                return true;
        }

        return false;
    }

    public override MethodKind GetAccessorKind(SyntaxNode node)
        => node.Kind() switch
        {
            SyntaxKind.GetAccessorDeclaration => MethodKind.PropertyGet,
            SyntaxKind.SetAccessorDeclaration => MethodKind.PropertySet,
            SyntaxKind.AddAccessorDeclaration => MethodKind.EventAdd,
            SyntaxKind.RemoveAccessorDeclaration => MethodKind.EventRemove,
            _ => throw Exceptions.ThrowEUnexpected(),
        };

    private static SyntaxKind GetAccessorSyntaxKind(MethodKind methodKind)
        => methodKind switch
        {
            MethodKind.PropertyGet => SyntaxKind.GetAccessorDeclaration,
            MethodKind.PropertySet => SyntaxKind.SetAccessorDeclaration,
            MethodKind.EventAdd => SyntaxKind.AddAccessorDeclaration,
            MethodKind.EventRemove => SyntaxKind.RemoveAccessorDeclaration,
            _ => throw Exceptions.ThrowEUnexpected(),
        };

    public override bool TryGetAccessorNode(SyntaxNode parentNode, MethodKind kind, [NotNullWhen(true)] out SyntaxNode? accessorNode)
    {
        Debug.Assert(parentNode is BasePropertyDeclarationSyntax);

        var basePropertyDeclaration = (BasePropertyDeclarationSyntax)parentNode;
        var accessorKind = GetAccessorSyntaxKind(kind);

        if (basePropertyDeclaration.AccessorList != null)
        {
            foreach (var accessor in basePropertyDeclaration.AccessorList.Accessors)
            {
                if (accessor.Kind() == accessorKind)
                {
                    accessorNode = accessor;
                    return true;
                }
            }
        }

        accessorNode = null;
        return false;
    }

    public override bool TryGetParameterNode(SyntaxNode parentNode, string name, [NotNullWhen(true)] out SyntaxNode? parameterNode)
    {
        foreach (ParameterSyntax parameter in GetParameterNodes(parentNode))
        {
            if (parameter.Identifier.ToString() == name)
            {
                parameterNode = parameter;
                return true;
            }
        }

        parameterNode = null;
        return false;
    }

    public override bool TryGetImportNode(SyntaxNode parentNode, string dottedName, [NotNullWhen(true)] out SyntaxNode? importNode)
    {
        foreach (UsingDirectiveSyntax usingDirective in GetImportNodes(parentNode))
        {
            if (usingDirective.Name?.ToString() == dottedName)
            {
                importNode = usingDirective;
                return true;
            }
        }

        importNode = null;
        return false;
    }

    public override bool TryGetOptionNode(SyntaxNode parentNode, string name, int ordinal, out SyntaxNode optionNode)
    {
        // Only VB has Option statements
        throw new NotSupportedException();
    }

    public override bool TryGetInheritsNode(SyntaxNode parentNode, string name, int ordinal, out SyntaxNode inheritsNode)
    {
        // Only VB has Inherits statements
        throw new NotSupportedException();
    }

    public override bool TryGetImplementsNode(SyntaxNode parentNode, string name, int ordinal, out SyntaxNode implementsNode)
    {
        // Only VB has Implements statements
        throw new NotSupportedException();
    }

    public override bool TryGetAttributeNode(SyntaxNode parentNode, string name, int ordinal, [NotNullWhen(true)] out SyntaxNode? attributeNode)
    {
        var count = -1;
        foreach (AttributeSyntax attribute in GetAttributeNodes(parentNode))
        {
            if (attribute.Name.ToString() == name)
            {
                count++;
                if (count == ordinal)
                {
                    attributeNode = attribute;
                    return true;
                }
            }
        }

        attributeNode = null;
        return false;
    }

    public override bool TryGetAttributeArgumentNode(SyntaxNode attributeNode, int index, [NotNullWhen(true)] out SyntaxNode? attributeArgumentNode)
    {
        Debug.Assert(attributeNode is AttributeSyntax);

        var attribute = (AttributeSyntax)attributeNode;
        if (attribute.ArgumentList != null &&
            attribute.ArgumentList.Arguments.Count > index)
        {
            attributeArgumentNode = attribute.ArgumentList.Arguments[index];
            return true;
        }

        attributeArgumentNode = null;
        return false;
    }

    public override void GetOptionNameAndOrdinal(SyntaxNode parentNode, SyntaxNode optionNode, out string name, out int ordinal)
    {
        // Only VB supports Option statements
        throw new NotSupportedException();
    }

    public override void GetInheritsNamespaceAndOrdinal(SyntaxNode parentNode, SyntaxNode inheritsNode, out string namespaceName, out int ordinal)
    {
        // Only VB supports Inherits statements
        throw new NotSupportedException();
    }

    public override void GetImplementsNamespaceAndOrdinal(SyntaxNode parentNode, SyntaxNode implementsNode, out string namespaceName, out int ordinal)
    {
        // Only VB supports Implements statements
        throw new NotSupportedException();
    }

    public override void GetAttributeNameAndOrdinal(SyntaxNode parentNode, SyntaxNode attributeNode, out string name, out int ordinal)
    {
        Debug.Assert(attributeNode is AttributeSyntax);

        name = ((AttributeSyntax)attributeNode).Name.ToString();

        ordinal = -1;
        foreach (AttributeSyntax attribute in GetAttributeNodes(parentNode))
        {
            if (attribute.Name.ToString() == name)
            {
                ordinal++;
            }

            if (attribute == attributeNode)
            {
                break;
            }
        }
    }

    public override void GetAttributeArgumentParentAndIndex(SyntaxNode attributeArgumentNode, out SyntaxNode attributeNode, out int index)
    {
        Debug.Assert(attributeArgumentNode is AttributeArgumentSyntax);

        var argument = (AttributeArgumentSyntax)attributeArgumentNode;
        var attribute = (AttributeSyntax)argument.Ancestors().First(n => n.Kind() == SyntaxKind.Attribute);

        attributeNode = attribute;
        index = attribute.ArgumentList!.Arguments.IndexOf((AttributeArgumentSyntax)attributeArgumentNode);
    }

    public override SyntaxNode GetAttributeTargetNode(SyntaxNode attributeNode)
    {
        Debug.Assert(attributeNode is AttributeSyntax);
        RoslynDebug.Assert(attributeNode.Parent is AttributeListSyntax);

        return (AttributeListSyntax)attributeNode.Parent;
    }

    public override string GetAttributeTarget(SyntaxNode attributeNode)
    {
        Debug.Assert(attributeNode is AttributeSyntax);
        RoslynDebug.Assert(attributeNode.Parent is AttributeListSyntax);

        var attributeList = (AttributeListSyntax)attributeNode.Parent;
        if (attributeList.Target != null)
        {
            return attributeList.Target.Identifier.ToString();
        }

        return string.Empty;
    }

    public override SyntaxNode SetAttributeTarget(SyntaxNode attributeNode, string target)
    {
        Debug.Assert(attributeNode is AttributeListSyntax);

        var attributeList = (AttributeListSyntax)attributeNode;

        if (string.IsNullOrEmpty(target))
        {
            return attributeList.WithTarget(null);
        }
        else
        {
            return attributeList.WithTarget(
                SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Identifier(target)));
        }
    }

    public override string GetAttributeValue(SyntaxNode attributeNode)
    {
        Debug.Assert(attributeNode is AttributeSyntax);

        var attribute = (AttributeSyntax)attributeNode;
        var argumentList = attribute.ArgumentList;
        if (argumentList != null)
        {
            return argumentList.Arguments.ToString();
        }

        return string.Empty;
    }

    public override SyntaxNode SetAttributeValue(SyntaxNode attributeNode, string value)
    {
        Debug.Assert(attributeNode is AttributeSyntax);

        var attribute = (AttributeSyntax)attributeNode;
        var argumentList = attribute.ArgumentList;
        var parsedArgumentList = SyntaxFactory.ParseAttributeArgumentList("(" + value + ")");

        // Parsing here will always succeed since it only returns null when the text doesn't start with a ( and we
        // enforce above that it has such a character.
        Contract.ThrowIfNull(parsedArgumentList);
        var newArgumentList = argumentList != null
            ? argumentList.WithArguments(parsedArgumentList.Arguments)
            : parsedArgumentList;

        return attribute.WithArgumentList(newArgumentList);
    }

    public override SyntaxNode GetNodeWithAttributes(SyntaxNode node)
    {
        return node is VariableDeclaratorSyntax
               ? node.GetAncestors<MemberDeclarationSyntax>().First()
               : node;
    }

    public override SyntaxNode GetEffectiveParentForAttribute(SyntaxNode node)
    {
        if (node.GetAncestor<BaseFieldDeclarationSyntax>() is BaseFieldDeclarationSyntax fieldDeclaration)
        {
            return fieldDeclaration.Declaration.Variables.First();
        }
        else if (node.GetAncestor<ParameterSyntax>() is ParameterSyntax parameterSyntax)
        {
            return parameterSyntax;
        }
        else
        {
            Contract.ThrowIfNull(node.Parent);
            return node.Parent;
        }
    }

    public override SyntaxNode CreateAttributeNode(string name, string value, string? target = null)
    {
        var specifier = target != null
            ? SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Identifier(target),
                SyntaxFactory.Token([SyntaxFactory.ElasticMarker], SyntaxKind.ColonToken, [SyntaxFactory.Space]))
            : null;

        return SyntaxFactory.AttributeList(
            target: specifier,
            attributes: [SyntaxFactory.Attribute(
                name: SyntaxFactory.ParseName(name),
                argumentList: SyntaxFactory.ParseAttributeArgumentList("(" + value + ")"))]);
    }

    public override SyntaxNode CreateAttributeArgumentNode(string name, string value)
    {
        if (!string.IsNullOrEmpty(name))
        {
            return SyntaxFactory.AttributeArgument(
                nameEquals: SyntaxFactory.NameEquals(name),
                nameColon: null,
                expression: SyntaxFactory.ParseExpression(value));
        }
        else
        {
            return SyntaxFactory.AttributeArgument(SyntaxFactory.ParseExpression(value));
        }
    }

    public override SyntaxNode CreateImportNode(string name, string? alias = null)
    {
        var nameSyntax = SyntaxFactory.ParseName(name);

        if (!RoslynString.IsNullOrEmpty(alias))
        {
            var aliasSyntax = SyntaxFactory.NameEquals(alias);
            return SyntaxFactory.UsingDirective(aliasSyntax, nameSyntax);
        }
        else
        {
            return SyntaxFactory.UsingDirective(nameSyntax);
        }
    }

    public override SyntaxNode CreateParameterNode(string name, string type)
        => SyntaxFactory.Parameter(SyntaxFactory.Identifier(name)).WithType(SyntaxFactory.ParseTypeName(type));

    public override string GetAttributeArgumentValue(SyntaxNode attributeArgumentNode)
    {
        Debug.Assert(attributeArgumentNode is AttributeArgumentSyntax);

        return ((AttributeArgumentSyntax)attributeArgumentNode).Expression.ToString();
    }

    public override string GetImportAlias(SyntaxNode importNode)
    {
        if (importNode is UsingDirectiveSyntax usingDirective)
        {
            return usingDirective.Alias != null
                ? usingDirective.Alias.Name.ToString()
                : string.Empty;
        }

        throw new InvalidOperationException();
    }

    public override string GetImportNamespaceOrType(SyntaxNode importNode)
    {
        if (importNode is UsingDirectiveSyntax usingDirective)
        {
            return usingDirective.NamespaceOrType.ToString();
        }

        throw new InvalidOperationException();
    }

    public override void GetImportParentAndName(SyntaxNode importNode, out SyntaxNode? namespaceNode, out string name)
    {
        if (importNode is UsingDirectiveSyntax usingDirective)
        {
            namespaceNode = usingDirective.IsParentKind(SyntaxKind.CompilationUnit)
                ? null
                : usingDirective.Parent;

            name = usingDirective.NamespaceOrType.ToString();

            return;
        }

        throw new InvalidOperationException();
    }

    public override string GetParameterName(SyntaxNode node)
    {
        if (node is ParameterSyntax parameter)
        {
            return parameter.Identifier.ToString();
        }

        throw new InvalidOperationException();
    }

    public override EnvDTE80.vsCMParameterKind GetParameterKind(SyntaxNode node)
    {
        if (node is ParameterSyntax parameter)
        {
            var kind = EnvDTE80.vsCMParameterKind.vsCMParameterKindNone;

            var modifiers = parameter.Modifiers;
            if (modifiers.Any(SyntaxKind.RefKeyword))
            {
                kind = EnvDTE80.vsCMParameterKind.vsCMParameterKindRef;
            }
            else if (modifiers.Any(SyntaxKind.OutKeyword))
            {
                kind = EnvDTE80.vsCMParameterKind.vsCMParameterKindOut;
            }
            else if (modifiers.Any(SyntaxKind.ParamsKeyword))
            {
                kind = EnvDTE80.vsCMParameterKind.vsCMParameterKindParamArray;
            }

            // Note: this is not an "else if" since it might be both
            // optional and "ref".
            if (parameter.Default != null)
            {
                kind |= EnvDTE80.vsCMParameterKind.vsCMParameterKindOptional;
            }

            return kind;
        }

        throw new InvalidOperationException();
    }

    public override SyntaxNode SetParameterKind(SyntaxNode node, EnvDTE80.vsCMParameterKind kind)
    {
        if (node is not ParameterSyntax parameter)
        {
            throw Exceptions.ThrowEFail();
        }

        // We can't do anything with "Optional", so just strip it out and ignore it.
        if ((kind & EnvDTE80.vsCMParameterKind.vsCMParameterKindOptional) != 0)
        {
            kind -= EnvDTE80.vsCMParameterKind.vsCMParameterKindOptional;
        }

        SyntaxTokenList newModifiers;

        switch (kind)
        {
            case EnvDTE80.vsCMParameterKind.vsCMParameterKindOut:
                newModifiers = [OutKeyword];
                break;

            case EnvDTE80.vsCMParameterKind.vsCMParameterKindRef:
                newModifiers = [RefKeyword];
                break;

            case EnvDTE80.vsCMParameterKind.vsCMParameterKindIn:
            case EnvDTE80.vsCMParameterKind.vsCMParameterKindNone:
                newModifiers = [];
                break;

            case EnvDTE80.vsCMParameterKind.vsCMParameterKindParamArray:
                {
                    var parameterList = (ParameterListSyntax)parameter.Parent!;
                    if (parameterList.Parameters.LastOrDefault() == parameter &&
                        parameter.Type is ArrayTypeSyntax)
                    {
                        newModifiers = [ParamsKeyword];
                        break;
                    }

                    throw Exceptions.ThrowEInvalidArg();
                }

            default:
                throw Exceptions.ThrowEInvalidArg();
        }

        return parameter.WithModifiers(newModifiers);
    }

    public override EnvDTE80.vsCMParameterKind UpdateParameterKind(EnvDTE80.vsCMParameterKind parameterKind, PARAMETER_PASSING_MODE passingMode)
    {
        var updatedParameterKind = parameterKind;

        switch (passingMode)
        {
            case PARAMETER_PASSING_MODE.cmParameterTypeIn:
                updatedParameterKind |= EnvDTE80.vsCMParameterKind.vsCMParameterKindNone;
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindRef;
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindOut;
                break;

            case PARAMETER_PASSING_MODE.cmParameterTypeInOut:
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindNone;
                updatedParameterKind |= EnvDTE80.vsCMParameterKind.vsCMParameterKindRef;
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindOut;
                break;

            case PARAMETER_PASSING_MODE.cmParameterTypeOut:
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindNone;
                updatedParameterKind &= ~EnvDTE80.vsCMParameterKind.vsCMParameterKindRef;
                updatedParameterKind |= EnvDTE80.vsCMParameterKind.vsCMParameterKindOut;
                break;
        }

        return updatedParameterKind;
    }

    public override EnvDTE.vsCMFunction ValidateFunctionKind(SyntaxNode containerNode, EnvDTE.vsCMFunction kind, string name)
    {
        if (kind == EnvDTE.vsCMFunction.vsCMFunctionSub)
        {
            return EnvDTE.vsCMFunction.vsCMFunctionFunction;
        }

        if (kind == EnvDTE.vsCMFunction.vsCMFunctionFunction)
        {
            return kind;
        }

        if (kind is EnvDTE.vsCMFunction.vsCMFunctionConstructor or
            EnvDTE.vsCMFunction.vsCMFunctionDestructor)
        {
            if (containerNode is InterfaceDeclarationSyntax)
            {
                throw Exceptions.ThrowEFail();
            }

            return kind;
        }

        throw Exceptions.ThrowENotImpl();
    }

    public override bool SupportsEventThrower
    {
        get { return false; }
    }

    public override bool GetCanOverride(SyntaxNode memberNode)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (member.Parent is InterfaceDeclarationSyntax)
        {
            return true;
        }

        var flags = member.GetModifierFlags();

        return (flags & (ModifierFlags.Abstract | ModifierFlags.Virtual)) != 0;
    }

    public override SyntaxNode SetCanOverride(SyntaxNode memberNode, bool value)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (member.Parent is InterfaceDeclarationSyntax)
        {
            if (!value)
            {
                throw Exceptions.ThrowEInvalidArg();
            }

            return memberNode;
        }

        var flags = member.GetModifierFlags();

        if (value)
        {
            flags |= ModifierFlags.Virtual;
        }
        else
        {
            flags &= ~ModifierFlags.Virtual;
        }

        return member.UpdateModifiers(flags);
    }

    public override EnvDTE80.vsCMClassKind GetClassKind(SyntaxNode typeNode, INamedTypeSymbol typeSymbol)
    {
        Debug.Assert(typeNode is ClassDeclarationSyntax);

        var type = (ClassDeclarationSyntax)typeNode;
        var flags = type.GetModifierFlags();

        return (flags & ModifierFlags.Partial) != 0
            ? EnvDTE80.vsCMClassKind.vsCMClassKindPartialClass
            : EnvDTE80.vsCMClassKind.vsCMClassKindMainClass;
    }

    public override SyntaxNode SetClassKind(SyntaxNode typeNode, EnvDTE80.vsCMClassKind kind)
    {
        Debug.Assert(typeNode is ClassDeclarationSyntax);

        var type = (ClassDeclarationSyntax)typeNode;
        var flags = type.GetModifierFlags();

        if (kind == EnvDTE80.vsCMClassKind.vsCMClassKindPartialClass)
        {
            flags |= ModifierFlags.Partial;
        }
        else if (kind == EnvDTE80.vsCMClassKind.vsCMClassKindMainClass)
        {
            flags &= ~ModifierFlags.Partial;
        }

        return type.UpdateModifiers(flags);
    }

    public override EnvDTE80.vsCMConstKind GetConstKind(SyntaxNode variableNode)
    {
        if (variableNode is EnumMemberDeclarationSyntax)
        {
            return EnvDTE80.vsCMConstKind.vsCMConstKindConst;
        }

        if (GetNodeWithModifiers(variableNode) is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        var flags = member.GetModifierFlags();

        var result = EnvDTE80.vsCMConstKind.vsCMConstKindNone;
        if ((flags & ModifierFlags.ReadOnly) != 0)
        {
            result |= EnvDTE80.vsCMConstKind.vsCMConstKindReadOnly;
        }

        if ((flags & ModifierFlags.Const) != 0)
        {
            result |= EnvDTE80.vsCMConstKind.vsCMConstKindConst;
        }

        // Note: It's possible that we could return vsCMConstKindCont | vsCMConstKindReadOnly if the
        // user had incorrectly specified both const and readonly in their code. That's OK since
        // Code Model represents the source.

        return result;
    }

    public override SyntaxNode SetConstKind(SyntaxNode variableNode, EnvDTE80.vsCMConstKind kind)
    {
        Debug.Assert(variableNode is FieldDeclarationSyntax or
                     EnumMemberDeclarationSyntax);

        if (variableNode is EnumMemberDeclarationSyntax)
        {
            if (kind is not EnvDTE80.vsCMConstKind.vsCMConstKindConst and
                not EnvDTE80.vsCMConstKind.vsCMConstKindReadOnly)
            {
                throw Exceptions.ThrowEInvalidArg();
            }

            return variableNode;
        }

        var member = (MemberDeclarationSyntax)variableNode;
        var flags = member.GetModifierFlags();
        flags &= ~(ModifierFlags.Const | ModifierFlags.ReadOnly);

        switch (kind)
        {
            case EnvDTE80.vsCMConstKind.vsCMConstKindConst:
                flags |= ModifierFlags.Const;
                break;
            case EnvDTE80.vsCMConstKind.vsCMConstKindReadOnly:
                flags |= ModifierFlags.ReadOnly;
                break;
        }

        return member.UpdateModifiers(flags);
    }

    public override EnvDTE80.vsCMDataTypeKind GetDataTypeKind(SyntaxNode typeNode, INamedTypeSymbol symbol)
    {
        Debug.Assert(typeNode is BaseTypeDeclarationSyntax);

        var type = (BaseTypeDeclarationSyntax)typeNode;
        var flags = type.GetModifierFlags();

        return (flags & ModifierFlags.Partial) != 0
            ? EnvDTE80.vsCMDataTypeKind.vsCMDataTypeKindPartial
            : EnvDTE80.vsCMDataTypeKind.vsCMDataTypeKindMain;
    }

    public override SyntaxNode SetDataTypeKind(SyntaxNode typeNode, EnvDTE80.vsCMDataTypeKind kind)
    {
        Debug.Assert(typeNode is BaseTypeDeclarationSyntax);

        var type = (BaseTypeDeclarationSyntax)typeNode;
        var flags = type.GetModifierFlags();

        if (kind == EnvDTE80.vsCMDataTypeKind.vsCMDataTypeKindPartial)
        {
            flags |= ModifierFlags.Partial;
        }
        else if (kind == EnvDTE80.vsCMDataTypeKind.vsCMDataTypeKindMain)
        {
            flags &= ~ModifierFlags.Partial;
        }

        return type.UpdateModifiers(flags);
    }

    public override EnvDTE.vsCMFunction GetFunctionKind(IMethodSymbol symbol)
    {
        switch (symbol.MethodKind)
        {
            case MethodKind.Ordinary:
            case MethodKind.ExplicitInterfaceImplementation:
                return EnvDTE.vsCMFunction.vsCMFunctionFunction;

            case MethodKind.Constructor:
            case MethodKind.StaticConstructor:
                return EnvDTE.vsCMFunction.vsCMFunctionConstructor;

            case MethodKind.Destructor:
                return EnvDTE.vsCMFunction.vsCMFunctionDestructor;

            case MethodKind.UserDefinedOperator:
            case MethodKind.Conversion:
                return EnvDTE.vsCMFunction.vsCMFunctionOperator;

            case MethodKind.PropertyGet:
            case MethodKind.EventRemove:
                return EnvDTE.vsCMFunction.vsCMFunctionPropertyGet;

            case MethodKind.PropertySet:
            case MethodKind.EventAdd:
                return EnvDTE.vsCMFunction.vsCMFunctionPropertySet;

            default:
                throw Exceptions.ThrowEUnexpected();
        }
    }

    public override EnvDTE80.vsCMInheritanceKind GetInheritanceKind(SyntaxNode typeNode, INamedTypeSymbol typeSymbol)
    {
        Debug.Assert(typeNode is ClassDeclarationSyntax);

        var type = (ClassDeclarationSyntax)typeNode;
        var flags = type.GetModifierFlags();

        var result = EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindNone;

        if ((flags & ModifierFlags.Abstract) != 0)
        {
            result |= EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindAbstract;
        }

        if ((flags & ModifierFlags.New) != 0)
        {
            result |= EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindNew;
        }

        if ((flags & ModifierFlags.Sealed) != 0)
        {
            result |= EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindSealed;
        }

        return result;
    }

    public override bool GetMustImplement(SyntaxNode memberNode)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (member.Parent is InterfaceDeclarationSyntax)
        {
            return true;
        }

        var flags = member.GetModifierFlags();

        return (flags & ModifierFlags.Abstract) != 0;
    }

    public override SyntaxNode SetMustImplement(SyntaxNode memberNode, bool value)
    {
        Debug.Assert(memberNode is MethodDeclarationSyntax or
                     BasePropertyDeclarationSyntax or
                     EventFieldDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (member.Parent is InterfaceDeclarationSyntax)
        {
            if (!value)
            {
                throw Exceptions.ThrowEInvalidArg();
            }

            return memberNode;
        }

        // If this is a class member and the class is not abstract, we throw.
        if (member.Parent is ClassDeclarationSyntax)
        {
            var parentFlags = ((ClassDeclarationSyntax)member.Parent).GetModifierFlags();

            if (value && (parentFlags & ModifierFlags.Abstract) == 0)
            {
                throw Exceptions.ThrowEInvalidArg();
            }
        }

        var flags = member.GetModifierFlags();

        if (value)
        {
            flags |= ModifierFlags.Abstract;

            // If this is a method, remove the body if it is empty.
            if (member is MethodDeclarationSyntax method)
            {
                if (method.Body != null && method.Body.Statements.Count == 0)
                {
                    member = method.WithBody(null).WithSemicolonToken(SyntaxFactory.Token([SyntaxFactory.ElasticMarker], SyntaxKind.SemicolonToken, method.Body.CloseBraceToken.TrailingTrivia));
                }
            }
            else
            {
                // If this is a property, remove the bodies of the accessors if they are empty.
                // Note that "empty" means that the bodies contain no statements or just a single return statement.
                if (member is BasePropertyDeclarationSyntax property && property.AccessorList != null)
                {
                    var updatedAccessors = new List<AccessorDeclarationSyntax>();
                    foreach (var accessor in property.AccessorList.Accessors)
                    {
                        if (accessor.Body == null ||
                            accessor.Body.Statements.Count > 1 ||
                            (accessor.Body.Statements.Count == 1 && !accessor.Body.Statements[0].IsKind(SyntaxKind.ReturnStatement)))
                        {
                            // Leave this accessor as is
                            updatedAccessors.Add(accessor);
                            continue;
                        }

                        var updatedAccessor = accessor.WithBody(null).WithSemicolonToken(SyntaxFactory.Token([SyntaxFactory.ElasticMarker], SyntaxKind.SemicolonToken, accessor.Body.CloseBraceToken.TrailingTrivia));
                        updatedAccessors.Add(updatedAccessor);
                    }

                    var updatedAccessorList = property.AccessorList.WithAccessors([.. updatedAccessors]);
                    member = property.ReplaceNode(property.AccessorList, updatedAccessorList);
                }
            }
        }
        else
        {
            flags &= ~ModifierFlags.Abstract;

            // If this is a method, add a body.
            if (member is MethodDeclarationSyntax method)
            {
                if (method.Body == null)
                {
                    var newBody = SyntaxFactory.Block();
                    newBody = newBody.WithCloseBraceToken(newBody.CloseBraceToken.WithTrailingTrivia(method.SemicolonToken.TrailingTrivia));
                    member = method.WithSemicolonToken(default).WithBody(newBody);
                }
            }
            else
            {
                // If this is a property, add bodies to the accessors if they don't have them.
                if (member is BasePropertyDeclarationSyntax property && property.AccessorList != null)
                {
                    var updatedAccessors = new List<AccessorDeclarationSyntax>();
                    foreach (var accessor in property.AccessorList.Accessors)
                    {
                        if (accessor.Body != null)
                        {
                            // Leave this accessor as is
                            updatedAccessors.Add(accessor);
                            continue;
                        }

                        var newBody = SyntaxFactory.Block();
                        newBody = newBody.WithCloseBraceToken(newBody.CloseBraceToken.WithTrailingTrivia(accessor.SemicolonToken.TrailingTrivia));
                        var updatedAccessor = accessor.WithSemicolonToken(default).WithBody(newBody);
                        updatedAccessors.Add(updatedAccessor);
                    }

                    var updatedAccessorList = property.AccessorList.WithAccessors([.. updatedAccessors]);
                    member = property.ReplaceNode(property.AccessorList, updatedAccessorList);
                }
            }
        }

        return member.UpdateModifiers(flags);
    }

    public override SyntaxNode SetInheritanceKind(SyntaxNode typeNode, EnvDTE80.vsCMInheritanceKind kind)
    {
        Debug.Assert(typeNode is ClassDeclarationSyntax);

        if (typeNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        var flags = member.GetModifierFlags();
        flags &= ~(ModifierFlags.Abstract | ModifierFlags.New | ModifierFlags.Sealed);

        if (kind != EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindNone)
        {
            if ((kind & EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindAbstract) != 0)
            {
                flags |= ModifierFlags.Abstract;
            }
            else if ((kind & EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindSealed) != 0)
            {
                flags |= ModifierFlags.Sealed;
            }

            // Can have new in combination with the above flags
            if ((kind & EnvDTE80.vsCMInheritanceKind.vsCMInheritanceKindNew) != 0)
            {
                flags |= ModifierFlags.New;
            }
        }

        return member.UpdateModifiers(flags);
    }

    public override EnvDTE80.vsCMOverrideKind GetOverrideKind(SyntaxNode memberNode)
    {
        Debug.Assert(memberNode is BaseMethodDeclarationSyntax or
                     BasePropertyDeclarationSyntax or
                     EventFieldDeclarationSyntax);

        var member = (MemberDeclarationSyntax)memberNode;

        var flags = member.GetModifierFlags();
        var containingType = member.FirstAncestorOrSelf<TypeDeclarationSyntax>();

        var result = EnvDTE80.vsCMOverrideKind.vsCMOverrideKindNone;

        if ((flags & ModifierFlags.Abstract) != 0 || containingType?.Kind() == SyntaxKind.InterfaceDeclaration)
        {
            result |= EnvDTE80.vsCMOverrideKind.vsCMOverrideKindAbstract;
        }

        if ((flags & ModifierFlags.Virtual) != 0 || containingType?.Kind() == SyntaxKind.InterfaceDeclaration)
        {
            result |= EnvDTE80.vsCMOverrideKind.vsCMOverrideKindVirtual;
        }

        if ((flags & ModifierFlags.Override) != 0)
        {
            result |= EnvDTE80.vsCMOverrideKind.vsCMOverrideKindOverride;
        }

        if ((flags & ModifierFlags.New) != 0)
        {
            result |= EnvDTE80.vsCMOverrideKind.vsCMOverrideKindNew;
        }

        if ((flags & ModifierFlags.Sealed) != 0)
        {
            result |= EnvDTE80.vsCMOverrideKind.vsCMOverrideKindSealed;
        }

        return result;
    }

    public override SyntaxNode SetOverrideKind(SyntaxNode memberNode, EnvDTE80.vsCMOverrideKind kind)
    {
        Debug.Assert(memberNode is BaseMethodDeclarationSyntax or
                     BasePropertyDeclarationSyntax or
                     EventFieldDeclarationSyntax);

        // The legacy C# code model sets the MustImplement property here depending on whether the Abstract kind is set
        // TODO(DustinCa): VB implements MustImplement in terms of OverrideKind, should we do the same?
        memberNode = SetMustImplement(memberNode, (kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindAbstract) != 0);

        var member = (MemberDeclarationSyntax)memberNode;
        var flags = member.GetModifierFlags();
        flags &= ~(ModifierFlags.Abstract | ModifierFlags.Virtual | ModifierFlags.Override | ModifierFlags.New | ModifierFlags.Sealed);

        if (member.IsParentKind(SyntaxKind.InterfaceDeclaration))
        {
            if ((kind & (EnvDTE80.vsCMOverrideKind.vsCMOverrideKindOverride | EnvDTE80.vsCMOverrideKind.vsCMOverrideKindSealed)) != 0)
            {
                throw Exceptions.ThrowEInvalidArg();
            }
            else if ((kind & (EnvDTE80.vsCMOverrideKind.vsCMOverrideKindAbstract | EnvDTE80.vsCMOverrideKind.vsCMOverrideKindVirtual)) != 0)
            {
                // Switch these flags off
                kind &= ~(EnvDTE80.vsCMOverrideKind.vsCMOverrideKindAbstract | EnvDTE80.vsCMOverrideKind.vsCMOverrideKindVirtual);
            }
        }

        if (kind != EnvDTE80.vsCMOverrideKind.vsCMOverrideKindNone)
        {
            if ((kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindAbstract) != 0)
            {
                flags |= ModifierFlags.Abstract;
            }

            if ((kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindVirtual) != 0)
            {
                flags |= ModifierFlags.Virtual;
            }

            if ((kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindOverride) != 0)
            {
                flags |= ModifierFlags.Override;
            }

            if ((kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindSealed) != 0)
            {
                flags |= ModifierFlags.Sealed;
            }

            if ((kind & EnvDTE80.vsCMOverrideKind.vsCMOverrideKindNew) != 0)
            {
                flags |= ModifierFlags.New;
            }
        }

        return member.UpdateModifiers(flags);
    }

    public override bool GetIsAbstract(SyntaxNode memberNode, ISymbol symbol)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        var member = (MemberDeclarationSyntax)memberNode;

        return (member.GetModifierFlags() & ModifierFlags.Abstract) != 0;
    }

    public override SyntaxNode SetIsAbstract(SyntaxNode memberNode, bool value)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        var flags = member.GetModifierFlags();

        if (value)
        {
            flags |= ModifierFlags.Abstract;
        }
        else
        {
            flags &= ~ModifierFlags.Abstract;
        }

        return member.UpdateModifiers(flags);
    }

    public override bool GetIsConstant(SyntaxNode variableNode)
    {
        Debug.Assert(variableNode is MemberDeclarationSyntax or
                     VariableDeclaratorSyntax);

        if (variableNode is EnumMemberDeclarationSyntax)
        {
            return true;
        }

        if (GetNodeWithModifiers(variableNode) is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        // C# legacy Code Model returns true for readonly fields as well.
        return (member.GetModifierFlags() & (ModifierFlags.Const | ModifierFlags.ReadOnly)) != 0;
    }

    public override SyntaxNode SetIsConstant(SyntaxNode variableNode, bool value)
    {
        Debug.Assert(variableNode is MemberDeclarationSyntax);

        if (variableNode is EnumMemberDeclarationSyntax)
        {
            if (!value)
            {
                throw Exceptions.ThrowEFail();
            }

            return variableNode;
        }

        if (variableNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        if (GetIsConstant(member) == value)
        {
            return member;
        }

        var flags = member.GetModifierFlags();

        if (value)
        {
            flags |= ModifierFlags.Const;
        }
        else
        {
            flags &= ~(ModifierFlags.Const | ModifierFlags.ReadOnly);
        }

        return member.UpdateModifiers(flags);
    }

    public override bool GetIsDefault(SyntaxNode propertyNode)
    {
        Debug.Assert(propertyNode is BasePropertyDeclarationSyntax);

        if (propertyNode is not BasePropertyDeclarationSyntax property)
        {
            throw Exceptions.ThrowEFail();
        }

        return property.IsKind(SyntaxKind.IndexerDeclaration);
    }

    public override SyntaxNode SetIsDefault(SyntaxNode propertyNode, bool value)
    {
        // The C# legacy Code Model throws this specific exception rather than a COM exception.
        throw new InvalidOperationException();
    }

    public override bool GetIsGeneric(SyntaxNode memberNode)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (GetNodeWithModifiers(memberNode) is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        return member.GetArity() > 0;
    }

    public override bool GetIsPropertyStyleEvent(SyntaxNode eventNode)
    {
        Debug.Assert(eventNode is EventFieldDeclarationSyntax or
                     EventDeclarationSyntax);

        return eventNode is EventDeclarationSyntax;
    }

    public override bool GetIsShared(SyntaxNode memberNode, ISymbol symbol)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax or
                     VariableDeclaratorSyntax);

        if (GetNodeWithModifiers(memberNode) is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        return (member.GetModifierFlags() & ModifierFlags.Static) != 0;
    }

    public override SyntaxNode SetIsShared(SyntaxNode memberNode, bool value)
    {
        Debug.Assert(memberNode is MemberDeclarationSyntax);

        if (memberNode is not MemberDeclarationSyntax member)
        {
            throw Exceptions.ThrowEFail();
        }

        var flags = member.GetModifierFlags();

        if (value)
        {
            flags |= ModifierFlags.Static;
        }
        else
        {
            flags &= ~ModifierFlags.Static;
        }

        return member.UpdateModifiers(flags);
    }

    public override EnvDTE80.vsCMPropertyKind GetReadWrite(SyntaxNode memberNode)
    {
        Debug.Assert(memberNode is BasePropertyDeclarationSyntax);

        if (memberNode is not BasePropertyDeclarationSyntax property)
        {
            throw Exceptions.ThrowEFail();
        }

        var hasGetter = property.AccessorList != null && property.AccessorList.Accessors.Any(SyntaxKind.GetAccessorDeclaration);
        var hasSetter = property.AccessorList != null && property.AccessorList.Accessors.Any(SyntaxKind.SetAccessorDeclaration);

        if (!hasGetter && !hasSetter)
        {
            var expressionBody = property.GetExpressionBody();
            if (expressionBody != null)
            {
                hasGetter = true;
            }
        }

        if (hasGetter && hasSetter)
        {
            return EnvDTE80.vsCMPropertyKind.vsCMPropertyKindReadWrite;
        }
        else if (hasSetter)
        {
            return EnvDTE80.vsCMPropertyKind.vsCMPropertyKindWriteOnly;
        }
        else if (hasGetter)
        {
            return EnvDTE80.vsCMPropertyKind.vsCMPropertyKindReadOnly;
        }
        else
        {
            return EnvDTE80.vsCMPropertyKind.vsCMPropertyKindReadWrite;
        }
    }

    public override SyntaxNode SetType(SyntaxNode node, ITypeSymbol? typeSymbol)
    {
        // The VB implementation of this supports typeSymbol being "null" to mean converting a Function to a Sub. The C# implementation
        // however doesn't support this being null. This was noticed during null annotation of the service itself.
        Contract.ThrowIfNull(typeSymbol);
        Debug.Assert(node is MemberDeclarationSyntax or
                     ParameterSyntax);

        TypeSyntax? oldType;
        if (node is MemberDeclarationSyntax memberDeclaration)
        {
            oldType = memberDeclaration.GetMemberType();
        }
        else if (node is ParameterSyntax parameter)
        {
            oldType = parameter.Type;
        }
        else
        {
            throw Exceptions.ThrowEFail();
        }

        if (oldType == null)
        {
            throw Exceptions.ThrowEFail();
        }

        var typeName = typeSymbol.ToDisplayString(s_setTypeFormat);
        var newType = SyntaxFactory.ParseTypeName(typeName);

        return node.ReplaceNode(oldType, newType);
    }

    private Document Delete(Document document, VariableDeclaratorSyntax node)
    {
        var fieldDeclaration = node.FirstAncestorOrSelf<BaseFieldDeclarationSyntax>();

        Contract.ThrowIfNull(fieldDeclaration);

        // If we won't have anything left, then just delete the whole declaration
        if (fieldDeclaration.Declaration.Variables.Count == 1)
        {
            return Delete(document, fieldDeclaration);
        }
        else
        {
            var newFieldDeclaration = fieldDeclaration.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
            Contract.ThrowIfNull(newFieldDeclaration);

            return document.ReplaceNodeSynchronously(fieldDeclaration, newFieldDeclaration, CancellationToken.None);
        }
    }

    private static Document Delete(Document document, EnumMemberDeclarationSyntax node)
    {
        var enumDeclaration = (EnumDeclarationSyntax)node.Parent!;
        var members = enumDeclaration.Members;
        var newMembers = members.Remove(node);
        var newEnumDeclaration = enumDeclaration.WithMembers(newMembers);

        // If we're removing the last enum member, we may need to move any trailing trivia
        // to the enum member that comes before it.
        var memberIndex = members.IndexOf(node);
        if (memberIndex == members.Count - 1 && newMembers.Count > 0)
        {
            var trailingTrivia = node.GetTrailingTrivia();
            var lastMember = newEnumDeclaration.Members.Last();
            newEnumDeclaration = newEnumDeclaration.ReplaceNode(lastMember, lastMember.WithTrailingTrivia(trailingTrivia));
        }

        return document.ReplaceNodeSynchronously(enumDeclaration, newEnumDeclaration, CancellationToken.None);
    }

    private static Document Delete(Document document, AttributeSyntax node)
    {
        var attributeList = node.FirstAncestorOrSelf<AttributeListSyntax>();
        Contract.ThrowIfNull(attributeList);

        // If we don't have anything left, then just delete the whole attribute list.
        if (attributeList.Attributes.Count == 1)
        {
            var text = document.GetTextSynchronously(CancellationToken.None);

            // Note that we want to keep all leading trivia and delete all trailing trivia.
            var deletionStart = attributeList.SpanStart;
            var deletionEnd = attributeList.FullSpan.End;

            text = text.Replace(TextSpan.FromBounds(deletionStart, deletionEnd), string.Empty);

            return document.WithText(text);
        }
        else
        {
            var newAttributeList = attributeList.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
            Contract.ThrowIfNull(newAttributeList);

            return document.ReplaceNodeSynchronously(attributeList, newAttributeList, CancellationToken.None);
        }
    }

    private static Document Delete(Document document, AttributeArgumentSyntax node)
    {
        var argumentList = node.FirstAncestorOrSelf<AttributeArgumentListSyntax>();
        Contract.ThrowIfNull(argumentList);
        var newArgumentList = argumentList.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
        Contract.ThrowIfNull(newArgumentList);

        return document.ReplaceNodeSynchronously(argumentList, newArgumentList, CancellationToken.None);
    }

    private static Document Delete(Document document, ParameterSyntax node)
    {
        var parameterList = node.FirstAncestorOrSelf<ParameterListSyntax>();
        Contract.ThrowIfNull(parameterList);
        var newParameterList = parameterList.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
        Contract.ThrowIfNull(newParameterList);

        return document.ReplaceNodeSynchronously(parameterList, newParameterList, CancellationToken.None);
    }

    private static Document DeleteMember(Document document, SyntaxNode node)
    {
        var text = document.GetTextSynchronously(CancellationToken.None);

        // We want to delete all the leading trivia from the node back to,
        // but not including:
        //  * the first preprocessor directive
        //  - or -
        //  * the first comment after a white-space only line
        // We also want to delete all the trailing trivia

        var deletionEnd = node.FullSpan.End;

        var deletionStart = node.SpanStart;

        var contiguousEndOfLines = 0;
        foreach (var trivia in node.GetLeadingTrivia().Reverse())
        {
            if (trivia.IsDirective)
            {
                break;
            }

            if (trivia.Kind() == SyntaxKind.EndOfLineTrivia)
            {
                if (contiguousEndOfLines > 0)
                {
                    break;
                }
                else
                {
                    contiguousEndOfLines++;
                }
            }
            else if (trivia.Kind() != SyntaxKind.WhitespaceTrivia)
            {
                contiguousEndOfLines = 0;
            }

            deletionStart = trivia.FullSpan.Start;
        }

        text = text.Replace(TextSpan.FromBounds(deletionStart, deletionEnd), string.Empty);

        return document.WithText(text);
    }

    public override Document Delete(Document document, SyntaxNode node)
        => node.Kind() switch
        {
            SyntaxKind.VariableDeclarator => Delete(document, (VariableDeclaratorSyntax)node),
            SyntaxKind.EnumMemberDeclaration => Delete(document, (EnumMemberDeclarationSyntax)node),
            SyntaxKind.Attribute => Delete(document, (AttributeSyntax)node),
            SyntaxKind.AttributeArgument => Delete(document, (AttributeArgumentSyntax)node),
            SyntaxKind.Parameter => Delete(document, (ParameterSyntax)node),
            _ => DeleteMember(document, node),
        };

    public override string GetMethodXml(SyntaxNode node, SemanticModel semanticModel)
    {
        if (node is not MethodDeclarationSyntax methodDeclaration)
        {
            throw Exceptions.ThrowEUnexpected();
        }

        return MethodXmlBuilder.Generate(methodDeclaration, semanticModel);
    }

    public override string? GetInitExpression(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.EnumMemberDeclaration:
                var enumMemberDeclaration = (EnumMemberDeclarationSyntax)node;
                return enumMemberDeclaration.EqualsValue?.Value.ToString();
            case SyntaxKind.VariableDeclarator:
                var variableDeclarator = (VariableDeclaratorSyntax)node;
                return variableDeclarator.Initializer?.Value.ToString();
            case SyntaxKind.Parameter:
                var parameter = (ParameterSyntax)node;
                return parameter.Default?.Value.ToString();
            default:
                throw Exceptions.ThrowEFail();
        }
    }

    public override SyntaxNode AddInitExpression(SyntaxNode node, string value)
    {
        switch (node.Kind())
        {
            case SyntaxKind.EnumMemberDeclaration:
                {
                    var enumMemberDeclaration = (EnumMemberDeclarationSyntax)node;

                    if (string.IsNullOrWhiteSpace(value))
                    {
                        return enumMemberDeclaration.WithEqualsValue(null);
                    }

                    var expression = SyntaxFactory.ParseExpression(value);
                    var equalsValueClause = enumMemberDeclaration.EqualsValue != null
                        ? enumMemberDeclaration.EqualsValue.WithValue(expression)
                        : SyntaxFactory.EqualsValueClause(expression);

                    return enumMemberDeclaration.WithEqualsValue(equalsValueClause);
                }

            case SyntaxKind.VariableDeclarator:
                {
                    var variableDeclarator = (VariableDeclaratorSyntax)node;

                    if (string.IsNullOrWhiteSpace(value))
                    {
                        return variableDeclarator.WithInitializer(null);
                    }

                    var expression = SyntaxFactory.ParseExpression(value);
                    var equalsValueClause = variableDeclarator.Initializer != null
                        ? variableDeclarator.Initializer.WithValue(expression)
                        : SyntaxFactory.EqualsValueClause(expression);

                    return variableDeclarator.WithInitializer(equalsValueClause);
                }

            case SyntaxKind.Parameter:
                {
                    var parameter = (ParameterSyntax)node;

                    if (string.IsNullOrWhiteSpace(value))
                    {
                        return parameter.WithDefault(null);
                    }

                    var expression = SyntaxFactory.ParseExpression(value);

                    var equalsValueClause = parameter.Default != null
                        ? parameter.Default.WithValue(expression)
                        : SyntaxFactory.EqualsValueClause(expression);

                    return parameter.WithDefault(equalsValueClause);
                }

            default:
                throw Exceptions.ThrowEFail();
        }
    }

    public override CodeGenerationDestination GetDestination(SyntaxNode node)
        => CSharpCodeGenerationHelpers.GetDestination(node);

    protected override Accessibility GetDefaultAccessibility(SymbolKind targetSymbolKind, CodeGenerationDestination destination)
    {
        switch (targetSymbolKind)
        {
            case SymbolKind.Field:
            case SymbolKind.Method:
            case SymbolKind.Property:
            case SymbolKind.Event:
                return Accessibility.Private;

            case SymbolKind.NamedType:
                switch (destination)
                {
                    case CodeGenerationDestination.ClassType:
                    case CodeGenerationDestination.EnumType:
                    case CodeGenerationDestination.InterfaceType:
                    case CodeGenerationDestination.StructType:
                        return Accessibility.Private;
                    default:
                        return Accessibility.Internal;
                }

            default:
                Debug.Fail("Invalid symbol kind: " + targetSymbolKind);
                throw Exceptions.ThrowEFail();
        }
    }

    protected override ITypeSymbol? GetTypeSymbolFromPartialName(string partialName, SemanticModel semanticModel, int position)
    {
        var parsedTypeName = SyntaxFactory.ParseTypeName(partialName);

        return semanticModel.GetSpeculativeTypeInfo(position, parsedTypeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
    }

    public override ITypeSymbol? GetTypeSymbolFromFullName(string fullName, Compilation compilation)
    {
        ITypeSymbol? typeSymbol = compilation.GetTypeByMetadataName(fullName);

        if (typeSymbol == null)
        {
            var parsedTypeName = SyntaxFactory.ParseTypeName(fullName);

            // Check to see if the name we parsed has any skipped text. If it does, don't bother trying to
            // speculatively bind it because we'll likely just get the wrong thing since we found a bunch
            // of non-sensical tokens.

            if (parsedTypeName.ContainsSkippedText)
            {
                return null;
            }

            // If we couldn't get the name, we just grab the first tree in the compilation to
            // speculatively bind at position zero. However, if there *aren't* any trees, we fork the
            // compilation with an empty tree for the purposes of speculative binding.
            //
            // I'm a bad person.

            var tree = compilation.SyntaxTrees.FirstOrDefault();
            if (tree == null)
            {
                tree = s_emptyTree;
                compilation = compilation.AddSyntaxTrees(tree);
            }

            var semanticModel = compilation.GetSemanticModel(tree);
            typeSymbol = semanticModel.GetSpeculativeTypeInfo(0, parsedTypeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
        }

        if (typeSymbol == null)
        {
            Debug.Fail("Could not find type: " + fullName);
            throw new ArgumentException();
        }

        return typeSymbol;
    }

    public override SyntaxNode CreateReturnDefaultValueStatement(ITypeSymbol type)
    {
        return SyntaxFactory.ReturnStatement(
            SyntaxFactory.DefaultExpression(
                SyntaxFactory.ParseTypeName(type.ToDisplayString())));
    }

    protected override int GetAttributeIndexInContainer(SyntaxNode containerNode, Func<SyntaxNode, bool> predicate)
    {
        var attributes = GetAttributeNodes(containerNode).ToArray();

        var index = 0;
        while (index < attributes.Length)
        {
            var attribute = (AttributeSyntax)attributes[index];

            if (predicate(attribute))
            {
                var attributeDeclaration = (AttributeListSyntax)attribute.Parent!;

                // If this attribute is part of a declaration with multiple attributes,
                // make sure to return the index of the last attribute in the declaration.
                if (attributeDeclaration.Attributes.Count > 1)
                {
                    var indexOfAttributeInDeclaration = attributeDeclaration.Attributes.IndexOf(attribute);
                    return index + (attributeDeclaration.Attributes.Count - indexOfAttributeInDeclaration);
                }

                return index + 1;
            }

            index++;
        }

        return -1;
    }

    protected override int GetAttributeArgumentIndexInContainer(SyntaxNode containerNode, Func<SyntaxNode, bool> predicate)
    {
        var attributeArguments = GetAttributeArgumentNodes(containerNode).ToArray();

        for (var index = 0; index < attributeArguments.Length; index++)
        {
            if (predicate(attributeArguments[index]))
            {
                return index + 1;
            }
        }

        return -1;
    }

    protected override int GetImportIndexInContainer(SyntaxNode containerNode, Func<SyntaxNode, bool> predicate)
    {
        var imports = GetImportNodes(containerNode).ToArray();

        for (var index = 0; index < imports.Length; index++)
        {
            if (predicate(imports[index]))
            {
                return index + 1;
            }
        }

        return -1;
    }

    protected override int GetParameterIndexInContainer(SyntaxNode containerNode, Func<SyntaxNode, bool> predicate)
    {
        var parameters = GetParameterNodes(containerNode).ToArray();

        for (var index = 0; index < parameters.Length; index++)
        {
            if (predicate(parameters[index]))
            {
                return index + 1;
            }
        }

        return -1;
    }

    protected override int GetMemberIndexInContainer(SyntaxNode containerNode, Func<SyntaxNode, bool> predicate)
    {
        var members = GetLogicalMemberNodes(containerNode).ToArray();

        var index = 0;
        while (index < members.Length)
        {
            var member = members[index];
            if (predicate(member))
            {
                // If a variable declarator was specified, make sure we return
                // the index of the last variable declarator in the parenting field declaration.
                if (member is VariableDeclaratorSyntax variableDeclarator)
                {
                    var variableDeclaration = (VariableDeclarationSyntax)member.Parent!;
                    var indexOfDeclaratorInField = variableDeclaration.Variables.IndexOf(variableDeclarator);
                    return index + (variableDeclaration.Variables.Count - indexOfDeclaratorInField);
                }

                // Note: we always return the item *after* this index.
                return index + 1;
            }

            index++;
        }

        return -1;
    }

    protected override SyntaxNode GetFieldFromVariableNode(SyntaxNode node)
    {
        return node.Kind() == SyntaxKind.VariableDeclarator
            ? node.GetAncestors<BaseFieldDeclarationSyntax>().First()
            : node;
    }

    protected override SyntaxNode GetVariableFromFieldNode(SyntaxNode finalNode)
    {
        // Work around the fact that code model really deals in terms of variable declarators
        return finalNode is BaseFieldDeclarationSyntax
            ? ((BaseFieldDeclarationSyntax)finalNode).Declaration.Variables.Single()
            : finalNode;
    }

    protected override SyntaxNode GetAttributeFromAttributeDeclarationNode(SyntaxNode node)
    {
        return node is AttributeListSyntax
            ? ((AttributeListSyntax)node).Attributes.First()
            : node;
    }

    protected override TextSpan GetSpanToFormat(SyntaxNode root, TextSpan span)
    {
        var startToken = root.FindToken(span.Start).GetPreviousToken();
        if (startToken.Kind() == SyntaxKind.OpenBraceToken)
        {
            startToken = startToken.GetPreviousToken();
        }

        var endToken = root.FindToken(span.End).GetNextToken();
        if (endToken.Kind() == SyntaxKind.CloseBraceToken)
        {
            endToken = endToken.GetPreviousToken();
        }

        startToken = GetTokenWithoutAnnotation(startToken, t => t.GetPreviousToken());
        endToken = GetTokenWithoutAnnotation(endToken, t => t.GetNextToken());

        return GetEncompassingSpan(root, startToken, endToken);
    }

    private static SyntaxNode InsertMemberNodeIntoContainerCore(int index, SyntaxNode member, SyntaxNode container)
    {
        if (container is CompilationUnitSyntax compilationUnit)
        {
            var newMembers = compilationUnit.Members.Insert(index, (MemberDeclarationSyntax)member);
            return compilationUnit.WithMembers(newMembers);
        }
        else if (container is BaseNamespaceDeclarationSyntax namespaceDeclaration)
        {
            var newMembers = namespaceDeclaration.Members.Insert(index, (MemberDeclarationSyntax)member);
            return namespaceDeclaration.WithMembers(newMembers);
        }
        else if (container is TypeDeclarationSyntax typeDeclaration)
        {
            var newMembers = typeDeclaration.Members.Insert(index, (MemberDeclarationSyntax)member);
            return typeDeclaration.WithMembers(newMembers);
        }
        else if (container is EnumDeclarationSyntax enumDeclaration)
        {
            // If we're inserting at the end of the list of enum members, we may need to strip the trailing
            // line from the last enum member and add it to the separator that comes after it.
            if (index > 0 && index == enumDeclaration.Members.Count)
            {
                var lastMember = enumDeclaration.Members[index - 1];
                var trailingTrivia = lastMember.GetTrailingTrivia();
                enumDeclaration = enumDeclaration.ReplaceNode(lastMember, lastMember.WithTrailingTrivia(SyntaxTriviaList.Empty));

                var newMembers = enumDeclaration.Members.Insert(index, (EnumMemberDeclarationSyntax)member);
                enumDeclaration = enumDeclaration.WithMembers(newMembers);

                var separator = enumDeclaration.Members.GetSeparator(index - 1);
                return enumDeclaration.ReplaceToken(separator, separator.WithTrailingTrivia(trailingTrivia));
            }
            else
            {
                var newMembers = enumDeclaration.Members.Insert(index, (EnumMemberDeclarationSyntax)member);
                return enumDeclaration.WithMembers(newMembers);
            }
        }

        throw Exceptions.ThrowEFail();
    }

    private static MemberDeclarationSyntax GetMember(SyntaxNode container, int index)
    {
        if (container is CompilationUnitSyntax compilationUnit)
        {
            return compilationUnit.Members[index];
        }
        else if (container is BaseNamespaceDeclarationSyntax namespaceDeclaration)
        {
            return namespaceDeclaration.Members[index];
        }
        else if (container is TypeDeclarationSyntax typeDeclaration)
        {
            return typeDeclaration.Members[index];
        }
        else if (container is EnumDeclarationSyntax enumDeclaration)
        {
            return enumDeclaration.Members[index];
        }

        throw Exceptions.ThrowEFail();
    }

    private static SyntaxNode EnsureAfterEndRegion(int index, SyntaxNode container)
    {
        // If the next token after our member has only whitespace and #endregion as leading
        // trivia, we'll move that to be leading trivia of our member.

        var newContainer = container;
        var newMember = GetMember(newContainer, index);

        var lastToken = newMember.GetLastToken();
        var nextToken = lastToken.GetNextToken();

        var triviaList = nextToken.LeadingTrivia;

        var lastNonWhitespaceTrivia = triviaList.LastOrDefault(trivia => !trivia.IsWhitespaceOrEndOfLine());
        if (lastNonWhitespaceTrivia.Kind() == SyntaxKind.EndRegionDirectiveTrivia)
        {
            newContainer = newContainer
                .ReplaceToken(nextToken, nextToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

            newMember = GetMember(newContainer, index);
            var firstToken = newMember.GetFirstToken();

            newContainer = newContainer
                .ReplaceToken(firstToken, firstToken.WithLeadingTrivia(triviaList));
        }

        return newContainer;
    }

    protected override SyntaxNode InsertMemberNodeIntoContainer(int index, SyntaxNode member, SyntaxNode container)
    {
        var newContainer = InsertMemberNodeIntoContainerCore(index, member, container);

        newContainer = EnsureAfterEndRegion(index, newContainer);

        return newContainer;
    }

    protected override SyntaxNode InsertAttributeArgumentIntoContainer(int index, SyntaxNode attributeArgument, SyntaxNode container)
    {
        if (container is AttributeSyntax attribute)
        {
            var argumentList = attribute.ArgumentList;

            AttributeArgumentListSyntax newArgumentList;

            if (argumentList == null)
            {
                newArgumentList = SyntaxFactory.AttributeArgumentList([(AttributeArgumentSyntax)attributeArgument]);
            }
            else
            {
                var newArguments = argumentList.Arguments.Insert(index, (AttributeArgumentSyntax)attributeArgument);
                newArgumentList = argumentList.WithArguments(newArguments);
            }

            return attribute.WithArgumentList(newArgumentList);
        }

        throw Exceptions.ThrowEFail();
    }

    protected override SyntaxNode InsertAttributeListIntoContainer(int index, SyntaxNode list, SyntaxNode container)
    {
        // If the attribute list is being inserted at the first index and the container is not the compilation unit, copy leading trivia
        // to the list that is being inserted.
        if (index == 0 && !(container is CompilationUnitSyntax))
        {
            var firstToken = container.GetFirstToken();
            if (firstToken.HasLeadingTrivia)
            {
                var trivia = firstToken.LeadingTrivia;

                container = container.ReplaceToken(firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));
                list = list.WithLeadingTrivia(trivia);
            }
        }

        if (container is CompilationUnitSyntax compilationUnit)
        {
            var newAttributeLists = compilationUnit.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return compilationUnit.WithAttributeLists(newAttributeLists);
        }
        else if (container is EnumDeclarationSyntax enumDeclaration)
        {
            var newAttributeLists = enumDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return enumDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is ClassDeclarationSyntax classDeclaration)
        {
            var newAttributeLists = classDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return classDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is StructDeclarationSyntax structDeclaration)
        {
            var newAttributeLists = structDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return structDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is InterfaceDeclarationSyntax interfaceDeclaration)
        {
            var newAttributeLists = interfaceDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return interfaceDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is MethodDeclarationSyntax method)
        {
            var newAttributeLists = method.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return method.WithAttributeLists(newAttributeLists);
        }
        else if (container is OperatorDeclarationSyntax operationDeclaration)
        {
            var newAttributeLists = operationDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return operationDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is ConversionOperatorDeclarationSyntax conversion)
        {
            var newAttributeLists = conversion.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return conversion.WithAttributeLists(newAttributeLists);
        }
        else if (container is ConstructorDeclarationSyntax constructor)
        {
            var newAttributeLists = constructor.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return constructor.WithAttributeLists(newAttributeLists);
        }
        else if (container is DestructorDeclarationSyntax destructor)
        {
            var newAttributeLists = destructor.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return destructor.WithAttributeLists(newAttributeLists);
        }
        else if (container is PropertyDeclarationSyntax property)
        {
            var newAttributeLists = property.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return property.WithAttributeLists(newAttributeLists);
        }
        else if (container is EventDeclarationSyntax eventDeclaration)
        {
            var newAttributeLists = eventDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return eventDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is IndexerDeclarationSyntax indexer)
        {
            var newAttributeLists = indexer.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return indexer.WithAttributeLists(newAttributeLists);
        }
        else if (container is FieldDeclarationSyntax field)
        {
            var newAttributeLists = field.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return field.WithAttributeLists(newAttributeLists);
        }
        else if (container is EventFieldDeclarationSyntax eventFieldDeclaration)
        {
            var newAttributeLists = eventFieldDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return eventFieldDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is DelegateDeclarationSyntax delegateDeclaration)
        {
            var newAttributeLists = delegateDeclaration.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return delegateDeclaration.WithAttributeLists(newAttributeLists);
        }
        else if (container is EnumMemberDeclarationSyntax member)
        {
            var newAttributeLists = member.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return member.WithAttributeLists(newAttributeLists);
        }
        else if (container is ParameterSyntax parameter)
        {
            var newAttributeLists = parameter.AttributeLists.Insert(index, (AttributeListSyntax)list);
            return parameter.WithAttributeLists(newAttributeLists);
        }
        else if (container is VariableDeclaratorSyntax or VariableDeclarationSyntax)
        {
            return InsertAttributeListIntoContainer(index, list, container.Parent!);
        }

        throw Exceptions.ThrowEUnexpected();
    }

    protected override SyntaxNode InsertImportIntoContainer(int index, SyntaxNode importNode, SyntaxNode container)
    {
        var import = (UsingDirectiveSyntax)importNode;

        if (container is CompilationUnitSyntax compilationUnit)
        {
            var usingsList = compilationUnit.Usings.Insert(index, import);
            return compilationUnit.WithUsings(usingsList);
        }

        throw Exceptions.ThrowEUnexpected();
    }

    protected override SyntaxNode InsertParameterIntoContainer(int index, SyntaxNode parameter, SyntaxNode container)
    {
        if (container is BaseMethodDeclarationSyntax method)
        {
            var parameterList = method.ParameterList.Parameters.Insert(index, (ParameterSyntax)parameter);
            return method.WithParameterList(method.ParameterList.WithParameters(parameterList));
        }
        else if (container is IndexerDeclarationSyntax indexer)
        {
            var parameterList = indexer.ParameterList.Parameters.Insert(index, (ParameterSyntax)parameter);
            return indexer.WithParameterList(indexer.ParameterList.WithParameters(parameterList));
        }
        else if (container is DelegateDeclarationSyntax delegateDeclaration)
        {
            var parameterList = delegateDeclaration.ParameterList.Parameters.Insert(index, (ParameterSyntax)parameter);
            return delegateDeclaration.WithParameterList(delegateDeclaration.ParameterList.WithParameters(parameterList));
        }

        throw Exceptions.ThrowEUnexpected();
    }

    protected override bool IsCodeModelNode(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.CompilationUnit:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.DelegateDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.EnumDeclaration:
            case SyntaxKind.EnumMemberDeclaration:
            case SyntaxKind.FieldDeclaration:
            case SyntaxKind.IndexerDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.PropertyDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.UsingDirective:
                return true;

            default:
                return false;
        }
    }

    public override bool IsNamespace(SyntaxNode node)
        => node is BaseNamespaceDeclarationSyntax;

    public override bool IsType(SyntaxNode node)
    {
        return node?.Kind()
            is SyntaxKind.ClassDeclaration
            or SyntaxKind.InterfaceDeclaration
            or SyntaxKind.StructDeclaration
            or SyntaxKind.EnumDeclaration
            or SyntaxKind.DelegateDeclaration;
    }

    private static bool IsAutoImplementedProperty(PropertyDeclarationSyntax propertyDeclaration)
    {
        if (propertyDeclaration.IsParentKind(SyntaxKind.InterfaceDeclaration))
        {
            return false;
        }

        var modifierFlags = propertyDeclaration.GetModifierFlags();
        if ((modifierFlags & ModifierFlags.Abstract) != 0 ||
            (modifierFlags & ModifierFlags.Extern) != 0)
        {
            return false;
        }

        if (propertyDeclaration.AccessorList == null)
        {
            return false;
        }

        AccessorDeclarationSyntax? getAccessor = null;
        AccessorDeclarationSyntax? setAccessor = null;

        foreach (var accessor in propertyDeclaration.AccessorList.Accessors)
        {
            switch (accessor.Kind())
            {
                case SyntaxKind.GetAccessorDeclaration:
                    getAccessor ??= accessor;

                    break;
                case SyntaxKind.SetAccessorDeclaration:
                    setAccessor ??= accessor;

                    break;
            }
        }

        if (getAccessor == null || setAccessor == null)
        {
            return false;
        }

        return getAccessor.Body == null && setAccessor.Body == null;
    }

    private static bool IsExtensionMethod(MethodDeclarationSyntax methodDeclaration)
    {
        if (methodDeclaration?.Parent is not ClassDeclarationSyntax classDecl ||
            !classDecl.Modifiers.Any(SyntaxKind.StaticKeyword))
        {
            return false;
        }

        if (methodDeclaration.ParameterList == null ||
            methodDeclaration.ParameterList.Parameters.Count == 0)
        {
            return false;
        }

        return methodDeclaration.ParameterList.Parameters[0].Modifiers.Any(SyntaxKind.ThisKeyword);
    }

    private static bool IsPartialMethod(MethodDeclarationSyntax methodDeclaration)
        => methodDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword);

    public override string[] GetFunctionExtenderNames()
        => [ExtenderNames.ExtensionMethod, ExtenderNames.PartialMethod];

    public override object GetFunctionExtender(string name, SyntaxNode node, ISymbol symbol)
    {
        if (node == null || node.Kind() != SyntaxKind.MethodDeclaration ||
            symbol == null || symbol.Kind != SymbolKind.Method)
        {
            throw Exceptions.ThrowEUnexpected();
        }

        if (StringComparer.Ordinal.Equals(name, ExtenderNames.PartialMethod))
        {
            var methodDeclaration = (MethodDeclarationSyntax)node;
            var isPartial = IsPartialMethod(methodDeclaration);
            var isDeclaration = false;
            var hasOtherPart = false;

            if (isPartial)
            {
                var methodSymbol = (IMethodSymbol)symbol;
                isDeclaration = methodSymbol.PartialDefinitionPart == null;
                hasOtherPart = isDeclaration
                    ? methodSymbol.PartialImplementationPart != null
                    : methodSymbol.PartialDefinitionPart != null;
            }

            return PartialMethodExtender.Create(isPartial, isDeclaration, hasOtherPart);
        }
        else if (StringComparer.Ordinal.Equals(name, ExtenderNames.ExtensionMethod))
        {
            var methodDeclaration = (MethodDeclarationSyntax)node;
            var isExtension = IsExtensionMethod(methodDeclaration);

            return ExtensionMethodExtender.Create(isExtension);
        }

        throw Exceptions.ThrowEFail();
    }

    public override string[] GetPropertyExtenderNames()
        => [ExtenderNames.AutoImplementedProperty];

    public override object GetPropertyExtender(string name, SyntaxNode node, ISymbol symbol)
    {
        if (node == null || node.Kind() != SyntaxKind.PropertyDeclaration ||
            symbol == null || symbol.Kind != SymbolKind.Property)
        {
            throw Exceptions.ThrowEUnexpected();
        }

        if (StringComparer.Ordinal.Equals(name, ExtenderNames.AutoImplementedProperty))
        {
            var propertyDeclaration = (PropertyDeclarationSyntax)node;
            var isAutoImplemented = IsAutoImplementedProperty(propertyDeclaration);

            return AutoImplementedPropertyExtender.Create(isAutoImplemented);
        }

        throw Exceptions.ThrowEFail();
    }

    public override string[] GetExternalTypeExtenderNames()
        => [ExtenderNames.ExternalLocation];

    public override object GetExternalTypeExtender(string name, string externalLocation)
    {
        Debug.Assert(externalLocation != null);

        if (StringComparer.Ordinal.Equals(name, ExtenderNames.ExternalLocation))
        {
            return CodeTypeLocationExtender.Create(externalLocation);
        }

        throw Exceptions.ThrowEFail();
    }

    public override string[] GetTypeExtenderNames()
        => [];

    public override object GetTypeExtender(string name, AbstractCodeType symbol)
        => throw Exceptions.ThrowEFail();

    protected override bool AddBlankLineToMethodBody(SyntaxNode node, SyntaxNode newNode)
    {
        return node is MethodDeclarationSyntax methodDeclaration
            && methodDeclaration.Body == null
            && newNode is MethodDeclarationSyntax newMethodDeclaration
            && newMethodDeclaration.Body != null;
    }

    private static TypeDeclarationSyntax InsertIntoBaseList(TypeDeclarationSyntax typeDeclaration, ITypeSymbol typeSymbol, SemanticModel semanticModel, int insertionIndex)
    {
        var position = typeDeclaration.SpanStart;
        var identifier = typeDeclaration.Identifier;
        if (identifier.HasTrailingTrivia)
        {
            typeDeclaration = typeDeclaration.WithIdentifier(
                identifier.WithTrailingTrivia(identifier.TrailingTrivia.SkipWhile(t => t.IsWhitespaceOrEndOfLine())));
        }

        var typeName = SyntaxFactory.ParseTypeName(typeSymbol.ToMinimalDisplayString(semanticModel, position));
        var baseList = typeDeclaration.BaseList != null
            ? typeDeclaration.BaseList.WithTypes(typeDeclaration.BaseList.Types.Insert(insertionIndex, SyntaxFactory.SimpleBaseType(typeName)))
            : SyntaxFactory.BaseList([SyntaxFactory.SimpleBaseType(typeName)]);

        return typeDeclaration.WithBaseList(baseList);
    }

    public override bool IsValidBaseType(SyntaxNode node, ITypeSymbol typeSymbol)
    {
        if (node.IsKind(SyntaxKind.ClassDeclaration))
        {
            return typeSymbol.TypeKind == TypeKind.Class;
        }
        else if (node.IsKind(SyntaxKind.InterfaceDeclaration))
        {
            return typeSymbol.TypeKind == TypeKind.Interface;
        }

        return false;
    }

    public override SyntaxNode AddBase(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel, int? position)
    {
        if (node.Kind() is not (SyntaxKind.ClassDeclaration or SyntaxKind.InterfaceDeclaration))
        {
            throw Exceptions.ThrowEFail();
        }

        var typeDeclaration = (TypeDeclarationSyntax)node;
        var baseCount = typeDeclaration.BaseList != null
            ? typeDeclaration.BaseList.Types.Count
            : 0;

        int insertionIndex;
        if (typeDeclaration.IsKind(SyntaxKind.ClassDeclaration))
        {
            insertionIndex = 0;
        }
        else if (position != null)
        {
            insertionIndex = position.Value;
            if (insertionIndex > baseCount)
            {
                throw Exceptions.ThrowEInvalidArg();
            }
        }
        else
        {
            insertionIndex = baseCount;
        }

        return InsertIntoBaseList(typeDeclaration, typeSymbol, semanticModel, insertionIndex);
    }

    public override SyntaxNode RemoveBase(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel)
    {
        if (node.Kind() is not (SyntaxKind.ClassDeclaration or SyntaxKind.InterfaceDeclaration))
        {
            throw Exceptions.ThrowEFail();
        }

        var typeDeclaration = (TypeDeclarationSyntax)node;
        if (typeDeclaration.BaseList == null ||
            typeDeclaration.BaseList.Types.Count == 0)
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var isFirst = true;
        BaseTypeSyntax? baseType = null;

        foreach (var type in typeDeclaration.BaseList.Types)
        {
            if (!isFirst && node.IsKind(SyntaxKind.ClassDeclaration))
            {
                break;
            }

            var typeInfo = semanticModel.GetTypeInfo(type.Type, CancellationToken.None);
            if (typeInfo.Type != null &&
                typeInfo.Type.Equals(typeSymbol))
            {
                baseType = type;
                break;
            }

            isFirst = false;
        }

        if (baseType == null)
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var newTypes = typeDeclaration.BaseList.Types.Remove(baseType);
        var newBaseList = typeDeclaration.BaseList.WithTypes(newTypes);
        if (newBaseList.Types.Count == 0)
        {
            newBaseList = null;
        }

        return typeDeclaration.WithBaseList(newBaseList);
    }

    public override bool IsValidInterfaceType(SyntaxNode node, ITypeSymbol typeSymbol)
    {
        if (node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration)
        {
            return typeSymbol.TypeKind == TypeKind.Interface;
        }

        return false;
    }

    public override SyntaxNode AddImplementedInterface(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel, int? position)
    {
        if (node.Kind() is not (SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration))
        {
            throw Exceptions.ThrowEFail();
        }

        if (typeSymbol.Kind != SymbolKind.NamedType ||
            typeSymbol.TypeKind != TypeKind.Interface)
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var typeDeclaration = (TypeDeclarationSyntax)node;
        var baseCount = typeDeclaration.BaseList != null
            ? typeDeclaration.BaseList.Types.Count
            : 0;

        int insertionIndex;
        if (position != null)
        {
            insertionIndex = position.Value;
            if (insertionIndex > baseCount)
            {
                throw Exceptions.ThrowEInvalidArg();
            }
        }
        else
        {
            insertionIndex = baseCount;
        }

        return InsertIntoBaseList(typeDeclaration, typeSymbol, semanticModel, insertionIndex);
    }

    public override SyntaxNode RemoveImplementedInterface(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel)
    {
        if (node.Kind() is not (SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration))
        {
            throw Exceptions.ThrowEFail();
        }

        var typeDeclaration = (TypeDeclarationSyntax)node;
        if (typeDeclaration.BaseList == null ||
            typeDeclaration.BaseList.Types.Count == 0)
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        BaseTypeSyntax? baseType = null;
        foreach (var type in typeDeclaration.BaseList.Types)
        {
            var typeInfo = semanticModel.GetTypeInfo(type.Type, CancellationToken.None);
            if (typeInfo.Type != null &&
                typeInfo.Type.Equals(typeSymbol))
            {
                baseType = type;
                break;
            }
        }

        if (baseType == null)
        {
            throw Exceptions.ThrowEInvalidArg();
        }

        var newTypes = typeDeclaration.BaseList.Types.Remove(baseType);
        var newBaseList = typeDeclaration.BaseList.WithTypes(newTypes);
        if (newBaseList.Types.Count == 0)
        {
            newBaseList = null;
        }

        return typeDeclaration.WithBaseList(newBaseList);
    }
}
