<?php
namespace Newland\Toubiz\Poi\Neos\Search\Indexer;

/*
 * 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\Flow\Annotations as Flow;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Newland\Contracts\Neos\Search\IndexerInterface;
use Newland\Contracts\Neos\Search\IndexRecordModification;
use Newland\Contracts\Neos\Search\ProgressHandler;
use Newland\Contracts\Neos\Search\SearchBackend;
use Newland\Toubiz\Api\ObjectAdapter\Attributes\GenericArticleAttributes;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Enum\ArticleType;

/**
 * Article indexer.
 *
 * Indexes articles for the search.
 *
 * @Flow\Scope("singleton")
 */
class ArticleIndexer implements IndexerInterface
{
    protected const FALLBACK_TYPE = 'article';
    protected const BATCH_SIZE = 100;

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

    /** @var int|null */
    private $total;

    /** @var string[] */
    private $indexed = [];

    /** @var array */
    protected $configuration;

    /**
     * @var array
     * @Flow\InjectConfiguration(path="clientFilter")
     */
    protected $clientFilter;

    /**
     * @Flow\Inject()
     * @var PersistenceManagerInterface
     */
    protected $persistenceManager;


    public function index(array $scopesToIndex, SearchBackend $backend, ProgressHandler $progressHandler): void
    {
        $this->articleRepository->withoutLanguageHandling(function () use ($backend, $progressHandler, $scopesToIndex) {
            $this->total = $this->articleRepository->countAll();

            $i = 0;
            foreach ($this->articleRepository->findAllIterator() as $articles) {
                foreach ($articles as $article) {
                    if (++$i % static::BATCH_SIZE === 0) {
                        $this->persistAndFlushEntityReferencesFromPersistenceManager();
                    }

                    $modification = $this->articleToModification($article, $scopesToIndex);
                    if ($modification) {
                        $backend->createOrUpdateIndexEntry($modification);
                        $this->indexed[] = $modification->getIdentifier();
                    }
                    $progressHandler->informProgress(static::FALLBACK_TYPE, \count($this->indexed), $this->total);
                }
            }
        });
    }

    public function postIndex(SearchBackend $backend): void
    {
        $backend->deleteObsoleteIndexEntries($this->indexed);
    }

    public function setConfiguration(array $configuration = []): void
    {
        $this->configuration = $configuration;
    }

    protected function persistAndFlushEntityReferencesFromPersistenceManager(): void
    {
        $this->persistenceManager->persistAll();
        $this->persistenceManager->clearState();
    }

    private function articleToModification(Article $article, array $scopes): ?IndexRecordModification
    {
        $content = [
            $article->getName(),
            $article->getAbstract(),
            $article->getDescription(),
            $this->getCategoryTitles($article),
            $article->getAdditionalSearchString(),
        ];

        foreach ($article->getAttributes() as $attribute) {
            if ($attribute->getName() === GenericArticleAttributes::ADDITIONAL_SEARCH_STRING) {
                $content[] = $attribute->getData();
            }
        }

        $scopesForArticle = $this->filterScopes($article, $scopes);
        if (empty($scopesForArticle)) {
            return null;
        }

        return new IndexRecordModification(
            (string) $article->getPersistenceObjectIdentifier(),
            ArticleType::$map[$article->getMainType()] ?? static::FALLBACK_TYPE,
            $article->getName(),
            (string) $article->getAbstract(),
            trim(implode(' ', $content)),
            $scopesForArticle,
            $article->getLanguage()
        );
    }

    private function filterScopes(Article $article, array $scopes): array
    {
        $filteredScopes = [];

        foreach ($scopes as $scope) {
            $nodeName = (string) str_replace('/sites/', '', $scope);
            $client = $this->clientFilter[$nodeName]['mainType']['values'][$article->getMainType()] ?? null;

            if (!$client || $article->getClient() === $client) {
                $filteredScopes[] = $scope;
            }
        }

        return $filteredScopes;
    }

    private function getCategoryTitles(Article $article): string
    {
        $categoryTitles = [];
        foreach ($article->getCategories() as $category) {
            $categoryTitles[] = $category->getTitle();
        }
        return implode(' ', $categoryTitles);
    }
}
