// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Immutable;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.Shared.Extensions;

#if CODE_STYLE
using Microsoft.CodeAnalysis.Internal.Editing;
#else
using Microsoft.CodeAnalysis.Editing;
#endif

namespace Microsoft.CodeAnalysis.CodeGeneration
{
    internal abstract class CodeGenerationSymbol : ISymbol
    {
        protected static ConditionalWeakTable<CodeGenerationSymbol, SyntaxAnnotation[]> annotationsTable = new();

        private readonly ImmutableArray<AttributeData> _attributes;
        protected readonly string _documentationCommentXml;

        public Accessibility DeclaredAccessibility { get; }
        protected internal DeclarationModifiers Modifiers { get; }
        public string Name { get; }
        public INamedTypeSymbol ContainingType { get; protected set; }

        protected CodeGenerationSymbol(
            IAssemblySymbol containingAssembly,
            INamedTypeSymbol containingType,
            ImmutableArray<AttributeData> attributes,
            Accessibility declaredAccessibility,
            DeclarationModifiers modifiers,
            string name,
            string documentationCommentXml = null)
        {
            this.ContainingAssembly = containingAssembly;
            this.ContainingType = containingType;
            _attributes = attributes.NullToEmpty();
            this.DeclaredAccessibility = declaredAccessibility;
            this.Modifiers = modifiers;
            this.Name = name;
            _documentationCommentXml = documentationCommentXml;
        }

        protected abstract CodeGenerationSymbol Clone();

        internal SyntaxAnnotation[] GetAnnotations()
        {
            annotationsTable.TryGetValue(this, out var annotations);
            return annotations ?? Array.Empty<SyntaxAnnotation>();
        }

        internal CodeGenerationSymbol WithAdditionalAnnotations(params SyntaxAnnotation[] annotations)
        {
            return annotations.IsNullOrEmpty()
                ? this
                : AddAnnotationsTo(this, this.Clone(), annotations);
        }

        private static CodeGenerationSymbol AddAnnotationsTo(
            CodeGenerationSymbol originalDefinition, CodeGenerationSymbol newDefinition, SyntaxAnnotation[] annotations)
        {
            annotationsTable.TryGetValue(originalDefinition, out var originalAnnotations);

            annotations = SyntaxAnnotationExtensions.CombineAnnotations(originalAnnotations, annotations);
            annotationsTable.Add(newDefinition, annotations);

            return newDefinition;
        }

        public abstract SymbolKind Kind { get; }

        public string Language => "Code Generation Agnostic Language";

        public virtual ISymbol ContainingSymbol => null;

        public IAssemblySymbol ContainingAssembly { get; }

        public static IMethodSymbol ContainingMethod => null;

        public IModuleSymbol ContainingModule => null;

        public INamespaceSymbol ContainingNamespace => null;

        public bool IsDefinition => true;

        public bool IsStatic
        {
            get
            {
                return this.Modifiers.IsStatic;
            }
        }

        public bool IsVirtual
        {
            get
            {
                return this.Modifiers.IsVirtual;
            }
        }

        public bool IsOverride
        {
            get
            {
                return this.Modifiers.IsOverride;
            }
        }

        public bool IsAbstract
        {
            get
            {
                return this.Modifiers.IsAbstract;
            }
        }

        public bool IsSealed
        {
            get
            {
                return this.Modifiers.IsSealed;
            }
        }

        public bool IsExtern => false;

        public bool IsImplicitlyDeclared => false;

        public bool CanBeReferencedByName => true;

        public ImmutableArray<Location> Locations
        {
            get
            {
                return ImmutableArray.Create<Location>();
            }
        }

        public static ImmutableArray<SyntaxNode> DeclaringSyntaxNodes
        {
            get
            {
                return ImmutableArray.Create<SyntaxNode>();
            }
        }

        public ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
        {
            get
            {
                return ImmutableArray.Create<SyntaxReference>();
            }
        }

        public ImmutableArray<AttributeData> GetAttributes()
            => _attributes;

        public ImmutableArray<AttributeData> GetAttributes(INamedTypeSymbol attributeType)
            => GetAttributes().WhereAsArray(a => a.AttributeClass.Equals(attributeType));

        public ImmutableArray<AttributeData> GetAttributes(IMethodSymbol attributeConstructor)
            => GetAttributes().WhereAsArray(a => a.AttributeConstructor.Equals(attributeConstructor));

        public ISymbol OriginalDefinition
        {
            get
            {
                return this;
            }
        }

        public abstract void Accept(SymbolVisitor visitor);

        public abstract TResult Accept<TResult>(SymbolVisitor<TResult> visitor);

        public abstract TResult Accept<TArgument, TResult>(SymbolVisitor<TArgument, TResult> visitor, TArgument argument);

        public string GetDocumentationCommentId()
            => null;

        public string GetDocumentationCommentXml(
            CultureInfo preferredCulture,
            bool expandIncludes,
            CancellationToken cancellationToken)
        {
            return _documentationCommentXml ?? "";
        }

        public string ToDisplayString(SymbolDisplayFormat format = null)
            => throw new NotImplementedException();

        public ImmutableArray<SymbolDisplayPart> ToDisplayParts(SymbolDisplayFormat format = null)
            => throw new NotImplementedException();

        public string ToMinimalDisplayString(SemanticModel semanticModel, int position, SymbolDisplayFormat format = null)
            => throw new NotImplementedException();

        public ImmutableArray<SymbolDisplayPart> ToMinimalDisplayParts(SemanticModel semanticModel, int position, SymbolDisplayFormat format = null)
            => throw new NotImplementedException();

        public virtual string MetadataName
        {
            get
            {
                return this.Name;
            }
        }

        public int MetadataToken => 0;

        public bool HasUnsupportedMetadata => false;

        public bool Equals(ISymbol other)
            => this.Equals((object)other);

        public bool Equals(ISymbol other, SymbolEqualityComparer equalityComparer)
            => this.Equals(other);
    }
}
