<?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\PageFrameProvider\Service\PageFrameLinkingService;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Poi\Neos\Service\ArticleFilterService;
use Newland\Toubiz\Poi\Neos\Service\ArticleUrlService;
use Newland\Toubiz\Sync\Neos\Domain\Filter\ArticleFilter;
use Newland\Toubiz\Sync\Neos\Domain\Filter\CategoryFilter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\CategoryRepository;

/**
 * Point of interests controller.
 *
 * @Flow\Scope("singleton")
 */
class PointOfInterestsController extends AbstractActionController
{

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

    /**
     * @Flow\Inject
     * @var CategoryRepository
     */
    protected $categoryRepository;

    /**
     * @var ArticleFilterService
     * @Flow\Inject
     */
    protected $articleFilterService;

    /**
     * @var ArticleUrlService
     * @Flow\Inject()
     */
    protected $articleUrlService;

    /**
     * Teaser action.
     *
     * Renders a single teaser.
     *
     * @return void
     */
    public function teaserAction(): void
    {
        if (array_key_exists('article', $this->properties)) {
            $this->view->assign('article', $this->articleRepository->find($this->properties['article']));
        }
        $this->view->assignMultiple(
            [
                'wrapStart' => $this->request->getInternalArgument('__wrapStart'),
                'wrapEnd' => $this->request->getInternalArgument('__wrapEnd'),
            ]
        );
    }

    /**
     * Teaser list action.
     *
     * Renders a list of teasers.
     *
     * @return void
     */
    public function teaserListAction(): void
    {
        $filter = $this->createArticleFilter();
        $articles = $this->articleRepository->findByFilter($filter);
        $displayStyle = $this->properties['displayStyle'];
        $this->view->assign('articles', $articles);
        $this->view->assign('enableSwiper', $displayStyle === 'swiper');
    }

    /**
     * List action.
     *
     * Lists articles and allows filtering.
     *
     * @return void
     */
    public function listAction(): void
    {
        $articleFilter = $this->createArticleFilter();
        $articleFilter->setLimit($this->configuration['listItemsPerPage']);
        $this->view->assignMultiple(
            [
                'articles' => $this->articleRepository->findByFilter($articleFilter),
                'pagination' => [
                    'isFirst' => true,
                    'page' => 1,
                    'isLast' => false,
                    'count' => $this->articleRepository->countByFilter(
                        $articleFilter,
                        $this->configuration['listItemsPerPage']
                    ),
                ],
            ]
        );

        $categoryFilter = new CategoryFilter;
        if (array_key_exists('articleType', $this->properties)) {
            $categoryFilter->setArticleMainType($this->properties['articleType']);
        }
        $categoryFilter->setOrderBy([ 'category.title' => 'asc' ]);
        $categories = $this->categoryRepository->findByFilter($categoryFilter);
        $this->view->assign('categories', $categories);
        $this->createStandaloneView('PointOfInterests/FilterList');
    }


    /**
     * Returns articles based on given filters.
     *
     * @param Node $node
     * @param array $query The filter query.
     * @return void
     */
    public function filterListAction(Node $node, array $query = []): void
    {
        $this->initializeAction($node);

        $page = (int) ($query['page'] ?? 1);

        $articleFilter = $this->createArticleFilter(array_merge($this->properties, $query));
        $articleFilter->setLimit($this->configuration['listItemsPerPage']);
        $articleFilter->setCurrentSite($this->currentSite->getNodeName());
        $articleFilter->setOffset(($page - 1) * $this->configuration['listItemsPerPage']);

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

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

    /**
     * Show action.
     *
     * Displays an article.
     *
     * @param Article $article
     * @return void
     */
    public function showAction(Article $article = null): void
    {
        $this->redirectToStaticDetailPageIfNotOnIt($article);

        $this->view->assignMultiple(
            [
                'url' => (string) $this->request->getHttpRequest()->getUri(),
                'article' => $article,
                'similarTours' => $this->getSimilarToursIfApplicable($article),
            ]
        );
    }

    /**
     * Creates a new article filter based on the given properties array
     * (or the properties of the current node if no argument is provided).
     *
     * @param array|null $properties
     * @return ArticleFilter
     */
    private function createArticleFilter(array $properties = null): ArticleFilter
    {
        $properties = $properties ?: $this->properties;

        $filter = new ArticleFilter();
        $filter->setCurrentSite($this->currentSite->getNodeName());

        if (array_key_exists('topicFilter', $properties)) {
            $filter = $this->articleFilterService->setFilterFromTopics($properties['topicFilter'], $filter);
        } elseif (array_key_exists('categoryFilter', $properties)) {
            $filter = $this->articleFilterService->setFilterFromTopics($properties['categoryFilter'], $filter);
        } elseif (array_key_exists('categories', $properties) && is_array($properties['categories'])) {
            $filter->setCategories($properties['categories']);
        }

        if (array_key_exists('recordLimit', $properties)) {
            $filter->setLimit($properties['recordLimit']);
        }

        if (array_key_exists('articleType', $properties)) {
            $filter->setMainType((int) $properties['articleType']);
        }

        /**
         * @deprecated
         * The "cities" attribute cannot be edited anymore but might still be
         * configured. Use "regions" instead.
         */
        if (array_key_exists('cities', $properties)) {
            $filter->setZips($this->properties['cities']);
        }

        if (array_key_exists('regions', $properties)) {
            $filter->setZips($this->regionService->getZipsFromRegionKeys($properties['regions']));
        }

        return $filter;
    }


    /**
     * @param Article|null $article
     * @return void
     */
    private function redirectToStaticDetailPageIfNotOnIt(Article $article = null): void
    {
        if ($article === null) {
            return;
        }

        if (strtolower($this->request->getMainRequest()->getControllerName()) !== PageFrameLinkingService::CONTROLLER) {
            $this->redirectToUri(
                $this->articleUrlService->generateUrl($article, $this->controllerContext),
                0,
                301
            );
        }
    }

    private function getSimilarToursIfApplicable(?Article $article): ?array
    {
        if ($article === null) {
            return null;
        }

        if ($article->getMainType() !== ArticleConstants::TYPE_TOUR) {
            return null;
        }

        $filter = new ArticleFilter();
        $filter->setCurrentSite($this->currentSite->getNodeName());
        $filter->setCategoryCollection($article->getCategories())
            ->setExcludes([ $article ])
            ->setLimit(4);

        return (array) $this->articleRepository->findByFilter($filter);
    }
}
