// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.TypeSpec.Generator.EmitterRpc;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.SourceInput;

namespace Microsoft.TypeSpec.Generator
{
    /// <summary>
    /// Base class for code model generators. This class is exported via MEF and can be implemented by an
    /// inherited generator class.
    /// </summary>
    [InheritedExport]
    [Export(typeof(CodeModelGenerator))]
    [ExportMetadata(GeneratorMetadataName, nameof(CodeModelGenerator))]
    public abstract class CodeModelGenerator
    {
        private List<LibraryVisitor> _visitors = [];
        private List<MetadataReference> _additionalMetadataReferences = [];
        private static CodeModelGenerator? _instance;
        private List<string> _sharedSourceDirectories = [];
        public const string GeneratorMetadataName = "GeneratorName";

        /// <summary>
        /// The fixed namespace used for CodeGen customization attributes.
        /// Using a fixed namespace avoids API compatibility failures when the project namespace changes.
        /// </summary>
        internal const string CustomizationAttributeNamespace = "Microsoft.TypeSpec.Generator.Customizations";

        internal Stopwatch Stopwatch { get; } = new Stopwatch();
        public static CodeModelGenerator Instance
        {
            get
            {
                return _instance ?? throw new InvalidOperationException("CodeModelGenerator is not initialized");
            }
            internal set
            {
                _instance = value;
            }
        }

        public Configuration Configuration { get; }

        public IReadOnlyList<LibraryVisitor> Visitors => _visitors;

        public IReadOnlyList<LibraryRewriter> Rewriters => _rewriters;

        [ImportingConstructor]
        public CodeModelGenerator(GeneratorContext context)
        {
            Configuration = context.Configuration;
            _inputLibrary = new InputLibrary(Configuration.OutputDirectory);
            TypeFactory = new TypeFactory();
            Emitter = new Emitter(Console.OpenStandardOutput());
        }

        // for mocking
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        protected CodeModelGenerator()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
        {
        }

        internal bool IsNewProject { get; set; }
        private InputLibrary _inputLibrary;

        public virtual Emitter Emitter { get; }

        // Extensibility points to be implemented by a generator
        public virtual TypeFactory TypeFactory { get; }

        private SourceInputModel? _sourceInputModel;
        private List<LibraryRewriter> _rewriters = [];

        public virtual SourceInputModel SourceInputModel
        {
            get => _sourceInputModel ?? throw new InvalidOperationException($"SourceInputModel has not been initialized yet");
            internal set
            {
                _sourceInputModel = value;
            }
        }

        public string LicenseHeader => Configuration.LicenseInfo?.Header ?? string.Empty;
        public virtual OutputLibrary OutputLibrary { get; } = new();
        public virtual InputLibrary InputLibrary => _inputLibrary;
        public virtual TypeProviderWriter GetWriter(TypeProvider provider) => new(provider);
        public IReadOnlyList<MetadataReference> AdditionalMetadataReferences => _additionalMetadataReferences;

        public IReadOnlyList<string> SharedSourceDirectories => _sharedSourceDirectories;

        internal IReadOnlyList<TypeProvider> CustomCodeAttributeProviders { get; } =
        [
            new CodeGenTypeAttributeDefinition(),
            new CodeGenMemberAttributeDefinition(),
            new CodeGenSuppressAttributeDefinition(),
            new CodeGenSerializationAttributeDefinition()
        ];

        protected internal virtual void Configure()
        {
            if (string.IsNullOrEmpty(Configuration.PackageName))
            {
                Configuration.PackageName = TypeFactory.PrimaryNamespace;
                Emitter.Info($"'package-name' was not specified. Defaulting to namespace '{Configuration.PackageName}'.");
            }

            foreach (var type in CustomCodeAttributeProviders)
            {
                AddTypeToKeep(type);
            }
        }

        public virtual void AddVisitor(LibraryVisitor visitor)
        {
            _visitors.Add(visitor);
        }

        /// <summary>
        /// Removes all visitors of the specified type from the list of visitors.
        /// </summary>
        /// <typeparam name="T">The type of visitor to remove.</typeparam>
        public virtual void RemoveVisitor<T>() where T : LibraryVisitor
        {
            _visitors.RemoveAll(v => v.GetType() == typeof(T));
        }

        /// <summary>
        /// Removes all visitors whose type name matches the specified name from the list of visitors.
        /// This overload is useful when the visitor type is not publicly accessible.
        /// </summary>
        /// <param name="visitorTypeName">The name of the visitor type to remove.</param>
        public virtual void RemoveVisitor(string visitorTypeName)
        {
            _visitors.RemoveAll(v => v.GetType().Name == visitorTypeName);
        }

        public virtual void AddRewriter(LibraryRewriter rewriter)
        {
            _rewriters.Add(rewriter);
        }

        public virtual void AddMetadataReference(MetadataReference reference)
        {
            _additionalMetadataReferences.Add(reference);
        }

        public virtual void AddSharedSourceDirectory(string sharedSourceDirectory)
        {
            _sharedSourceDirectories.Add(sharedSourceDirectory);
        }

        internal HashSet<string> AdditionalRootTypes { get; } = [];

        internal HashSet<string> NonRootTypes { get; } = [];

        /// <summary>
        /// Adds a type to the list of types to keep.
        /// </summary>
        /// <param name="typeName">Either a fully qualified type name or simple type name.</param>
        /// <param name="isRoot">Whether to treat the type as a root type. Any dependencies of root types will
        /// not have their accessibility changed regardless of the 'unreferenced-types-handling' value.</param>
        public void AddTypeToKeep(string typeName, bool isRoot = true)
        {
            if (isRoot)
            {
                AdditionalRootTypes.Add(typeName);
            }
            else
            {
                NonRootTypes.Add(typeName);
            }
        }

        /// <summary>
        /// Adds a type to the list of types to keep.
        /// </summary>
        /// <param name="type">The type provider representing the type.</param>
        /// <param name="isRoot">Whether to treat the type as a root type. Any dependencies of root types will
        /// not have their accessibility changed regardless of the 'unreferenced-types-handling' value.</param>
        public void AddTypeToKeep(TypeProvider type, bool isRoot = true) => AddTypeToKeep(type.Type.FullyQualifiedName, isRoot);

        /// <summary>
        /// Writes additional output files (e.g. configuration schemas) after the main code generation is complete.
        /// Override this method to generate non-C# output files.
        /// </summary>
        /// <param name="outputPath">The root output directory.</param>
        public virtual Task WriteAdditionalFiles(string outputPath) => Task.CompletedTask;
    }
}
