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

#nullable enable

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Razor.Compilation;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;

/// <summary>
/// A factory for producing <see cref="CompiledPageActionDescriptor"/> given a <see cref="PageActionDescriptor"/>.
/// </summary>
internal sealed class CompiledPageActionDescriptorFactory
{
    private readonly IPageApplicationModelProvider[] _applicationModelProviders;
    private readonly PageConventionCollection _conventions;
    private readonly FilterCollection _globalFilters;

    public CompiledPageActionDescriptorFactory(
        IEnumerable<IPageApplicationModelProvider> applicationModelProviders,
        MvcOptions mvcOptions,
        RazorPagesOptions pageOptions)
    {
        _applicationModelProviders = applicationModelProviders.OrderBy(a => a.Order).ToArray();
        _conventions = pageOptions.Conventions;
        _globalFilters = mvcOptions.Filters;
    }

    public CompiledPageActionDescriptor CreateCompiledDescriptor(
        PageActionDescriptor actionDescriptor,
        CompiledViewDescriptor viewDescriptor)
    {
        var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type!.GetTypeInfo());
        for (var i = 0; i < _applicationModelProviders.Length; i++)
        {
            _applicationModelProviders[i].OnProvidersExecuting(context);
        }

        for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
        {
            _applicationModelProviders[i].OnProvidersExecuted(context);
        }

        ApplyConventions(_conventions, context.PageApplicationModel);

        var compiled = CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters);
        actionDescriptor.CompiledPageDescriptor = compiled;

        return compiled;
    }

    internal static void ApplyConventions(
        PageConventionCollection conventions,
        PageApplicationModel pageApplicationModel)
    {
        var applicationModelConventions = GetConventions<IPageApplicationModelConvention>(pageApplicationModel.HandlerTypeAttributes);
        foreach (var convention in applicationModelConventions)
        {
            convention.Apply(pageApplicationModel);
        }

        var handlers = pageApplicationModel.HandlerMethods.ToArray();
        foreach (var handlerModel in handlers)
        {
            var handlerModelConventions = GetConventions<IPageHandlerModelConvention>(handlerModel.Attributes);
            foreach (var convention in handlerModelConventions)
            {
                convention.Apply(handlerModel);
            }

            var parameterModels = handlerModel.Parameters.ToArray();
            foreach (var parameterModel in parameterModels)
            {
                var parameterModelConventions = GetConventions<IParameterModelBaseConvention>(parameterModel.Attributes);
                foreach (var convention in parameterModelConventions)
                {
                    convention.Apply(parameterModel);
                }
            }
        }

        var properties = pageApplicationModel.HandlerProperties.ToArray();
        foreach (var propertyModel in properties)
        {
            var propertyModelConventions = GetConventions<IParameterModelBaseConvention>(propertyModel.Attributes);
            foreach (var convention in propertyModelConventions)
            {
                convention.Apply(propertyModel);
            }
        }

        IEnumerable<TConvention> GetConventions<TConvention>(
            IReadOnlyList<object> attributes)
        {
            return Enumerable.Concat(
                conventions.OfType<TConvention>(),
                attributes.OfType<TConvention>());
        }
    }
}
