<?php
namespace Newland\Toubiz\Poi\Neos\Controller;

/*
 * This file is part of the "toubiz-poi-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\ContentRepository\Domain\Model\Node;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Poi\Neos\Domain\Repository\TopicRepository;
use Newland\Toubiz\Poi\Neos\Encoder\TopicsToQueryEncoder;
use Newland\Toubiz\Poi\Neos\Service\CacheService;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;

/**
 * @Flow\Scope("singleton")
 */
class FilteredListsController extends AbstractActionController
{
    public const CACHE_NAME = 'Newland_Toubiz_Poi_Neos-FilteredLists';
    public const CACHE_KEY_TEMPLATE = 'SelectableFilterSections-%s';

    /**
     * @var TopicRepository
     * @Flow\Inject
     */
    protected $topicRepository;

    /**
     * @Flow\Inject
     * @var ArticleRepository
     */
    protected $articleRepository;

    /**
     * @var TopicsToQueryEncoder
     * @Flow\Inject
     */
    protected $topicsToQueryEncoder;

    /**
     * @var CacheService
     * @Flow\Inject
     */
    protected $cacheService;

    /**
     * @var array
     */
    protected $queryOverride = [];

    public function indexAction()
    {
        $topics = $this->topicRepository->findByIdentifiers($this->properties['topics'] ?? []);
        $lists = $this->topicRepository->findByIdentifiers($this->properties['lists'] ?? []);

        $this->assignFilterAttributes();
        $this->view->assignMultiple(
            [
                'topics' => $topics,
                'lists' => $lists,
                'queryOverride' => $this->queryOverride,
                'listLimit' => $this->properties['listLimit'] ?? 11,
            ]
        );
    }

    /**
     * Returns articles based on given filters.
     *
     * @param array $query The filter query.
     * @param Node|null $node
     * @param string $format
     */
    public function showAction(array $query = [], Node $node = null, string $format = 'html')
    {
        // For AJAX requests we need to set the node manually and re-parse the properties, etc.
        $this->initializeNode($node);

        $demand = $this->overrideQuery($query, $this->queryOverride);
        $this->assignArticlesAndPagination($demand);
        $this->assignFilterAttributes();

        $this->view->assignMultiple(
            [
                'query' => $query,
                'node' => $this->node,
                'format' => $format,
            ]
        );
    }

    protected function initializeNode(Node $node = null): void
    {
        parent::initializeNode($node);

        $this->queryOverride = $this->topicsToQueryEncoder->encode($this->getPreselectedTopics());

        $regions = $this->properties['regions'] ?? [];
        $this->queryOverride = array_merge_recursive($this->queryOverride, [ 'regions' => (array) $regions ]);

        $this->cacheService->setCacheName(self::CACHE_NAME);
    }

    private function assignFilterAttributes()
    {
        $this->view->assignMultiple(
            [
                'filterSections' => $this->getSelectableFilterSectionsFromCache(),
                'queryOverride' => $this->queryOverride,
                'argumentNamespace' => $this->request->getArgumentNamespace(),
            ]
        );
    }

    private function getSelectableFilterSectionsFromCache(): array
    {
        $cacheKey = sprintf(self::CACHE_KEY_TEMPLATE, $this->node->getIdentifier());
        $hash = md5(serialize($this->filterSections));
        $filterSections = $this->cacheService->get($cacheKey, $hash);

        if (!$filterSections) {
            $filterSections = $this->filterSections;
            if (!empty($this->getPreselectedTopics())) {
                $filterSections = $this->generateSelectableFilterSections();
            }
            $this->cacheService->set($cacheKey, $filterSections, $hash);
        }

        return $filterSections;
    }

    private function assignArticlesAndPagination(array $query): void
    {
        $page = max((int) ($query['page'] ?? 1), 1);

        $articleFilter = $this->initializeFilter(array_merge($this->properties, $query));
        $articleFilter->setOffset(($page - 1) * $this->configuration['gridItemsPerPage']);

        $articles = $this->articleRepository->findByFilter($articleFilter);

        $articleCounts = $this->articleRepository->countByFilter(
            $articleFilter,
            $this->configuration['gridItemsPerPage']
        );

        $this->view->assignMultiple(
            [
                'articles' => $articles,
                'pagination' => [
                    'isFirst' => $page === 1,
                    'page' => $page,
                    'isLast' => $articleCounts['pages'] <= $page,
                    'count' => $articleCounts,
                ],
            ]
        );
    }

    /**
     * If an override is set, then the relevant parts of the current query are ignored
     * and replaced with the override.
     *
     * @param array $query
     * @param array $override
     * @return array
     */
    private function overrideQuery(array $query = [], array $override = []): array
    {
        foreach ($override as $key => $value) {
            $query[$key] = $value;
        }

        return $query;
    }

    /**
     * @return array
     */
    private function generateSelectableFilterSections(): array
    {
        $filterSections = [];

        foreach ($this->filterSections as $filterSectionKey => $filterSectionConfig) {
            if (!is_array($filterSectionConfig) || !array_key_exists('fieldSets', $filterSectionConfig)) {
                continue;
            }

            $filterSection = [ 'fieldSets' => [] ];

            foreach ($filterSectionConfig['fieldSets'] as $fieldSetKey => $fieldSetConfig) {
                if ($fieldSetConfig['type'] === 'categories') {
                    // If a category is preselected, then we hide the field set.
                    if (!array_key_exists('categories', $this->queryOverride)) {
                        $filterSection['fieldSets'][$fieldSetKey] = $fieldSetConfig;
                    }
                } elseif ($fieldSetConfig['type'] === 'checkboxes'
                    && isset($fieldSetConfig['items'])
                    && is_array($fieldSetConfig['items'])
                ) {
                    // If an attribute is preselected, we have to check if the field set is a list of variants.
                    // For variants, all other variant options are also hidden.
                    // If the field set is not a set of variants, we only hide single options.
                    if (empty($fieldSetConfig['variants'])) {
                        // No variants here. Remove any preselected options and add rest of the field set.
                        $fieldSetConfig['items'] = array_diff(
                            $fieldSetConfig['items'],
                            $this->getPreselectedTopics()
                        );
                        if (\count($fieldSetConfig['items'])) {
                            $filterSection['fieldSets'][$fieldSetKey] = $fieldSetConfig;
                        }
                    } elseif (isset($fieldSetConfig['variants'])
                        && $fieldSetConfig['variants']
                        && \count(array_intersect($fieldSetConfig['items'], $this->getPreselectedTopics()))
                    ) {
                        // If one or more variant is preselected, we skip the entire fieldset.
                        continue;
                    } else {
                        // Nothing is preselected. Add the entire fieldset.
                        $filterSection['fieldSets'][$fieldSetKey] = $fieldSetConfig;
                    }
                } else {
                    // FieldSets of type "range" cannot be pre-filtered right now. So we always show the field set.
                    $filterSection['fieldSets'][$fieldSetKey] = $fieldSetConfig;
                }
            }

            if (!empty($filterSection['fieldSets'])) {
                $filterSections[$filterSectionKey] = $filterSection;
            }
        }

        return $filterSections;
    }

    private function getPreselectedTopics(): array
    {

        $preselectedTopics = array_merge(
            $this->properties['preselectedCategories'] ?? [],
            $this->properties['preselectedAttributes'] ?? [],
            $this->properties['preselectedTags'] ?? []
        );

        if (is_array($this->properties)) {
            $preselectedTopics = array_merge(
                $preselectedTopics,
                $this->properties['preselectedTopics'] ?? []
            );
        }
        return $preselectedTopics;
    }
}
