// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Razor.Hosting;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;

namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
    internal sealed class RazorCompiledItemFeatureProvider : IApplicationFeatureProvider<ViewsFeature>
    {
        private Dictionary<string, Type>? _hotReloadedViews;

        public void PopulateFeature(IEnumerable<ApplicationPart> parts, ViewsFeature feature)
        {
            foreach (var provider in parts.OfType<IRazorCompiledItemProvider>())
            {
                // Ensure parts do not specify views with differing cases. This is not supported
                // at runtime and we should flag at as such for precompiled views.
                var duplicates = provider.CompiledItems
                    .GroupBy(i => i.Identifier, StringComparer.OrdinalIgnoreCase)
                    .FirstOrDefault(g => g.Count() > 1);

                if (duplicates != null)
                {
                    var viewsDifferingInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier));

                    var message = string.Join(
                        Environment.NewLine,
                        Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
                        viewsDifferingInCase);
                    throw new InvalidOperationException(message);
                }

                foreach (var item in provider.CompiledItems)
                {
                    var compiledItem = item;
                    if (_hotReloadedViews is not null && _hotReloadedViews.TryGetValue(item.Identifier, out var hotReloadedType))
                    {
                        // Determine if a hot reload update is available for this view.
                        compiledItem = new HotReloadRazorCompiledItem(item, hotReloadedType);
                    }

                    var descriptor = new CompiledViewDescriptor(compiledItem);
                    feature.ViewDescriptors.Add(descriptor);
                }
            }
        }

        public void UpdateCache(Type[]? types)
        {
            if (types is null)
            {
                return;
            }

            foreach (var type in types)
            {
                // The Razor file has a [RazorCompiledItemMetadata("Identifier", "/Index.cshtml")]. We'll look it up.
                var metadataAttribute = type.GetCustomAttributes<RazorCompiledItemMetadataAttribute>()
                    .FirstOrDefault(a => a.Key == "Identifier");

                if (metadataAttribute is RazorCompiledItemMetadataAttribute identifierAttribute)
                {
                    _hotReloadedViews ??= new(StringComparer.Ordinal);
                    _hotReloadedViews[identifierAttribute.Value] = type;
                }
            }
        }

        private sealed class HotReloadRazorCompiledItem : RazorCompiledItem
        {
            private readonly RazorCompiledItem _previous;
            public HotReloadRazorCompiledItem(RazorCompiledItem previous, Type type)
            {
                _previous = previous;
                Type = type;
            }

            public override string Identifier => _previous.Identifier;
            public override string Kind => _previous.Kind;
            public override IReadOnlyList<object> Metadata => Type.GetCustomAttributes(inherit: true);
            public override Type Type { get; }
        }
    }
}
