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

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Microsoft.AspNetCore.Components.Reflection;

internal sealed class PropertyGetter
{
    private static readonly MethodInfo CallPropertyGetterOpenGenericMethod =
        typeof(PropertyGetter).GetMethod(nameof(CallPropertyGetter), BindingFlags.NonPublic | BindingFlags.Static)!;

    private readonly Func<object, object?> _GetterDelegate;

    public PropertyInfo PropertyInfo { get; }

    [UnconditionalSuppressMessage(
        "ReflectionAnalysis",
        "IL2060:MakeGenericMethod",
        Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
    public PropertyGetter(Type targetType, PropertyInfo property)
    {
        PropertyInfo = property;

        if (property.GetMethod == null)
        {
            throw new InvalidOperationException("Cannot provide a value for property " +
                $"'{property.Name}' on type '{targetType.FullName}' because the property " +
                "has no getter.");
        }

        if (RuntimeFeature.IsDynamicCodeSupported)
        {
            var getMethod = property.GetMethod;

            var propertyGetterAsFunc =
                getMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(targetType, property.PropertyType));

            var callPropertyGetterClosedGenericMethod =
                CallPropertyGetterOpenGenericMethod.MakeGenericMethod(targetType, property.PropertyType);

            _GetterDelegate = (Func<object, object>)
                callPropertyGetterClosedGenericMethod.CreateDelegate(typeof(Func<object, object>), propertyGetterAsFunc);
        }
        else
        {
            _GetterDelegate = property.GetValue;
        }
    }

    public object? GetValue(object target) => _GetterDelegate(target);

    private static object? CallPropertyGetter<TTarget, TValue>(
        Func<TTarget, TValue> Getter,
        object target)
        where TTarget : notnull
    {
        return (object?)Getter((TTarget)target);
    }
}
