// 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.ApplicationParts;

namespace Microsoft.AspNetCore.Mvc.Controllers;

/// <summary>
/// Discovers controllers from a list of <see cref="ApplicationPart"/> instances.
/// </summary>
public class ControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
    private const string ControllerTypeNameSuffix = "Controller";

    /// <inheritdoc />
    public void PopulateFeature(
        IEnumerable<ApplicationPart> parts,
        ControllerFeature feature)
    {
        foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
        {
            foreach (var type in part.Types)
            {
                if (IsController(type) && !feature.Controllers.Contains(type))
                {
                    feature.Controllers.Add(type);
                }
            }
        }
    }

    /// <summary>
    /// Determines if a given <paramref name="typeInfo"/> is a controller.
    /// </summary>
    /// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
    /// <returns><see langword="true" /> if the type is a controller; otherwise <see langword="false" />.</returns>
    protected virtual bool IsController(TypeInfo typeInfo)
    {
        if (!typeInfo.IsClass)
        {
            return false;
        }

        if (typeInfo.IsAbstract)
        {
            return false;
        }

        // We only consider public top-level classes as controllers. IsPublic returns false for nested
        // classes, regardless of visibility modifiers
        if (!typeInfo.IsPublic)
        {
            return false;
        }

        if (typeInfo.ContainsGenericParameters)
        {
            return false;
        }

        if (typeInfo.IsDefined(typeof(NonControllerAttribute)))
        {
            return false;
        }

        if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
            !typeInfo.IsDefined(typeof(ControllerAttribute)))
        {
            return false;
        }

        return true;
    }
}
