<?php

namespace KupShop\CatalogBundle\View;

use Doctrine\Common\Collections\ArrayCollection;
use KupShop\CatalogBundle\CategorySort;
use KupShop\CatalogBundle\Entity\Wrapper\CategoryWrapper;
use KupShop\CatalogBundle\Entity\Wrapper\ProducerWrapper;
use KupShop\CatalogBundle\Entity\Wrapper\SectionProducerWrapper;
use KupShop\CatalogBundle\Enum\OrderDirection;
use KupShop\CatalogBundle\Enum\ProductOrder;
use KupShop\CatalogBundle\Event\CatalogEvent;
use KupShop\CatalogBundle\Event\CategoryEvent;
use KupShop\CatalogBundle\ProductList\Builder\CompileStrategy;
use KupShop\CatalogBundle\ProductList\Builder\FilteredProductListBuilder;
use KupShop\CatalogBundle\ProductList\DynamicFilterAttributes;
use KupShop\CatalogBundle\ProductList\ProductCollectionWrapper;
use KupShop\CatalogBundle\ProductList\ProductList;
use KupShop\CatalogBundle\Section\SectionTree;
use KupShop\CatalogBundle\Util\ActiveCategory;
use KupShop\CatalogBundle\Util\SectionUtil;
use KupShop\CatalogBundle\Util\SectionViewUtil;
use KupShop\CatalogBundle\Util\VirtualCategoryProductsFilterSpecs;
use KupShop\CatalogBundle\View\Wrapper\FilterParamsWrapper;
use KupShop\ComponentsBundle\Twig\DataProvider\ProductDataProvider;
use KupShop\ComponentsBundle\Twig\DataProvider\SectionDataProvider;
use KupShop\ContentBundle\Util\Block;
use KupShop\ContentBundle\Util\SliderUtil;
use KupShop\I18nBundle\Translations\ParametersListTranslation;
use KupShop\I18nBundle\Translations\ProducersTranslation;
use KupShop\I18nBundle\Translations\VariationsValuesTranslation;
use KupShop\IndexedFilterBundle\Translations\IndexedFiltersContentTranslation;
use KupShop\KupShopBundle\Context\CacheContext;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Context\PriceLevelContext;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\KupShopBundle\Exception\RedirectException;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\KupShopBundle\Util\Suppliers\SuppliersUtil;
use KupShop\KupShopBundle\Views\View;
use KupShop\MetricsBundle\PrometheusWrapper;
use KupShop\PricelistBundle\Context\PricelistContext;
use KupShop\ResponseCacheBundle\Util\Cache\ResponseCache;
use Query\Operator;
use Query\Product;
use Query\QueryBuilder;
use Query\Translation;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Contracts\Service\Attribute\Required;

class CategoryView extends View implements CategoryRequestHandler
{
    use \DatabaseCommunication;

    protected $template = 'category.tpl';

    /** @var EventDispatcherInterface */
    protected $dispatcher;

    /**
     * @var ProductList
     */
    public $productList;

    /** @var RequestStack */
    protected $requestStack;

    /**
     * @var \FilterParams
     */
    public $filterParams;

    /**
     * @var \Filter
     */
    public $filter;

    /**
     * @var \Filter base filter for indexed filters
     */
    public $baseFilter;

    /** @var PricelistContext */
    protected $priceListContext;
    protected SliderUtil $sliderUtil;

    public $category;
    public $categoryId = 0;

    public $producer;
    public $producerId;
    public $parameterValueId;

    protected $campaign;

    public $categoryProducer;

    protected $dynamicFilterAttributes;

    private $filterActive = false;
    protected $filterData = [];

    protected $parsedURLData = [];
    protected $dynamicTitle;

    private $sort_options = [
        'date' => 4,
        'title' => 2,
        'price' => 3,
        'store' => 7,
        'sell' => 5,
        'code' => 1,
        'discount' => 10,
        'soldPrice' => 11,
    ];

    private $sort_dir_options = [
        'ASC' => 1,
        'DESC' => 2,
    ];

    private $orderBy;

    private $orderDir;

    private $languageContext;

    private $currencyContext;

    private $indexed_filter;

    protected SectionViewUtil $categoryUtil;

    private CompileStrategy $compileStrategy;

    protected Block $block;

    protected SectionUtil $sectionUtil;

    public function __construct(EventDispatcherInterface $dispatcher, ProductList $productList,
        protected ActiveCategory $activeCategory,
        protected ?SectionDataProvider $categoryDataProvider = null,
        protected ?ProductDataProvider $productDataProvider = null,
    ) {
        $this->dispatcher = $dispatcher;
        $this->productList = $productList;
        $this->proxyCacheEnabled = findModule(\Modules::PROXY_CACHE, 'category');
    }

    /**
     * @return $this
     *
     * @required
     */
    public function setPriceListContext(?PricelistContext $context = null)
    {
        $this->priceListContext = $context;

        return $this;
    }

    /**
     * @return $this
     *
     * @required
     */
    public function setLanguageContext(LanguageContext $languageContext)
    {
        $this->languageContext = $languageContext;

        return $this;
    }

    /**
     * @return $this
     *
     * @required
     */
    public function setCurrencyContext(CurrencyContext $currencyContext)
    {
        $this->currencyContext = $currencyContext;

        return $this;
    }

    /**
     * @return $this
     *
     * @required
     */
    public function setRequestStack(RequestStack $requestStack)
    {
        $this->requestStack = $requestStack;

        return $this;
    }

    public function render(): string
    {
        $cacheEnabled = false;
        $valuesCache = null;

        if (findModule('filter_cache')) {
            $cacheEnabled = findModule('filter_cache', 'always_cached', empty($this->filterData));
            $cacheKey = [
                'sections',
                $this->categoryId,
                $this->producerId,
            ];
            $campaign = $this->getCampaign();
            if ($campaign) {
                $cacheKey[] = $campaign;
            }
            $cacheKey[] = $this->languageContext->getActiveId();
            $cacheKey[] = $this->currencyContext->getActiveId();
            $cacheContext = Contexts::get(CacheContext::class);
            $cacheKey[] = $cacheContext->getKey([CacheContext::TYPE_AVAILABILITY]);
            $cacheKey = join('-', $cacheKey);

            if ($campaign == 'F' || $this instanceof WatchdogView) {
                $cacheEnabled = false;
            }

            if ($cacheEnabled && ($valuesCache = getCache($cacheKey))) {
                $this->filter->setValuesCache($valuesCache);
                $this->baseFilter->setValuesCache($valuesCache);
            }

            $metrics = ServiceContainer::getService(PrometheusWrapper::class);
            $metrics->addLabel('custom', join('-', [$this->categoryId]));

            $metrics->setCounter('kupshop', 'filter_cached', '', 1, ['used' => $cacheEnabled ? '1' : '0']);
            $metrics->setCounter('kupshop', 'filter_cache_used', '', 1, ['used' => $valuesCache ? '1' : '0']);
        }

        $output = parent::render();

        if ($cacheEnabled && !$valuesCache) {
            $valuesCache = $this->filter->getValuesCache();
            $valuesCache += $this->baseFilter->getValuesCache();
            setCache($cacheKey, $valuesCache, 3600 * 5);
        }

        return $output;
    }

