<?php

declare(strict_types=1);

namespace KupShop\ComponentsBundle\Twig\Components;

use KupShop\CatalogBundle\BaseFilterParams;
use KupShop\CatalogBundle\CategorySort;
use KupShop\CatalogBundle\Entity\Section;
use KupShop\CatalogBundle\Enum\Ordering;
use KupShop\CatalogBundle\ProductList\Builder\CompileStrategy;
use KupShop\CatalogBundle\ProductList\Builder\FilteredProductListBuilder;
use KupShop\CatalogBundle\ProductList\Builder\FilteredProductListInterface;
use KupShop\CatalogBundle\ProductList\Builder\SQLCompileStrategy;
use KupShop\CatalogBundle\ProductList\DynamicFilterAttributes;
use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\CatalogBundle\ProductList\ProductListBanners;
use KupShop\CatalogBundle\Search\FulltextElastic;
use KupShop\CatalogElasticBundle\Contracts\ElasticSectionEventDispatcher;
use KupShop\CatalogElasticBundle\Dto\PreparedSectionQueryInnerState;
use KupShop\CatalogElasticBundle\Exception\ElasticSearchUnavailableException;
use KupShop\CatalogElasticBundle\SectionRequest;
use KupShop\ComponentsBundle\Twig\BaseComponent;
use KupShop\ComponentsBundle\Twig\DataProvider\ProductDataProvider;
use KupShop\ComponentsBundle\Twig\DataProvider\SectionDataProvider;
use KupShop\ContentBundle\Util\SliderUtil;
use Query\Operator;
use Query\QueryBuilder;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class FilteredProductList extends BaseComponent
{
    // required props
    public \FilterParams $baseFilterParams;
    public \FilterParams $filterParams;
    public DynamicFilterAttributes $dynamicFilterAttributes;

    // optional props (have default values)
    public Ordering|string|null $order = null;
    public int $page = 1;
    public int $perPage; // set from __constructor (from dbcfg)
    public bool $fetchAllFilters = false;
    public string $catalogSlider = 'main';
    public int $bannerPosition = 5;

    // cache internal properties
    private Section $section;
    private FilteredProductListInterface $result;
    private ProductCollection $collection;
    private CategorySort $sectionSort;

    public function __construct(
        private readonly CompileStrategy $strategy,
        private readonly SectionDataProvider $sectionDataProvider,
        private readonly ProductDataProvider $productDataProvider,
        #[Autowire(lazy: true)] private readonly ProductListBanners $banners,
        #[Autowire(lazy: true)] private readonly SliderUtil $sliderUtil,
        #[Autowire(lazy: true)] private readonly SQLCompileStrategy $SQLCompileStrategy,
    ) {
        if (!isset($this->perPage)) {
            $dbcfg = \Settings::getDefault();

            $this->perPage = (int) $dbcfg->cat_show_products;
        }
    }

    public function getProducts(): ProductCollection
    {
        if (!isset($this->collection)) {
            $this->collection = $products = $this->getResult()->getProductList()->getProducts();
            $this->productDataProvider->addProducts($products);
        }

        return $this->collection;
    }

    public function getPager(): \Pager
    {
        return $this->getResult()->getPager();
    }

    public function getDynamicFilter(): \Filter
    {
        return $this->getResult()->getDynamicFilter();
    }

    protected function getResult(): FilteredProductListInterface
    {
        if (!isset($this->result)) {
            $builder = (new FilteredProductListBuilder())
                ->baseFilterParams($this->baseFilterParams)
                ->dynamicFilter($this->filterParams)
                ->paged($this->page, $this->perPage)
                ->dynamicFilterAttributes($this->dynamicFilterAttributes ?? new DynamicFilterAttributes())
                ->fetchAllFilters($this->fetchAllFilters);

            if (is_string($this->order)) {
                $order = Ordering::fromString($this->order);
            } else {
                $order = $this->order;
            }

            if ($order) {
                $builder->orderBy($order->orderBy, $order->orderDir);
            } elseif ($catSort = $this->getSectionSort()) {
                $builder->categoryDefaultSort($catSort);
            }

            try {
                FulltextElastic::isServerAvailable() && $this->result = $builder->build($this->strategy);
            } catch (ElasticSearchUnavailableException $e) {
                $e->logToSentry();
            }

            // Fallback to SQL fetch when elastic is unavailable
            $this->result = $this->result ?? $builder->build($this->SQLCompileStrategy);

            $this->mountProductListBanners();
        }

        return $this->result;
    }

    private function mountProductListBanners(): void
    {
        if (!findModule(\Modules::SLIDERS)
            || !array_key_exists($this->catalogSlider, SliderUtil::getPositions())
            || !($this->result instanceof ElasticSectionEventDispatcher)
        ) {
            return;
        }

        $this->result->onBeforeCompile(function (SectionRequest $request, PreparedSectionQueryInnerState $me) {
            $sectionId = BaseFilterParams::wrap($request->baseFilterParams)->getSectionId();
            if ($sectionId === null) {
                return;
            }

            // Fetch the slider on the position set by the $this->sliderPosition for the current section.
            $sliders = $this->sliderUtil->fetchSliders(function (QueryBuilder $qb) use ($sectionId) {
                $qb->andWhere(Operator::inSubQuery('sl.id', sqlQueryBuilder()
                    ->select('sis.id_slider')
                    ->from('sliders_in_sections', 'sis')
                    ->andWhere(Operator::equals([
                        'sis.id_section' => $sectionId,
                        'sis.position' => $this->catalogSlider,
                    ])))
                );
            });

            if (empty($sliders)) {
                return;
            }

            $slider = reset($sliders);

            $banners = $this->banners->setStepSize($this->bannerPosition)
                ->setCount(count($slider->images))
                ->setMinPosition($this->bannerPosition)
                ->setSliderId((int) $slider->id)
                ->setSeedFromFilterParams($me->filter->getFilterParams());

            if ($extraItem = $banners->getPagerExtraItem($me->pager)) {
                $me->pager->addExtraItems($extraItem);
            }

            assert($this->result instanceof ElasticSectionEventDispatcher);
            $this->result->onAfterExecute(static function (PreparedSectionQueryInnerState $me) use ($banners) {
                if (!$banners) {
                    return;
                }

                $banners->attachToProductList($me->productList, $me->pager);
            });
        });
    }

    public function getSectionSort(): CategorySort
    {
        return $this->sectionSort ??= CategorySort::fromCategory($this->getSection());
    }

    public function getSection(): Section
    {
        if (!isset($this->section)) {
            $sectionId = BaseFilterParams::wrap($this->baseFilterParams)->getSectionId();

            $this->section = $this->sectionDataProvider->getSection($sectionId)->getObject();
        }

        return $this->section;
    }

    public function getGTMPlaceholders(): array
    {
        if ($this->getSection()->getId() > 0) {
            return ['item_list_name' => "category: /{$this->section->getUrl()}", 'item_list_id' => $this->section->getId()];
        }

        $producerId = BaseFilterParams::wrap($this->baseFilterParams)->getProducerId();
        if ($producerId) {
            $producer = $this->sectionDataProvider->getProducer($producerId);

            return ['item_list_name' => "brands: {$producer->getName()}", 'item_list_id' => $producer->getId()];
        }

        return ['item_list_name' => "category: /{$this->section->getUrl()}", 'item_list_id' => $this->section->getId()];
    }
}
