<?php declare(strict_types=1);

namespace Newland\Toubiz\Poi\Neos\Domain\Repository;

use Doctrine\ORM\AbstractQuery;
use Newland\NeosCommon\Domain\Repository\TopicRepository as CommonTopicRepository;
use Newland\NeosCommon\Domain\Model\Topic;
use Newland\Toubiz\Api\Service\LanguageAware;
use Newland\Toubiz\Poi\Neos\Service\AttributeTopicService;
use Newland\Toubiz\Sync\Neos\Domain\Filter\CategoryFilter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Category;
use Newland\Toubiz\Sync\Neos\Domain\Repository\CategoryRepository;
use Neos\Flow\Annotations as Flow;

/**
 * @Flow\Scope("singleton")
 */
class TopicRepository extends CommonTopicRepository
{
    use LanguageAware;
    public const TYPE_CATEGORY = 'category';
    public const TYPE_ATTRIBUTE = 'attribute';
    public const TYPE_TAG = 'tag';

    /**
     * Types of topics this repository handles.
     * For all types in this array there must be a `findAll{Type}` and `findBy{Type}`
     * method.
     *
     * @var string[]
     */
    protected $types = [
        self::TYPE_CATEGORY,
        self::TYPE_ATTRIBUTE,
        self::TYPE_TAG,
    ];

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

    /**
     * @var AttributeTopicService
     * @Flow\Inject()
     */
    protected $attributeTopicService;

    /**
     * @var array
     * @Flow\InjectConfiguration(package="Newland.Toubiz.Poi.Neos", path="tags")
     */
    protected $tagConfiguration;

    /**
     * @param array $config
     * @return Topic[]
     */
    public function findAllCategory(array $config = []): iterable
    {
        $articleType = $config['articleType'] ?? null;
        $filter = new CategoryFilter();
        $filter->setOrderBy([ 'category.title' => 'ASC' ]);

        if ($articleType !== null) {
            $filter->setArticleMainType($articleType);
        }

        $results = $this->categoryRepository->withLanguage($this->language, function () use ($filter) {
            return (array) $this->categoryRepository->findByFilter($filter);
        });

        return array_map(
            function (Category $category) {
                return $this->topicFromCategory($category);
            },
            $results
        );
    }

    /**
     * @param string $categoryId
     * @return Topic|null
     */
    public function findByCategory(string $categoryId): ?Topic
    {
        $category = $this->categoryRepository->findByIdentifier($categoryId);
        if (!$category) {
            return null;
        }

        return $this->topicFromCategory($category);
    }

    public function findCategoryByMainType(int $mainType)
    {
        $queryBuilder = $this->categoryRepository->createQueryBuilder('category');

        $query = $queryBuilder
            ->select(
                [
                    'category.Persistence_Object_Identifier as identifier',
                    'category.title as title'
                ]
            )->groupBy('category.Persistence_Object_Identifier')
            ->innerJoin('category.articles', 'article')
            ->andWhere($queryBuilder->expr()->eq('article.mainType', ':type'))
            ->orderBy('COUNT(article.Persistence_Object_Identifier)', 'DESC')
            ->getQuery()
            ->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
            ->setParameters([ 'type' => $mainType ]);

        return $this->cache('article_topic_by_main_type_' . $mainType, function () use ($query) {
            return array_map(
                function (array $row) {
                    return new Topic(self::TYPE_CATEGORY, $row['identifier'], $row['title']);
                },
                $query->execute()
            );
        });
    }

    /**
     * @param array $config
     * @return Topic[]
     */
    public function findAllAttribute(array $config = []): iterable
    {
        $articleType = $config['articleType'] ?? null;
        return $this->findByIdentifiers(
            $this->attributeTopicService->getConfiguredIdentifiers($articleType),
            true
        );
    }

    /**
     * @param string $attributeString
     * @return Topic
     */
    public function findByAttribute(string $attributeString): Topic
    {
        $exploded = explode(':', $attributeString);
        $attribute = $exploded[0];
        $value = \count($exploded) > 1 ? $exploded[1] : null;

        $title = $this->attributeTopicService->titleForAttribute($attribute, $value) ?: $attributeString;

        return new Topic(
            self::TYPE_ATTRIBUTE,
            $attribute,
            $title,
            $value
        );
    }

    public function findAllTag(array $config): iterable
    {
        $articleType = $config['articleType'] ?? null;

        return array_map(
            function (string $tag) {
                return $this->findByIdentifier($tag);
            },
            $this->tagConfiguration['articleTypes'][$articleType] ?? []
        );
    }

    public function findByTag(string $tag): Topic
    {
        return new Topic(
            static::TYPE_TAG,
            $tag,
            $tag
        );
    }

    private function topicFromCategory(Category $category): Topic
    {
        return new Topic(
            self::TYPE_CATEGORY,
            (string) $category->getPersistenceObjectIdentifier(),
            $category->getTitle()
        );
    }
}