    public function prepareUrlFilterData()
    {
        $dynamicFilter = $this->getDynamicFilterConfiguration();
        $data = $this->parsedURLData;
        $filterData = $this->filterData;

        if (empty($data)) {
            return $filterData;
        }

        if ($dynamicFilter['producers'] && !empty($data['producerIds'])) {
            if (isset($filterData['producerIds'])) {
                $filterData['producerIds'] = array_merge_recursive($filterData['producerIds'], $data['producerIds']);
            } else {
                $filterData['producerIds'] = $data['producerIds'];
            }
        }

        if (isset($data['variations'])) {
            foreach ($data['variations'] as $id => $value) {
                if (key_exists($id, $dynamicFilter['variations'])) {
                    foreach ($value['value'] as $value_id) {
                        $filterData['variations'][$id]['value'][] = $value_id;
                    }
                }
            }
        }

        if (isset($data['parameters'])) {
            foreach ($data['parameters'] as $id => $value) {
                foreach ($value['value'] as $value_id) {
                    if (in_array($value_id, $filterData['parameters'][$id]['value'] ?? [])) {
                        continue;
                    }
                    if (!isset($filterData['parameters'][$id])) {
                        $filterData['parameters'][$id]['type'] = \Filter::PARAM_LIST;
                    }
                    $filterData['parameters'][$id]['value'][] = $value_id;
                }
            }
        }

        if (isset($data['variationsSizeConvertion'])) {
            $filterData['variationsSizeConvertion'] = $data['variationsSizeConvertion'];
        }

        $this->setFilterData($filterData);

        return $filterData;
    }

    public function getBodyVariables()
    {
        $data = [
            'id_category' => $this->categoryId,
            'id_producer' => $this->producerId,
        ];

        $category = $this->getCategory();
        $producer = $this->getProducer();
        $this->activeCategory->setProducerId($producer['id'] ?? null);
        $categoryProducer = $this->getCategoryProducer();

        $this->prepareProductList();
        $this->prepareFilterParams();

        // merge filter data from url to filter data
        $this->prepareUrlFilterData();

        $data['ordering'] = $this->getOrdering();
        $data['listShow'] = $this->getListShowType();
        $data['dynamic_filter'] = $this->getDynamicFilter();
        $data['dynamic_filter']['filterParams'] = FilterParamsWrapper::wrap($this->filterParams);
        $data['indexed_filter'] = $this->getIndexedFilter();
        $data['filter_active'] = $this->filterActive;

        $pager = $this->getPager();
        $event = new CategoryEvent($this->getCategory(), $this->productList, $this->filter, $this->filterParams, $this->filterData, $pager);
        $this->dispatcher->dispatch($event, CategoryEvent::NAME);

        $this->dispatcher->dispatch(
            new CatalogEvent($this->productList, $this->filterParams, CatalogEvent::TYPE_CATEGORY)
        );

        if (findModule(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH) && empty($this->campaign)) {
            ['page' => $page, 'noOnPage' => $noOnPage] = $this->getPagerOptions();
            $configuration = $this->getDynamicFilterConfiguration();

            $builder = (new FilteredProductListBuilder())
                ->paged((int) $page, (int) $noOnPage, $this->requestStack->getMainRequest()?->getUri())
                ->dynamicFilterAttributes(DynamicFilterAttributes::fromArray($configuration))
                ->dynamicFilter($this->filterData)
                ->baseFilterParams(clone $this->filterParams);

            $ordering = $this->getOrderingBy();

            //            if ($ordering['default_sort']) {
            //                $builder->categoryDefaultSort(CategorySort::fromCategory($this->getCategory(), $this->producerId));
            //            } elseif ($ordering['order_by']) {
            //                $builder->orderBy(
            //                    ProductOrder::tryFrom($ordering['order_by']),
            //                    OrderDirection::tryFrom($ordering['order_dir']),
            //                );
            //            }

            $result = $builder->build($this->compileStrategy);
            $this->applyProductListSpecs($result->getProductList());

            $data['pager'] = $pager = $result->getPager();
            $data['productsList'] = ProductCollectionWrapper::wrap($productList = $result->getProductList()->getProducts());

            $this->filter->setValuesCache($result->getDynamicFilter()->getValuesCache());
            $this->baseFilter->setValuesCache($result->getDynamicFilter()->getValuesCache());
        } else {
            $totalProducts = 0;
            $productList = $this->productList->getProducts($totalProducts);
            $pager->setTotal($totalProducts);
        }

        if (!isAjax() && $pager->number != 1 && $pager->count < $pager->number) {
            throw new NotFoundHttpException("Page '{$pager->number}' is out of range. Valid range is '1 - {$pager->count}'.");
        }
        $data['pager'] = $pager;

        $this->filter->setFilterParamsInStoreWithVariations(false);
        $this->baseFilter->setFilterParamsInStoreWithVariations(false);

        if (findModule(\Modules::COMPONENTS) && $this->categoryId) {
            $this->categoryDataProvider->addCategory($this->categoryId);
            if ($producer) {
                $this->categoryDataProvider->addProducer($producer);
            }
            $this->productDataProvider->addProducts($productList->toArray());
            $data['productList'] = $productList;

            return $data;
        }

        $data['productsList'] = ProductCollectionWrapper::wrap($productList);
        $data['category'] = ServiceContainer::getService(CategoryWrapper::class)->setObject($category);
        $data['producer'] = ($producer ? ServiceContainer::getService(ProducerWrapper::class)->setObject($producer) : null);
        $data['categoryProducer'] = $categoryProducer;
        if ($data['categoryProducer']) {
            $data['categoryProducer'] = ServiceContainer::getService(SectionProducerWrapper::class)->setObject($data['categoryProducer']);
        }
        $data['title'] = $this->getTitle();

        if ($this->producerId && $this->categoryId == 0) {
            $data['category']['blocks']->clear();
        }

        if ($indexed_filter = $this->getIndexedFilter()) {
            if ($id_block = $indexed_filter['id_block'] ?? null) {
                unset($data['category']['blocks']);
                $data['category']['id_block'] = $id_block;
            }
        }

        return array_merge($data, parent::getBodyVariables());
    }

    public function setCategoryId($categoryId)
    {
        $this->categoryId = $categoryId;
    }

    public function setProducerId($producerId)
    {
        $this->producerId = $producerId;
    }

    public function setParameterValueId($id_parameter_value)
    {
        $this->parameterValueId = $id_parameter_value;
    }

    protected function getPagerOptions(): array
    {
        $dbcfg = \Settings::getDefault();

        $noOnPage = $dbcfg['cat_show_products'];

        if (is_numeric(getVal('filter_onpage'))) {
            $noOnPage = getVal('filter_onpage');
            SetCookies('filter_onpage', $noOnPage);
        } elseif (!empty($_COOKIE['filter_onpage'])) {
            $noOnPage = intval($_COOKIE['filter_onpage']);
        }

        $page = max(intval(getVal('page', null, 1)), 1);

        return compact('noOnPage', 'page');
    }

    protected function getPager()
    {
        ['noOnPage' => $noOnPage, 'page' => $page] = $this->getPagerOptions();

        $pager = new \Pager();
        $pager->setOnPage($noOnPage);
        $pager->setPageNumber($page);
        // $pager->setUrl($url);

        $this->productList->andSpec($pager->getSpec());

        return $pager;
    }

