#if NET
namespace DigitalData.Core.Abstraction.Application;

/// <summary>
/// Provides extension methods for retrieving the value of an 'Id' property from objects.
/// </summary>
public static class EntityExtensions
{
    /// <summary>
    /// Attempts to retrieve the value of the 'Id' property from the specified object.
    /// </summary>
    /// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <returns>
    /// The value of the 'Id' property if it exists and is of type <typeparamref name="TId"/>; otherwise, <c>default</c>.
    /// </returns>
    public static TId? GetIdOrDefault<TId>(this object? obj)
    {
        var prop = obj?.GetType().GetProperty("Id");
        return prop is not null && prop.GetValue(obj) is TId id ? id : default;
    }

    /// <summary>
    /// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found or of the wrong type.
    /// </summary>
    /// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <returns>The value of the 'Id' property.</returns>
    /// <exception cref="InvalidOperationException">
    /// Thrown if the object does not have a readable 'Id' property of type <typeparamref name="TId"/>.
    /// </exception>
    public static TId GetId<TId>(this object? obj)
        => obj.GetIdOrDefault<TId>()
        ?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property of type '{typeof(TId).FullName}'.");

    /// <summary>
    /// Tries to retrieve the value of the 'Id' property from the specified object.
    /// </summary>
    /// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <param name="id">When this method returns, contains the value of the 'Id' property if found; otherwise, the default value for the type.</param>
    /// <returns>
    /// <c>true</c> if the 'Id' property was found and is of type <typeparamref name="TId"/>; otherwise, <c>false</c>.
    /// </returns>
    public static bool TryGetId<TId>(object? obj, out TId id)
    {
#pragma warning disable CS8601
        id = obj.GetIdOrDefault<TId>();
#pragma warning restore CS8601
        return id is not null;
    }

    /// <summary>
    /// Attempts to retrieve the value of the 'Id' property from the specified object.
    /// </summary>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <returns>
    /// The value of the 'Id' property if it exists; otherwise, <c>null</c>.
    /// </returns>
    public static object? GetIdOrDefault(this object? obj)
    {
        var prop = obj?.GetType().GetProperty("Id");
        return prop?.GetValue(obj);
    }

    /// <summary>
    /// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found.
    /// </summary>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <returns>The value of the 'Id' property.</returns>
    /// <exception cref="InvalidOperationException">
    /// Thrown if the object does not have a readable 'Id' property.
    /// </exception>
    public static object GetId(this object? obj)
        => obj.GetIdOrDefault()
        ?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property.");

    /// <summary>
    /// Tries to retrieve the value of the 'Id' property from the specified object.
    /// </summary>
    /// <param name="obj">The object from which to retrieve the 'Id' property.</param>
    /// <param name="id">
    /// When this method returns, contains the value of the 'Id' property if found; otherwise, <c>null</c>.
    /// </param>
    /// <returns>
    /// <c>true</c> if the 'Id' property was found; otherwise, <c>false</c>.
    /// </returns>
    public static bool TryGetId(object? obj, out object id)
    {
#pragma warning disable CS8601
        id = obj.GetIdOrDefault();
#pragma warning restore CS8601
        return id is not null;
    }
}
#endif