using System.Linq.Expressions;
using Tiku.Application.Security;

namespace Tiku.Infrastructure.Security;

internal static class DataScopeQueryableExtensions
{
    public static IQueryable<TEntity> ApplyDataScope<TEntity>(
        this IQueryable<TEntity> query,
        CurrentDataScope scope,
        Expression<Func<TEntity, bool>>? selfPredicate,
        Expression<Func<TEntity, bool>>? restrictedPredicate)
    {
        if (scope.Mode == DataScopeMode.All) return query;

        Expression<Func<TEntity, bool>>? predicate = null;
        if (scope.IncludesSelf && selfPredicate is not null) predicate = selfPredicate;

        if (scope.Mode == DataScopeMode.Restricted && restrictedPredicate is not null)
            predicate = predicate is null ? restrictedPredicate : OrElse(predicate, restrictedPredicate);

        return predicate is null ? query.Where(_ => false) : query.Where(predicate);
    }

    private static Expression<Func<TEntity, bool>> OrElse<TEntity>(
        Expression<Func<TEntity, bool>> left,
        Expression<Func<TEntity, bool>> right)
    {
        var parameter = Expression.Parameter(typeof(TEntity), "entity");
        var leftBody = new ReplaceParameterVisitor(left.Parameters[0], parameter).Visit(left.Body)!;
        var rightBody = new ReplaceParameterVisitor(right.Parameters[0], parameter).Visit(right.Body)!;
        return Expression.Lambda<Func<TEntity, bool>>(Expression.OrElse(leftBody, rightBody), parameter);
    }

    private sealed class ReplaceParameterVisitor(ParameterExpression source, ParameterExpression target)
        : ExpressionVisitor
    {
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == source ? target : base.VisitParameter(node);
        }
    }
}