    protected function &getCategory()
    {
        if ($this->category) {
            return $this->category;
        }

        global $cfg, $catPath;

        $catPath = [];

        if (is_array($this->categoryId)) {
            $catPath = array_unique($this->categoryId);
            $this->categoryId = $catPath[0];
            $catPath = array_reverse($catPath);
        }

        if (!empty($this->categoryId)) {
            $cat = $this->activeCategory->getSectionById($this->categoryId);
            if (!$cat) {
                throw new NotFoundHttpException('Section not found!');
            }
            if (($cat->getFigure() == 'N') && !getAdminUser()) {
                // skryta sekce - 404 (pro admina nezobrazovat 404, pokud je sekce skryta)
                throw new NotFoundHttpException('Section not found!');
            }
        }

        $this->activeCategory->setCategoryId($this->categoryId);

        if (!is_array($this->categoryId) && empty($catPath)) {
            $catPath = $this->activeCategory->getOpenedTreePath();
        }

        $this->activeCategory->validate([$this->categoryId]);

        $urlFlags = $cfg['Products']['UrlFlags'] ?? null;
        if (preg_match("/^[[:digit:]]+({$urlFlags})?$/i", $this->categoryId)) {
            $campaign = $this->campaign;
            if (!is_numeric($this->categoryId)) {
                $campaign = substr($this->categoryId, -1);
                $this->categoryId = substr($this->categoryId, 0, -1);
            }

            if ($this->categoryId >= 0) {
                // Tohle je demence, ale Honza tvrdí, že to je třeba kvůli Seznam remarketingu, bohužel :-(
                $cqb = sqlQueryBuilder()
                    ->select('s.*, fs.category_text as feed_seznam_category')
                    ->from('sections', 's')
                    ->leftJoin('s', 'kupshop_shared.feed_seznam', 'fs', 's.feed_seznam = fs.id')
                    ->where(Operator::equals(['s.id' => $this->categoryId]));
                $cqb->andWhere(
                    Translation::coalesceTranslatedFields(
                        \KupShop\I18nBundle\Translations\SectionsTranslation::class,
                        function ($columns) {
                            $columns['data'] = 'data_translated';

                            return $columns;
                        }
                    )
                );
                $this->category = $cqb->execute()
                    ->fetch();
                if ($this->category) {
                    $this->category['param'] = '';
                    $this->category['parents'] = $catPath;
                    $sectionData = array_replace_recursive(json_decode($this->category['data'], true) ?: [], json_decode($this->category['data_translated'], true) ?: []);
                    $this->category['data'] = $sectionData;
                    $this->category['date_updated'] = $this->category['date_updated'] ? new \DateTime($this->category['date_updated']) : null;

                    if (findModule(\Modules::SLIDERS)) {
                        $this->category['sliders'] = $sliders = $this->sliderUtil->getSectionSliders((int) $this->categoryId);

                        $this->category['id_slider'] = $sliders['main']?->id ?? null;
                        $this->category['data']['additional_slider'] = $sliders['additional']?->id ?? null;
                    }
                } else {
                    throw new NotFoundHttpException('Section not found!');
                }
            } else {
                $this->category = [
                    'id' => 0,
                    'name' => '',
                    'descr' => '',
                    'behaviour' => 0,
                    'param' => '',
                    'lead_figure' => false,
                    'lead_text' => '',
                    'lead_products' => '',
                    'orderby' => 'title',
                    'orderdir' => 'ASC',
                    'producers_filter' => true,
                    'producers_to_title' => true,
                    'producers_indexing' => false,
                ];
            }
            $this->category['name_orig'] = $this->category['name'];

            $this->category['orderby'] = getVal($this->category['orderby'], $this->getSortOptions(), 2);
            $this->category['orderdir'] = getVal($this->category['orderdir'], $this->sort_dir_options, 1);

            $this->category['campaign'] = $campaign;

            // materialized sections should be used (Y or N)
            $this->category['virtual_materialized'] = findModule(\Modules::PRODUCTS_SECTIONS, 'virtual_to_db') ? 'Y' : 'N';

            $this->category = new ArrayCollection($this->category);
        } else {
            redirection('REFERER');
        }

        return $this->category;
    }

    /**
     * Creating title from filterParams.
     *
     * @return string
     */
    public function createTitle()
    {
        if (!findModule(\Modules::INDEXED_FILTER)) {
            return '';
        }

        if (isset($this->dynamicTitle)) {
            return $this->dynamicTitle;
        }

        $dynamicFilter = $this->getDynamicFilterConfiguration();
        $params = $this->filterParams;

        $variationNames = [];
        $parameterNames = [];
        $producerNames = [];

        // Variation names
        foreach ($params->getVariations() as $labelId => $values) {
            if (isset($dynamicFilter['variations'][$labelId])
                && $dynamicFilter['variations'][$labelId]['to_title'] == 'Y') {
                foreach ($values as $value) {
                    $data = sqlQueryBuilder()->select('pvcv.id')
                        ->from('products_variations_choices_values', 'pvcv')
                        ->where(Operator::equals(['pvcv.id' => $value]))
                        ->andWhere(Translation::coalesceTranslatedFields(VariationsValuesTranslation::class, ['value', 'title']))
                        ->execute()->fetch();
                    if ($data) {
                        $variationNames[] = (!empty($data['title']) ? $data['title'] : $data['value']);
                    }
                }
            }
        }

        // Parameter names
        foreach ($params->getParameters() as $id => $parameter) {
            if (isset($dynamicFilter['parameters'][$id])
                && $dynamicFilter['parameters'][$id]['to_title'] == 'Y') {
                if ($parameter['type'] == \Filter::PARAM_LIST) {
                    foreach ($parameter['values'] as $parameterListId) {
                        $data = sqlQueryBuilder()->select('pl.id')
                            ->from('parameters_list', 'pl')->where(Operator::equals(['pl.id' => $parameterListId]))
                            ->andWhere(Translation::coalesceTranslatedFields(ParametersListTranslation::class, ['value', 'title']))
                            ->execute()->fetch();
                        if ($data) {
                            $parameterNames[$id][] = (!empty($data['title']) ? $data['title'] : $data['value']);
                        }
                    }
                }
            }
        }

        // Producer names
        if (!$this->producer) {
            if (($dynamicFilter['producers'] ?? false)
                && ($dynamicFilter['producers']['to_title'] ?? false) == 'Y') {
                foreach ($params->getProducers() as $producer) {
                    $data = sqlQueryBuilder()->select('pr.id')
                        ->from('producers', 'pr')->where(Operator::equals(['pr.id' => $producer]))
                        ->andWhere(Translation::joinTranslatedFields(ProducersTranslation::class, function (QueryBuilder $qb, $columnName, $translatedField) {}, ['name', 'title']))
                        ->execute()->fetch();
                    if ($data) {
                        $producerNames[] = (!empty($data['title']) ? $data['title'] : $data['name']);
                    }
                }
            }
        }

        foreach ($params->getVariationsSizeConvertion() ?? [] as $labelId => $convertor) {
            if (($dynamicFilter['variations'][$labelId]['to_title'] ?? false) == 'Y') {
                foreach ($convertor['value'] as $paramId) {
                    $data = sqlQueryBuilder()->select('pl.id')
                        ->from('parameters_list', 'pl')->where(Operator::equals(['pl.id' => $paramId]))
                        ->andWhere(Translation::coalesceTranslatedFields(ParametersListTranslation::class, ['value', 'title']))
                        ->execute()->fetch();
                    if ($data) {
                        $variationNames[] = (!empty($data['title']) ? $data['title'] : $data['value']);
                    }
                }
            }
        }

        $parametersTitle = '';
        foreach ($parameterNames as $values) {
            $parametersTitle .= ' '.join(', ', $values);
        }

        $title = '';
        $title .= !empty($producerNames) ? ' '.join(', ', $producerNames) : '';
        $title .= $parametersTitle;
        $title .= !empty($variationNames) ? ' '.join(', ', $variationNames) : '';

        return $this->dynamicTitle = $title;
    }

