using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;

namespace adas_core.Domain;

/// <summary>
///     Allow to pass to a <see cref="IQueryable" />
///     multiple requests like:
///     * Include (to fetch eagerly relations inside T entity)
///     Use a Builder pattern.
///     <example>
///         <code>
/// var queryCustomizer = QueryCustomizer&lt;Video&gt;.New()
///     .Include(v => v.Room)
///     .Include(v => v.Clips)
///     .Include(v => v.Diagnosis)
///     .Include(v => v.Movies)
///     .Include(v => v.Patient)
///     .Include(v => v.Service)
///     .Include(v => v.Thumbnail)
///     .Include(v => v.VideoDoctors)
///     .Include(v => v.Procedure)
///     .Include(v => v.Recordings)
///     .Include(v => v.Chapters)
///     .Include(v => v.LogDownloadVideoUsers)
///     .Include("VideoDoctors.Doctor")
///     ;
/// var v = _videoRepository.GetById(id, queryCustomizer);
/// </code>
///     </example>
/// </summary>
public class QueryCustomizer<T> where T : class
{
    private readonly List<Expression<Func<T, object>>> _includeLinqExpressions = [];
    private readonly List<string> _includeLinqStringExpressions = [];

    /// <summary>
    /// Creates and returns a new instance of the <see cref="QueryCustomizer{T}"/> class.
    /// </summary>
    /// <returns>A new <see cref="QueryCustomizer{T}"/> instance.</returns>
    public static QueryCustomizer<T> New()
        {
            return new QueryCustomizer<T>();
        }

    /// <summary>
    /// Adds a related entity to the set of include expressions used by the query, enabling eager loading of the specified navigation property.
    /// </summary>
    /// <param name="includeExpression">A lambda expression selecting the related entity to include in the query results.</param>
    /// <returns>The current <see cref="QueryCustomizer{T}"/> instance to allow fluent chaining of additional includes or customizations.</returns>
    public QueryCustomizer<T> Include(Expression<Func<T, object>> includeExpression)
        {
            _includeLinqExpressions.Add(includeExpression);
            return this;
        }

    /// <summary>
    /// Adds one or more include expressions to the query customizer, enabling eager loading of related entities in the resulting query.
    /// </summary>
    /// <param name="includeExpressions">The expressions specifying the related entities to include in the query.</param>
    /// <returns>The current <see cref="QueryCustomizer{T}"/> instance to support fluent method chaining.</returns>
    public QueryCustomizer<T> Include(params Expression<Func<T, object>>[] includeExpressions)
        {
            _includeLinqExpressions.AddRange(includeExpressions);
            return this;
        }

    /// <summary>
    /// Adds an include expression to the customizer, typically used to specify related entities to eager-load in the query, and returns the current instance to support fluent chaining.
    /// </summary>
    /// <param name="includeExpression">The include expression to be added to the collection of include expressions.</param>
    /// <returns>The current <see cref="QueryCustomizer{T}"/> instance with the include expression added.</returns>
    public QueryCustomizer<T> Include(string includeExpression)
        {
            _includeLinqStringExpressions.Add(includeExpression);
            return this;
        }

    /// <summary>
    /// Adds the specified include expressions to the query, allowing additional related entities or navigation properties to be loaded alongside the primary result.
    /// </summary>
    /// <param name="includeExpressions">The string expressions representing the related entities or paths to include in the query.</param>
    /// <returns>The current <see cref="QueryCustomizer{T}"/> instance to enable fluent method chaining.</returns>
    public QueryCustomizer<T> Include(params string[] includeExpressions)
        {
            _includeLinqStringExpressions.AddRange(includeExpressions);
            return this;
        }


    /// <summary>
    /// Applies a sequence of Include expressions to the specified queryable, enabling eager loading of related entities.
    /// Includes are applied in two passes: first using the pre-built LINQ expressions, then using the string-based include expressions.
    /// </summary>
    /// <param name="queryable">The source queryable to which the include expressions will be applied.</param>
    /// <returns>An <see cref="IQueryable{T}"/> with all configured include expressions applied.</returns>
    public IQueryable<T> AddToQueryable(IQueryable<T> queryable)
        {
            var resultQueryable = queryable;
            resultQueryable = _includeLinqExpressions
                    .Aggregate(resultQueryable, (current, expression) => current.Include(expression))
                ;
            resultQueryable = _includeLinqStringExpressions
                    .Aggregate(resultQueryable, (current, expression) => current.Include(expression))
                ;
            return resultQueryable;
        }
}