<?php 
 
/* 
 * This file is part of Monsieur Biz' Search plugin for Sylius. 
 * 
 * (c) Monsieur Biz <sylius@monsieurbiz.com> 
 * 
 * For the full copyright and license information, please view the LICENSE.txt 
 * file that was distributed with this source code. 
 */ 
 
declare(strict_types=1); 
 
namespace App\Search\Request; 
 
use App\Repository\Brand\BrandRepository; 
use Elastica\Query; 
use Elastica\QueryBuilder; 
use MonsieurBiz\SyliusSearchPlugin\Model\Documentable\DocumentableInterface; 
use MonsieurBiz\SyliusSearchPlugin\Repository\ProductAttributeRepositoryInterface; 
use MonsieurBiz\SyliusSearchPlugin\Repository\ProductOptionRepositoryInterface; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\AggregationBuilder; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\FunctionScore\FunctionScoreRegistryInterface; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\PostFilter\PostFilterRegistryInterface; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\QueryFilter\QueryFilterRegistryInterface; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\RequestConfiguration; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\RequestInterface; 
use MonsieurBiz\SyliusSearchPlugin\Search\Request\Sorting\SorterRegistryInterface; 
use RuntimeException; 
use Sylius\Component\Channel\Context\ChannelContextInterface; 
use Sylius\Component\Registry\ServiceRegistryInterface; 
 
final class Taxon implements RequestInterface 
{ 
    private DocumentableInterface $documentable; 
 
    private ProductAttributeRepositoryInterface $productAttributeRepository; 
 
    private ProductOptionRepositoryInterface $productOptionRepository; 
 
    private ChannelContextInterface $channelContext; 
 
    private AggregationBuilder $aggregationBuilder; 
 
    private ?RequestConfiguration $configuration; 
 
    private QueryFilterRegistryInterface $queryFilterRegistry; 
 
    private PostFilterRegistryInterface $postFilterRegistry; 
 
    private SorterRegistryInterface $sorterRegistry; 
 
    private FunctionScoreRegistryInterface $functionScoreRegistry; 
 
    /** @var BrandRepository */ 
    private $brandRepository; 
 
    public function __construct( 
        ServiceRegistryInterface $documentableRegistry, 
        ProductAttributeRepositoryInterface $productAttributeRepository, 
        ProductOptionRepositoryInterface $productOptionRepository, 
        BrandRepository $brandRepository, 
        ChannelContextInterface $channelContext, 
        AggregationBuilder $aggregationBuilder, 
        QueryFilterRegistryInterface $queryFilterRegistry, 
        PostFilterRegistryInterface $postFilterRegistry, 
        SorterRegistryInterface $sorterRegistry, 
        FunctionScoreRegistryInterface $functionScoreRegistry 
    ) { 
        /** @var DocumentableInterface $documentable */ 
        $documentable = $documentableRegistry->get('search.documentable.monsieurbiz_product'); 
        $this->documentable = $documentable; 
        $this->productAttributeRepository = $productAttributeRepository; 
        $this->productOptionRepository = $productOptionRepository; 
        $this->brandRepository = $brandRepository; 
        $this->channelContext = $channelContext; 
        $this->aggregationBuilder = $aggregationBuilder; 
        $this->queryFilterRegistry = $queryFilterRegistry; 
        $this->postFilterRegistry = $postFilterRegistry; 
        $this->sorterRegistry = $sorterRegistry; 
        $this->functionScoreRegistry = $functionScoreRegistry; 
    } 
 
    public function getType(): string 
    { 
        return RequestInterface::TAXON_TYPE; 
    } 
 
    public function getDocumentable(): DocumentableInterface 
    { 
        return $this->documentable; 
    } 
 
    public function getQuery(): Query 
    { 
        $qb = new QueryBuilder(); 
 
        $boolQuery = $qb->query()->bool(); 
 
        foreach ($this->queryFilterRegistry->all() as $queryFilter) { 
            $queryFilter->apply($boolQuery, $this->configuration); 
        } 
 
        $query = Query::create($boolQuery); 
 
        $postFilter = new Query\BoolQuery(); 
 
        foreach ($this->postFilterRegistry->all() as $postFilterApplier) { 
            $postFilterApplier->apply($postFilter, $this->configuration); 
        } 
        $query->setPostFilter($postFilter); 
        $this->addAggregations($query, $postFilter); 
 
        foreach ($this->sorterRegistry->all() as $sorter) { 
            $sorter->apply($query, $this->configuration); 
        } 
 
        /** @var Query\AbstractQuery $queryObject */ 
        $queryObject = $query->getQuery(); 
        $functionScore = $qb->query()->function_score() 
            ->setQuery($queryObject) 
            ->setBoostMode(Query\FunctionScore::BOOST_MODE_MULTIPLY) 
            ->setScoreMode(Query\FunctionScore::SCORE_MODE_MULTIPLY) 
        ; 
        foreach ($this->functionScoreRegistry->all() as $functionScoreClass) { 
            $functionScoreClass->addFunctionScore($functionScore, $this->configuration); 
        } 
 
        $query->setQuery($functionScore); 
 
        return $query; 
    } 
 
    public function supports(string $type, string $documentableCode): bool 
    { 
        return RequestInterface::TAXON_TYPE === $type && $documentableCode === $this->getDocumentable()->getIndexCode(); 
    } 
 
    public function setConfiguration(RequestConfiguration $configuration): void 
    { 
        $this->configuration = $configuration; 
    } 
 
    private function addAggregations(Query $query, Query\BoolQuery $postFilter): void 
    { 
        if (null === $this->configuration) { 
            throw new RuntimeException('Missing request configuration'); 
        } 
 
        $aggregations = $this->aggregationBuilder->buildAggregations( 
            [ 
                ['taxons' => $this->configuration->getTaxon()], 
                'price', 
                $this->productAttributeRepository->findIsSearchableOrFilterable(), 
                $this->productOptionRepository->findIsSearchableOrFilterable(), 
                'brand', 
            ], 
            $postFilter->hasParam('must') ? $postFilter->getParam('must') : [] 
        ); 
        foreach ($aggregations as $aggregation) { 
            $query->addAggregation($aggregation); 
        } 
    } 
}