<?php

/*
 * 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.
 */

namespace Newland\Toubiz\Poi\Neos\ViewHelpers\Filter\Controller;

use Neos\ContentRepository\Domain\Model\Node;
use Neos\Flow\Annotations as Flow;
use Neos\FluidAdaptor\Core\Widget\AbstractWidgetController;
use Neos\Utility\Arrays;
use Newland\NeosCommon\Service\NodeService;
use Newland\Toubiz\Poi\Neos\Domain\Repository\TagRepository;
use Newland\Toubiz\Sync\Neos\Domain\Filter\CategoryFilter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Category;
use Newland\Toubiz\Sync\Neos\Domain\Repository\CategoryRepository;

class TagController extends AbstractWidgetController
{
    /**
     * @var CategoryRepository
     * @Flow\Inject()
     */
    protected $categoryRepository;

    /**
     * @var CategoryFilter
     * @Flow\Inject()
     */
    protected $categoryFilter;

    /**
     * @var TagRepository
     * @Flow\Inject()
     */
    protected $tagRepository;

    /**
     * @var NodeService
     * @Flow\Inject()
     */
    protected $nodeService;

    /**
     * @var Node
     */
    protected $node;

    /**
     * @var array
     */
    private $tags = [];

    /**
     * @var array
     */
    private $parameters = [];

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

    public function indexAction(): void
    {
        $this->initialize();

        if ($this->node !== null && !empty($this->parameters)) {
            $this->createTags();
        }

        $this->view->assignMultiple(
            [
                'tags' => $this->tags,
                'node' => $this->node,
                'documentNode' => $this->nodeService->getDocumentNode($this->node),
            ]
        );
    }

    private function initialize(): void
    {
        $this->node = $this->widgetConfiguration['node'] ?? null;
        $this->parameters = $this->widgetConfiguration['queryParameters'] ?? [];
        $this->queryOverride = $this->widgetConfiguration['queryOverride'] ?? [];
    }


    private function createTags(): void
    {
        $this->tags = [];
        $this->parseCategories();
        $this->parseAttributes();
    }

    private function parseCategories(): void
    {
        foreach ($this->getCategories() as $category) {
            $this->tags[] = $this->tagRepository->findOneByCategory($category);
        }
    }

    /**
     * @return Category[]
     */
    private function getCategories(): array
    {
        /** @var Category[] $categories */
        $categories = [];

        if (array_key_exists('categories', $this->parameters) && $this->hasOverrideFor('categories') === false) {
            foreach ((array) $this->parameters['categories'] as $identifier) {
                $category = $this->categoryRepository->findByIdentifier($identifier);
                if ($category !== null) {
                    $categories[] = $category;
                }
            }
        }

        return $categories;
    }

    /**
     * If some filters have an override, they are fixed and should not display tags - because tags
     * can change the query (remove the respective filter on click).
     *
     * @param string $filterSection
     * @return bool
     */
    private function hasOverrideFor(string $filterSection): bool
    {
        return !empty($this->queryOverride[$filterSection]);
    }

    private function parseAttributes(): void
    {
        if (array_key_exists('attributes', $this->parameters)) {
            $tags = [ [] ];

            foreach ($this->parameters['attributes'] as $attribute => $attributeValues) {
                if ($attribute === 'properties') {
                    $tags[] = $this->getPropertyTags($attributeValues);
                } elseif ($attribute === 'labels') {
                    $tags[] = $this->getLabelTags($attributeValues);
                } elseif ($this->isRangeAttribute($attributeValues)) {
                    if ($this->rangeNotEmpty($attributeValues)) {
                        $tags[] = [ $this->tagRepository->findOneByRange($attribute, $attributeValues) ];
                    }
                } elseif ($this->isMultiValueAttribute($attributeValues)) {
                    $tags[] = $this->getMultiValueTags($attribute, $attributeValues);
                } else {
                    $tags[] = [ $this->tagRepository->findOneByValue($attribute, '') ];
                }
            }

            $this->tags = array_merge(...$tags);
        }
    }

    private function getPropertyTags($attributeValues): array
    {
        $properties = (array) Arrays::getValueByPath($attributeValues, 'in');

        return array_map(
            function (string $property) {
                return $this->tagRepository->findOneByProperty($property);
            },
            array_values($properties)
        );
    }

    private function getLabelTags(array $attribute)
    {
        $labels = (array) Arrays::getValueByPath($attribute, 'in');

        return array_map(
            function (string $label) {
                return $this->tagRepository->findOneByLabel($label);
            },
            $labels
        );
    }

    private function isRangeAttribute($attribute): bool
    {
        return is_array($attribute)
            && array_key_exists('min', $attribute)
            && array_key_exists('max', $attribute);
    }

    private function rangeNotEmpty($values): bool
    {
        return !empty($values['min']) || !empty($values['max']);
    }

    private function isMultiValueAttribute($attributeValues): bool
    {
        return is_array(Arrays::getValueByPath($attributeValues, 'in'));
    }

    private function getMultiValueTags(string $attribute, array $attributeValues): array
    {
        $tags = array_map(
            function (string $value) use ($attribute) {
                return $this->tagRepository->findOneByValue($attribute, $value);
            },
            $attributeValues['in']
        );

        return $tags;
    }
}