    protected function &getProducer()
    {
        // ################################################
        //	VYROBKY JEN OD URCITEHO VYROBCE
        // ################################################
        if (!empty($this->producerId)) {
            $pqb = sqlQueryBuilder()
                ->select('pr.*')
                ->from('producers', 'pr')
                ->where(Operator::equals(['pr.id' => $this->producerId, 'pr.active' => 'Y']));

            $pqb->andWhere(
                Translation::coalesceTranslatedFields(
                    ProducersTranslation::class
                )
            );

            $this->producer = $pqb->execute()
                ->fetch();

            if (!$this->producer) {
                throw new NotFoundHttpException('Producer not found');
            }

            $this->producer['orderby'] = getVal($this->producer['orderby'], $this->getSortOptions(), 2);
            $this->producer['orderdir'] = getVal($this->producer['orderdir'], $this->sort_dir_options, 1);

            if (!empty($this->producer['photo'])) {
                $this->producer['image'] = getImage($this->producerId, $this->producer['photo'], '../producer/', 7, $this->producer['name']);
            } else {
                $this->producer['image'] = null;
            }

            $this->producer['data'] = json_decode($this->producer['data'], true);

            if (!empty($this->producer['data']['id_slider'])) {
                $this->category['id_slider'] = $this->producer['data']['id_slider'];
            }

            unset($this->producer['photo']);
        }

        // ################################################
        return $this->producer;
    }

    protected function getCategoryProducer()
    {
        if ($this->producerId && $this->categoryId >= 0) {
            $cpqb = sqlQueryBuilder()
                ->select('sp.*')
                ->from('section_producer', 'sp')
                ->where(Operator::equals(['sp.id_producer' => $this->producerId, 'sp.id_section' => $this->categoryId]));

            $cpqb->andWhere(
                Translation::coalesceTranslatedFields(
                    \KupShop\I18nBundle\Translations\SectionProducerTranslation::class
                )
            );

            $this->categoryProducer = $cpqb->execute()
                ->fetch();

            if ($this->categoryProducer) {
                if (!empty($this->categoryProducer['id_slider'])) {
                    $this->category['id_slider'] = $this->categoryProducer['id_slider'];
                }

                return $this->categoryProducer;
            }
        }

        return null;
    }

    public function setOrderBy($orderBy)
    {
        $this->orderBy = $orderBy;
    }

    public function setOrderDir($orderDir)
    {
        $this->orderDir = $orderDir;
    }

    public function getOrderBy()
    {
        return $this->orderBy;
    }

    public function getOrderDir()
    {
        return $this->orderDir;
    }

    protected function getOrderingBy()
    {
        global $cfg;

        if ($this->getOrderBy() == null || $this->getOrderDir() == null) {
            $order = (int) getVal('order');
            if ((int) $order) {
                $order_by = abs($order);
                $order_dir = $order >= 0 ? 1 : 2;
            } else {
                $order_by = getVal('order_by');
                $order_dir = getVal('order_dir');
            }
        } else {
            $order_by = $this->getOrderBy();
            $order_dir = $this->getOrderDir();
        }

        // Set default if < 0
        if ($order_by < 0) {
            $order_by = null;
        }

        $order_by_preset = $order_by;
        $order_dir_preset = $order_dir;

        // Order according to campaign
        if (empty($order_by_preset) && !empty($this->category['campaign'])) {
            $campaignOrder = getVal($this->category['campaign'], $cfg['Products']['Flags']);
            $campaignOrder2 = $campaignOrder = getVal('order', $campaignOrder);

            if (!empty($campaignOrder)) {
                $order_by_preset = abs($campaignOrder);
                $order_dir_preset = $campaignOrder >= 0 ? 1 : 2;
            }
        }

        // Order according to url params
        if (empty($order_by_preset) && ($campaignOrder = getVal('default_order')) && !empty($campaignOrder)) {
            $order_by_preset = abs($campaignOrder);
            $order_dir_preset = $campaignOrder >= 0 ? 1 : 2;
        }

        // Default ordering
        if (empty($order_by_preset)) {
            if (!empty($this->producer['orderby'])) {
                $order_by_preset = $this->producer['orderby'];
            } else {
                $order_by_preset = $this->category['orderby'];
            }
        }

        // Default ordering direction
        if (empty($order_dir_preset)) {
            if (!empty($this->producer['orderdir'])) {
                $order_dir_preset = $this->producer['orderdir'];
            } else {
                $order_dir_preset = $this->category['orderdir'];
            }
        }

        $default_sort = (
            empty($order_by)
            || intval($order_by) < 0
            || intval($order_by_preset) < 0
        ) && (
            // When campaign has no 'order' settings configured
            // it should reflect category (id=0) ordering instead of default_sort
            $this->categoryId !== 0 || $this->producerId !== 0 || isset($campaignOrder2)
        );

        return [
            'order' => (int) $order_by_preset * ((int) $order_dir_preset == 2 ? -1 : 1),
            'order_by' => $order_by_preset,
            'order_dir' => $order_dir_preset,
            'default_sort' => $default_sort,
        ];
    }

