/* (C) 2023 YiRing, Inc. */
package com.yiring.common.utils;

import com.yiring.common.domain.BasicEntity;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.springframework.data.domain.Example;
import org.springframework.data.jpa.domain.Specification;

/**
 * @author Jim
 * @version 0.1
 * 2023/3/17 16:36
 */

@SuppressWarnings("unused")
@UtilityClass
public class Specifications {

    /**
     * 构建谓词条件，支持扩展条件
     *
     * @param fn  回调函数
     * @param <T> Entity 类型
     * @return Specification
     */
    public static <T> Specification<T> of(
        CallbackFunction<Root<T>, CriteriaQuery<?>, CriteriaBuilder, List<Predicate>> fn
    ) {
        return build(fn, null);
    }

    /**
     * 构建存在的谓词条件，支持扩展条件
     *
     * @param fn  回调函数
     * @param <T> Entity 类型
     * @return Specification
     */
    public static <T> Specification<T> exist(
        CallbackFunction<Root<T>, CriteriaQuery<?>, CriteriaBuilder, List<Predicate>> fn
    ) {
        return build(fn, false);
    }

    /**
     * 构建存在的谓词条件
     *
     * @param <T> Entity 类型
     * @return Specification
     */
    public static <T> Specification<T> exist() {
        return build(null, false);
    }

    /**
     * 构建存在的 Example
     *
     * @param type Entity 类型
     * @return Example
     */
    @SneakyThrows
    public static <T extends BasicEntity> Example<T> exist(Class<T> type) {
        Constructor<T> declaredConstructor = type.getDeclaredConstructor();
        T probe = declaredConstructor.newInstance();
        probe.setDeleted(false);
        return Example.of(probe);
    }

    /**
     * 构建存在的谓词条件
     *
     * @param fn      回调函数
     * @param deleted 是否删除
     * @param <T>     Entity 类型
     * @return Specification
     */
    private static <T> Specification<T> build(
        CallbackFunction<Root<T>, CriteriaQuery<?>, CriteriaBuilder, List<Predicate>> fn,
        Boolean deleted
    ) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (Objects.nonNull(deleted)) {
                predicates.add(cb.equal(root.get(BasicEntity.Fields.deleted), deleted));
            }

            if (Objects.nonNull(fn)) {
                fn.apply(root, query, cb, predicates);
            }

            assert query != null;
            return query.where(predicates.toArray(new Predicate[0])).getRestriction();
        };
    }

    /**
     * 构建存在的谓词条件
     *
     * @param root 根
     * @param cb   构建器
     * @return 谓词
     */
    public static List<Predicate> buildExistPredicates(Root<?> root, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<>();
        predicates.add(cb.equal(root.get(BasicEntity.Fields.deleted), false));
        return predicates;
    }

    @FunctionalInterface
    public interface CallbackFunction<R, Q, C, P> {
        void apply(R root, Q query, C cb, P predicates);
    }
}