    protected function getOrderFields($order_by)
    {
        $dbcfg = \Settings::getDefault();

        switch ($order_by) {
            case 1:
                $order = 'p.code';
                break;
            case 2:
                $order = 'p.title';
                break;
            case 3:
                $order = $this->getOrderFieldPrice();
                break;
            case 4:
                $order = 'p.date_added';
                break;
            case 5:
                $order = 'p.pieces_sold';
                break;
            case 6:
                $order = 'p.updated';
                break;
            case 7:
                $order = 'p.in_store > 0 DESC';

                if (SuppliersUtil::isSuppliersStoreEnabled()) {
                    $order .= ', SUM(pos.in_store) DESC';
                    $this->productList->andSpec(function (QueryBuilder $qb) {
                        $qb->joinProductsOfSuppliers();
                    });
                }

                if ($dbcfg->prod_subtract_from_store == 'N') {
                    $order .= ', p.delivery_time DESC';
                }

                $order .= ', p.title';
                break;
            case 8:
                $order = 'pr.name, p.title';
                $this->productList->andSpec(function (QueryBuilder $qb) {
                    $qb->joinProducersOnProducts();
                });
                break;
            case 10:
                $discountFieldDefinition = Contexts::get(UserContext::class)->getOriginalPriceType()->getDiscountFieldDefinition();
                $order = $discountFieldDefinition->getField();
                $this->productList->andSpec($discountFieldDefinition->getSpec());
                break;
            case 11:
                $order = 'p.pieces_sold * p.price';
                break;
            case 12:
                $order = 'p.date_stock_in';
                break;
            case 13:
                $order = 'p.in_store';
                break;
            case 14:
                $order = 'p.price';
                if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_COMMON)) {
                    $this->productList->andSpec(function (QueryBuilder $qb) {
                        $qb->joinVatsOnProducts();
                    });

                    $getDiscountField = function ($alias = 'p') {
                        return "MAX(((({$alias}.price_common - ({$alias}.price * ((v.vat / 100) + 1))) / {$alias}.price_common) * 100))";
                    };

                    $order = "COALESCE(IF(pv.id IS NULL, {$getDiscountField()}, {$getDiscountField('pv')}), 0)";
                }
                break;
            default:
                $order = 'p.title';
                break;
        }

        return $order;
    }

    protected function getOrdering()
    {
        $order_params = $this->getOrderingBy();

        $order_by = $order_params['order_by'];
        $order_dir = $order_params['order_dir'];

        $order = $this->getOrderFields($order_by);

        switch ($order_dir) {
            case 1:
                $dir = ' ASC ';
                break;
            case 2:
                $dir = ' DESC ';
                break;
            default:
                $dir = ' ASC ';
                break;
        }

        if ($order_params['default_sort']) {
            $productAlias = 'p';
            if (isset($this->category['list_variations']) && $this->category['list_variations'] == 'Y') {
                $productAlias = 'pv';
            }
            $query = $this->defaultOrderStart($productAlias, []);

            $order = "{$query['order_start']}, {$order}";
        }

        $this->productList->orderBy($order, $dir);

        return [
            'order' => $order_params['default_sort'] ? '' : $order_params['order'],
            'order_by' => $order_params['default_sort'] ? '' : $order_by,
            'order_dir' => $order_params['default_sort'] ? '' : $order_dir,
        ];
    }

    protected function defaultOrderStart($productAlias, $query)
    {
        $dbcfg = \Settings::getDefault();

        $this->productList->andSpec(function (QueryBuilder $qb) {
            if ($this->categoryId && $this->producerId) {
                $cond = 'psp.id_section = :id_section AND psp.id_producer = :id_producer';
            } elseif ($this->producerId) {
                $cond = 'psp.id_producer = :id_producer AND psp.id_section IS NULL';
            } else {
                $cond = 'psp.id_section = :id_section AND psp.id_producer IS NULL';
            }

            $qb->leftJoin('p', 'products_in_sections_positions', 'psp', 'p.id = psp.id_product AND '.$cond)
                ->addParameters(['id_section' => $this->categoryId, 'id_producer' => $this->producerId]);
        });

        $defaultOrderByPosition = 'COALESCE(psp.position - '.POSITION_STANDARD.', p.position, '.POSITION_STANDARD.')';
        if (($this->getCategory()['data']['enable_sort_by_product_position'] ?? 'Y') === 'N') {
            $defaultOrderByPosition = 'COALESCE(psp.position - '.POSITION_STANDARD.', '.POSITION_STANDARD.')';
        }

        $query['order_start'] = $defaultOrderByPosition;
        if ($dbcfg->prod_subtract_from_store) {
            if (($this->getCategory()['data']['enable_sort_by_in_store'] ?? $dbcfg->sort_in_cat_by_in_store) === 'Y') {
                $orderStartNotInStore = "{$defaultOrderByPosition} + 10000";

                if (findModule(\Modules::PRODUCTS_SUPPLIERS)) {
                    $this->productList->andSpec(function (QueryBuilder $qb) use ($productAlias) {
                        $qb->joinProductsOfSuppliers($productAlias);
                    });

                    $orderStartNotInStore = "IF(SUM(pos.in_store) > 0, {$defaultOrderByPosition} + 5000, {$orderStartNotInStore})";
                }

                $query['order_start'] = 'IF('.$this->getOrderingInstoreField($productAlias)." > 0, {$defaultOrderByPosition}, {$orderStartNotInStore})";
            }
        }

        return $query;
    }

    protected function getOrderingInStoreField(string $productAlias): string
    {
        return "{$productAlias}.in_store";
    }

    public function getTemplate()
    {
        if ($templateName = $this->category['template'] ?? null) {
            return "section/{$templateName}.tpl";
        }

        if (isAjax()) {
            return 'category.ajax.tpl';
        }

        return parent::getTemplate();
    }

    public function getWpjToolbar()
    {
        $wpjToolbar = [];
        $this->categoryId;

        if ($this->categoryId > 0 && $this->producerId > 0) {
            $params = [
                'ID' => "{$this->categoryId}-{$this->producerId}",
            ];
            $wpjToolbar = [
                'url' => getAdminUrl('sectionProducer', $params),
                'title' => 'Upravit sekci výrobce',
            ];

            if ($this->categoryProducer) {
                $wpjToolbar += [
                    'translateUrl' => getAdminUrl('list', [
                        'type' => 'translateSectionProducer',
                        'iframe' => 1,
                        'objectIDs' => $this->categoryProducer['id'],
                    ]),
                    'translateTitle' => 'Překlad kategorie výrobce',
                ];
            }
        } elseif ($this->categoryId >= 0 && $this->producerId === null) {
            $params = ['ID' => $this->categoryId];
            if ($this->producerId > 0) {
                $params['id_producer'] = $this->producerId;
                $params['flap'] = 7;
            }
            $wpjToolbar = [
                'url' => getAdminUrl('sections', $params),
                'title' => 'Upravit sekci',
                'translateUrl' => getAdminUrl('list', [
                    'type' => 'translateSections',
                    'iframe' => 1,
                    'objectIDs' => $this->categoryId,
                ]),
                'translateTitle' => 'Překlad sekce',
            ];
            $wpjToolbar['indexed_filter'] = $this->getIndexedFilterAdminUrl();
        } elseif ($this->producerId > 0) {
            $wpjToolbar = [
                'url' => getAdminUrl('producers', ['ID' => $this->producerId]),
                'title' => 'Upravit výrobce',
                'translateUrl' => getAdminUrl('list', [
                    'type' => 'translateProducers',
                    'iframe' => 1,
                    'objectIDs' => $this->producerId,
                ]),
                'translateTitle' => 'Překlad výrobce',
            ];
        }

        return array_merge(parent::getWpjToolbar(), $wpjToolbar);
    }

    public function getIndexedFilterAdminUrl()
    {
        $url = false;
        $indexed_filter = $this->getIndexedFilter();
        if ($indexed_filter) {
            if (!empty($indexed_filter['id'])) {
                $params = ['ID' => $indexed_filter['id']];
            } else {
                $params = $indexed_filter + ['title' => $this->getTitle()];
            }
            $url = getAdminUrl('IndexedFiltersContent', $params);
        }

        return $url;
    }

    protected function getIndexedFilter()
    {
        if (isset($this->indexed_filter)) {
            return $this->indexed_filter;
        }

        if (findModule(\Modules::INDEXED_FILTER)) {
            if (empty($this->filterData['producerIds']) && empty($this->filterData['parameters']) && empty($this->filterData['variationsSizeConvertion'])) {
                return $this->indexed_filter;
            }
            $params = ['id_section' => $this->categoryId, 'id_producer' => $this->producerId,
                'parameter1' => null, 'parameter2' => null, 'parameter3' => null, ];
            if (!empty($this->filterData['producerIds'])) {
                $params['id_producer'] = reset($this->filterData['producerIds']);
            }
            $parameters = [];
            if (!empty($this->filterData['parameters'])) {
                foreach ($this->filterData['parameters'] as $id_param => $param) {
                    if (!empty($param['value'])) {
                        $parameters[] = is_array($param['value']) ? reset($param['value']) : $param['value'];
                    }
                }
            }

            if (!empty($this->filterData['variationsSizeConvertion'])) {
                foreach ($this->filterData['variationsSizeConvertion'] as $id_label => $parameterValues) {
                    $parameters = array_merge($parameters, array_unique($parameterValues['value']));
                }
            }

            if (count($parameters) > 3) { // maximalne 3 parametry
                $this->indexed_filter = false;
            } else {
                sort($parameters);
                $i = 1;
                foreach ($parameters as $param) {
                    $params["parameter{$i}"] = $param;
                    $i++;
                }
                $this->indexed_filter = sqlQueryBuilder()->select('ifc.*')
                    ->from('indexed_filters_content', 'ifc')
                    ->andWhere(Operator::equalsNullable($params))
                    ->andWhere(Translation::coalesceTranslatedFields(IndexedFiltersContentTranslation::class))
                    ->execute()->fetch();
                if (!$this->indexed_filter) {
                    $this->indexed_filter = $params;
                }
            }
        }

        return $this->indexed_filter;
    }

    public function getBreadcrumbs()
    {
        if (is_array($this->categoryId)) {
            $categoryID = $this->categoryId[0];
        } else {
            $categoryID = $this->categoryId;
        }

        $campaign = !empty($this->category['campaign']) ? $this->category['campaign'] : null;

        if (empty($campaign)) {
            $campaign = getVal('campaign', null, '');
        }

        if (empty($campaign) && empty($this->category) && ($this->campaign == 'F')) {
            $userContext = Contexts::get(UserContext::class);
            $navType = 'NO_TYPE';
            if ($userContext->getActiveId()) {
                $navType = 'USER';
            }

            return getReturnNavigation(-1, $navType, [translate('title', 'category')['favorites']]);
        }

        return getReturnNavigation($categoryID, 'ESHOP_CATEGORY', [], $campaign, $this->producerId);
    }

    public function getBreadcrumbsNew(): array
    {
        if (is_array($this->categoryId)) {
            $categoryID = $this->categoryId[0];
        } else {
            $categoryID = $this->categoryId;
        }

        $returnNav = getReturnNavigation($categoryID, 'ESHOP_CATEGORY', [], '', $this->producerId);

        return reset($returnNav);
    }

    protected function getTitleParams()
    {
        global $cfg;
        $title = [];

        if (!empty($this->producer['name'])) {
            $title['producer'] = $this->producer['name'];
        }

        // ################################################
        //	KAMPAN NA VYROBKY
        // ################################################
        if (array_key_exists($this->category['campaign'], $cfg['Products']['Flags'])) {
            $title['campaign'] = getCampaigns()[$this->category['campaign']]['plural'];
        } elseif ($this->category['campaign'] == 'F') {
            if (!findModule(\Modules::PRODUCTS_FAVORITES, \Modules::SUB_PRODUCTS_FAVORITES_NOT_LOGGED)) {
                loggedRedirect();
            }
            $title['campaign'] = translate('title', 'category')['favorites'];
        }
        // ################################################

        if (($this->categoryId == 0) && (!empty($title['producer']) || !empty($title['campaign']))) {
            // pokud mame producera nebo campaign a root category -> nedavame do title category name (Produkty)
        } else {
            if (!empty($this->category['name'])) {
                $title = array_merge(['category' => $this->category['name']], $title);
            }
        }

        return $title;
    }

    public function getTitle()
    {
        $title = $this->getTitleParams();

        $title = join(' - ', $title).$this->createTitle();

        $indexed_filter = $this->getIndexedFilter();
        if (!empty($indexed_filter['title'])) {
            $title = $indexed_filter['title'];
        }

        return $title;
    }

    public function getMetaTitle()
    {
        $indexed_filter = $this->getIndexedFilter();
        if (!empty($indexed_filter['meta_title'])) {
            return $indexed_filter['meta_title'];
        }

        if (!empty($this->categoryProducer['meta_title'])) {
            return $this->categoryProducer['meta_title'].$this->createTitle();
        }

        $parts = [];

        global $cfg;

        if (!empty($this->producer['meta_title'])) {
            $parts['producer'] = $this->producer['meta_title'];
        } elseif (!empty($this->producer['name'])) {
            $parts['producer'] = $this->producer['name'];
        }

        if (array_key_exists($this->category['campaign'], $cfg['Products']['Flags'])) {
            $parts['campaign'] = getCampaigns()[$this->category['campaign']]['plural'];
        } elseif ($this->category['campaign'] == 'F') {
            if (!findModule(\Modules::PRODUCTS_FAVORITES, \Modules::SUB_PRODUCTS_FAVORITES_NOT_LOGGED)) {
                loggedRedirect();
            }
            $parts['campaign'] = translate('title', 'category')['favorites'];
        }

        if (($this->categoryId == 0) && (!empty($parts['producer']) || !empty($parts['campaign']))) {
            // pokud mame producera nebo campaign a root category -> nedavame do title category name (Produkty)
        } else {
            if (!empty($this->category['meta_title'])) {
                array_unshift($parts, $this->category['meta_title']);
            } elseif (!empty($this->category['name'])) {
                array_unshift($parts, $this->category['name']);
            }
        }

        return join(' - ', array_filter($parts)).$this->createTitle();
    }

    public function getMetaKeywords()
    {
        $parts = [];

        if (!empty($this->category['meta_keywords'])) {
            $parts['category'] = $this->category['meta_keywords'];
        }
        if (!empty($this->producer['meta_keywords'])) {
            $parts['producer'] = $this->producer['meta_keywords'];
        }

        return join(',', $parts);
    }

    public function getMetaDescription()
    {
        $indexed_filter = $this->getIndexedFilter();
        if (!empty($indexed_filter['meta_description'])) {
            return $indexed_filter['meta_description'];
        }

        if (!empty($this->categoryProducer['meta_description'])) {
            return $this->categoryProducer['meta_description'];
        }

        $parts = [];

        if (!empty($this->category['meta_description'])) {
            $parts['category'] = $this->category['meta_description'];
        }
        if (!empty($this->producer['meta_description'])) {
            $parts['producer'] = $this->producer['meta_description'];
        }

        // Kategorie
        if (!isset($parts['category']) && !empty($this->category['id_block']) && $this->categoryId > 0) {
            $block = $this->block->getBlockContent($this->category['id_block'], true);
            if ($block) {
                $parts['category_block'] = $block;
            }
        }

        if (!isset($parts['producer']) && $id_producer_block = $this->producer['id_block'] ?? null) {
            $block = $this->block->getBlockContent($id_producer_block, true);
            if ($block) {
                $parts['producer_block'] = $block;
            }
        }

        if ($parts) {
            return StringUtil::cutToWords(join(', ', $parts), 160);
        } else {
            return $this->getMetaTitle().' - '.\Settings::getDefault()->shop_description;
        }
    }

    public function getCampaign()
    {
        return $this->campaign;
    }

    /**
     * Returns correct url of category.
     */
    public function getCorrectUrl(): ?string
    {
        if (!$this->getCategory()) {
            return null;
        }

        // Redirect if parents are not correct
        if (!$this->categoryId && !$this->producerId) {
            $urlCorrect = createScriptURL([
                'URL' => 'launch.php',
                's' => 'category',
                'IDcat' => '0',
                'campaign' => $this->campaign,
                'TITLE' => translate('category', 'SEO_URL'),
            ]);

            return $urlCorrect;
        }

        $parentsIDs = [];
        $title = '';
        if ($this->categoryId >= 0) {
            if (is_array($this->categoryId)) {
                $this->categoryId = $this->categoryId[0];
            }
            $section = ServiceContainer::getService(SectionTree::class)->getSectionById($this->categoryId);
            if (!$section) {
                return null;
            }
            $parentsIDs = $section['parents'];
            $title = $section['name'];
        }

        $urlCorrect = createScriptURL([
            'URL' => 'launch.php',
            's' => 'category',
            'IDcat' => $this->categoryId,
            'IDpd' => $this->producerId,
            'IDpv' => $this->parameterValueId,
            'TITLE' => $title,
            'producerTitle' => $this->producer['name'] ?? null,
            'ESCAPE' => 'NO',
            'parents' => $parentsIDs,
            'campaign' => $this->campaign,
        ]);

        return $urlCorrect;
    }

    protected function getListShowType()
    {
        return $this->categoryUtil->getListShowType();
    }

    protected function getDynamicFilter()
    {
        $this->baseFilter = new \Filter(clone $this->filterParams);

        $this->filter = new \Filter($this->filterParams);
        try {
            $this->filter->setFilterData($this->filterData);
        } catch (\InvalidArgumentException $e) {
            // ignore it
            // InvalidArgumentException is thrown when something went wrong in class Decimal
        }
        $this->filterActive = (bool) array_filter($this->filterData);

        return $this->dynamicFilterAttributes + [
            'filter' => $this->filter,
            'baseFilter' => $this->baseFilter,
        ];
    }

    public function getDynamicFilterConfiguration()
    {
        if (!is_null($this->dynamicFilterAttributes)) {
            return $this->dynamicFilterAttributes;
        }

        $parameters = [];
        if (findModule('producers') || findModule('products_sections')) {
            $qb = sqlQueryBuilder()
                ->select('pa.*')
                ->from('parameters', 'pa')
                ->leftJoin('pa', 'parameters_sections', 'ps', 'ps.id_section=:id_section AND ps.id_parameter=pa.id')
                ->setParameter('id_section', $this->getCategory()['id'])
                ->where(Operator::orX(
                    findModule('products_sections') ? Operator::andX(\Query\Parameter::inSections([$this->getCategory()['id']]), Operator::equals(['ps.filter' => 'Y'])) : null,
                    findModule('producers') ? \Query\Parameter::inProducers([$this->producerId]) : null))
                ->groupBy('pa.id')
                ->orderBy('ps.weight')
                ->addOrderBy('pa.position');

            if (findModule(\Modules::INDEXED_FILTER)) {
                $qb->addSelect('ps.to_title, ps.indexing');
            }

            $qb->andWhere(Translation::coalesceTranslatedFields(\KupShop\I18nBundle\Translations\ParametersTranslation::class, ['name', 'descr', 'name_frontend']));
            $parameters = sqlFetchAll($qb->execute(), 'id');
        }

        $variations = [];
        if (findModule('products_variations')) {
            $qb = sqlQueryBuilder()
                ->select('pvcl.id, pvcl.label as name')
                ->from('products_variations_choices_labels', 'pvcl')
                ->where(\Query\Variation::labelsInSections([$this->getCategory()['id']]))
                ->orderBy('pvs.weight');

            if (findModule(\Modules::INDEXED_FILTER)) {
                $qb->addSelect('to_title, indexing');
            }

            if (findModule(\Modules::CONVERTORS)) {
                $qb->addSelect('convertor');
            }

            $qb->andWhere(Translation::coalesceTranslatedFields(\KupShop\I18nBundle\Translations\VariationsLabelsTranslation::class, ['label' => 'name']));

            $variations = sqlFetchAll($qb->execute(), 'id');
        }
        $producers = true;
        if (findModule(\Modules::PRODUCERS)) {
            $producers = $this->getCategory()->get('producers_filter') == 'Y';
            if ($producers && findModule(\Modules::INDEXED_FILTER)) {
                $producers = [
                    'to_title' => $this->getCategory()->get('producers_to_title'),
                    'indexing' => $this->getCategory()->get('producers_indexing'),
                ];
            }
        }

        $labels = findModule(\Modules::LABELS) && $this->getCategory()->get('labels_filter') === 'Y';

        return $this->dynamicFilterAttributes = [
            'parameters' => $parameters,
            'variations' => $variations,
            'producers' => $producers,
            'labels' => $labels,
        ];
    }

    public function getSortOptions(): array
    {
        $sortOptions = $this->sort_options;

        if (findModule(\Modules::STOCK_IN)) {
            $sortOptions['dateStockIn'] = 12;
        }

        if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
            $sortOptions['storeValue'] = 15;
        }

        return $sortOptions;
    }

    public function findNextPrevProduct(int $nextProduct)
    {
        $this->getCategory();
        $this->prepareProductList();
        $this->prepareFilterParams();
        $this->prepareUrlFilterData();
        $this->getOrdering();
        $this->getDynamicFilter();

        $qb = $this->productList->getQueryBuilder();

        $dir = $nextProduct > 0 ? 1 : -1;
        $nextProduct = abs($nextProduct);

        $index = getVal('row', $this->getPrevNext(['id' => $nextProduct], $qb));
        $index += $dir;
        $id = getVal('id', $this->getPrevNext(['row' => $index], $qb));

        if (!$id) {
            if ($dir > 0) {
                $index = 0;
            } else {
                $index = returnSQLResult("SELECT COUNT(*) FROM ({$qb->getSQL()}) a", $qb->getParameters(), $qb->getParameterTypes()) - 1;
            }

            $id = getVal('id', $this->getPrevNext(['row' => $index], $qb));
        }

        throw new RedirectException(
            createScriptURL([
                's' => 'redir',
                'type' => 'product',
                'id' => $id,
            ])
        );
    }

    private function getPrevNext($params, $qb)
    {
        $field = key($params);

        $qb->select('p.id as id');

        return sqlFetchAssoc(sqlQuery("SELECT *
               FROM
               (SELECT @rownum:=@rownum+1 row, a.id
               FROM ({$qb->getSQL()}) a, (SELECT @rownum:=-1) r) as z
               WHERE z.{$field} = :{$field}", array_merge($qb->getParameters(), $params), $qb->getParameterTypes()));
    }

    protected function prepareFilterParams()
    {
        $this->filterParams = $this->productList->applyDefaultFilterParams();

        if ($this->categoryId > 0) {
            if ($this->category['virtual'] == 'Y' && $this->category['virtual_materialized'] !== 'Y') {
                // section is virtual and materialized section is disabled, so we use runtime filter
                $virtual_settings = ($this->category['data']['virtual_settings'] ?? null);
                if ($virtual_settings) {
                    $productsFilterSpecs = ServiceContainer::getService(VirtualCategoryProductsFilterSpecs::class);
                    $productsFilterSpecs->setCategoryId($this->categoryId);
                    $filterSpecs = $productsFilterSpecs->getSpecs($virtual_settings);
                    $this->filterParams->andSpec($filterSpecs);
                }
            } else {
                // Filter products in current category
                if ($this->category['behaviour'] == 1) {
                    $this->filterParams->setSections([$this->categoryId]);
                } else {
                    $this->filterParams->setSections(getDescendantCategories($this->categoryId));
                }
            }
        }

        if ($this->category['campaign']) {
            $this->filterParams->setCampaign($this->category['campaign']);
        }

        if ($this->producerId) {
            $this->filterParams->setProducers([$this->producerId]);
        }
    }

    private function applyProductListSpecs(\ProductList $productList): void
    {
        $productList->fetchImages(2, null, $productList->getVariationsAsResult());
        $productList->fetchProducers();

        // Variations in product list
        $variations = findModule('products_variations', 'category_variations');
        if ($variations) {
            $productList->fetchVariations($variations);
        }

        // Parameters in product list
        $parameters = findModule('products_parameters', 'category_params');
        if ($parameters) {
            $productList->fetchParameters($parameters);
        }

        // Product sets in product list
        if (findModule('products_sets')) {
            $productList->fetchSets();
        }

        // Product sets in product list
        if (findModule('reviews', 'show_in_category')) {
            $productList->fetchReviews();
        }

        if (findModule(\Modules::PRICE_LEVELS)) {
            $priceLevelContext = ServiceContainer::getService(PriceLevelContext::class);
            $priceLevel = $priceLevelContext->getActive();
            if ($priceLevel && $priceLevel->sections) {
                $productList->andSpec(
                    function ($qb) {
                        $subSelect = sqlQueryBuilder()->select('GROUP_CONCAT(DISTINCT id_section SEPARATOR ",")')
                            ->from('products_in_sections')->where('id_product = p.id');
                        $qb->addSubselect($subSelect, 'priceLevelSections');
                    }
                );
            }
        }
    }

    protected function prepareProductList()
    {
        // Create products list with filter
        $this->productList = $this->createProductList();

        $this->applyProductListSpecs($this->productList);
    }

    protected function createProductList()
    {
        return $this->doCreateProductList();
    }

    protected function doCreateProductList($preferVariations = false)
    {
        $variationsAsResult = $preferVariations
            || getVal('list_variations', $this->category, 'N') == 'Y'
            || getVal('list_variations', null, 'N') == 'Y'; // TODO Big ugly hack - delete me when virtual categories implemented

        $this->productList->setVariationsAsResult(findModule(\Modules::PRODUCTS_VARIATIONS) && $variationsAsResult);

        return $this->productList;
    }

    protected function getOrderFieldPrice()
    {
        $this->productList->andSpec(function (QueryBuilder $qb) {
            $qb->joinVatsOnProducts();
        });

        $rate = \DecimalConstants::one()->div(!$this->currencyContext->getActive()->getRate()->isZero() ? $this->currencyContext->getActive()->getRate() : \DecimalConstants::one());

        if ($this->priceListContext) {
            if ($this->priceListContext->getActiveId()) {
                return 'COALESCE(IF(COALESCE(MAX(prlv.discount), prlp.discount) != 0, MIN(COALESCE(prlv.price * (1-COALESCE(prlv.discount, prlp.discount, 0)/100), pv.price*'.$rate.', prlp.price * (1-COALESCE(prlv.discount, prlp.discount, 0)/100), p.price*'.$rate.')), MIN(COALESCE(prlv.price, pv.price*'.$rate.', prlp.price, p.price*'.$rate.')))*(1+v.vat/100), MIN(COALESCE(pv.price, p.price))=0, (MIN(IF(COALESCE(pv.price, p.price)*'.$rate.' > 0, COALESCE(pv.price, p.price)*'.$rate.', NULL))*(1+v.vat/100)*(1-p.discount/100)))';
            }
        }

        // Potřebuju aby se produkty/varianty s nulovou cenou řadily až na konec.
        // Ignoruju pak všechny varianty s nulovou cenou, aby se  řadilo podle nenulových variant
        return '(MIN(IF(COALESCE(pv.price, p.price) > 0, COALESCE(pv.price, p.price), NULL))*(1+v.vat/100)*(1-p.discount/100))';
    }

    /**
     * @return $this
     */
    public function setParsedURLData($parsedData)
    {
        $this->parsedURLData = $parsedData;

        return $this;
    }

    /**
     * @return CategoryView
     */
    public function setCampaign($campaign)
    {
        $this->campaign = $campaign;

        return $this;
    }

    public function setFilterData(array $filterData): CategoryView
    {
        $this->filterData = $filterData;

        return $this;
    }

    public function getResponse(?Request $request = null)
    {
        if (findModule(\Modules::RESPONSE_CACHE, 'section')
            && !isFunctionalTests()
        ) {
            $cacheEnabled = !getAdminUser();
            $isHit = false;

            $campaign = $this->getCampaign();
            if ($campaign == 'F' || $this instanceof WatchdogView) {
                $cacheEnabled = false;
            }

            $cacheContext = Contexts::get(CacheContext::class);
            $cacheKeyContext = $cacheContext->getKey([CacheContext::TYPE_FULL_PAGE, CacheContext::TYPE_PRICE, CacheContext::TYPE_TEXT]);

            if (is_null($cacheKeyContext)) {
                $cacheEnabled = false;
            }

            if ($cacheEnabled) {
                $this->responseCacheEnabled = true;

                $cacheKey = [
                    'sections',
                    $this->categoryId,
                    $this->producerId,
                    !empty($campaign) ? $campaign : '',
                    isAjax() ? 'ajax' : 'regular',
                    $cacheKeyContext,
                    md5($this->requestStack->getCurrentRequest()->getRequestUri()),
                    'rc',
                ];
                $cacheKey = join('-', $cacheKey);

                $responseCache = ServiceContainer::getService(ResponseCache::class);
                $response = $responseCache->wrap(function () {
                    return parent::getResponse();
                }, $cacheKey, 5 * 60, null, $isHit);
            }

            $metrics = ServiceContainer::getService(PrometheusWrapper::class);
            $metrics->addLabel('custom', join('-', [$this->categoryId]));

            $metrics->setCounter('kupshop', 'section_cached', '', 1, ['used' => $cacheEnabled ? '1' : '0']);
            $metrics->setCounter('kupshop', 'section_cache_used', '', 1, ['used' => $isHit ? '1' : '0']);
        }
        if (!isset($response)) {
            $response = parent::getResponse($request);
        }

        return $response;
    }

    public function getShowInSearch(): bool
    {
        return ($this->getCategory()['show_in_search'] ?? 'Y') == 'Y';
    }

    protected function useCanonicalUrl()
    {
        if (!empty($this->getIndexedFilter()['id'])) {
            return false;
        }

        return parent::useCanonicalUrl();
    }

    protected function getCurrentUrl(Request $request): string
    {
        $currentUrl = parent::getCurrentUrl($request);

        return $this->sectionUtil->removeFilterFromSectionUrl($request, $currentUrl);
    }

    public function getCategoryId()
    {
        return $this->categoryId;
    }

    public function getProducerId()
    {
        return $this->producerId;
    }

    #[Required]
    final public function _setSliderUtil(SliderUtil $sliderUtil): void
    {
        $this->sliderUtil = $sliderUtil;
    }

    #[Required]
    final public function _setCategoryUtil(SectionViewUtil $categoryUtil): void
    {
        $this->categoryUtil = $categoryUtil;
    }

    #[Required]
    final public function setCompileStrategy(CompileStrategy $compileStrategy): void
    {
        $this->compileStrategy = $compileStrategy;
    }

    #[Required]
    final public function setBlock(Block $block): void
    {
        $this->block = $block;
    }

    #[Required]
    final public function setSectionUtil(SectionUtil $sectionUtil): void
    {
        $this->sectionUtil = $sectionUtil;
    }
}
