<?php declare(strict_types=1);
namespace Newland\Toubiz\Search\Neos\Command;

/*
 * This file is part of the "toubiz-search-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\Cli\CommandController;
use Neos\Flow\ObjectManagement\Exception\InvalidObjectException;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use Newland\Contracts\Neos\Search\IndexerInterface;
use Newland\Contracts\Neos\Search\SearchBackend;
use Newland\Contracts\Neos\Search\ProgressHandler;
use Newland\Toubiz\Search\Neos\Backend\SearchBackendFactory;
use Newland\Toubiz\Search\Neos\Progress\NullHandler;
use Newland\Toubiz\Search\Neos\Progress\ProgressBarHandler;
use Newland\Toubiz\Search\Neos\Utility\ArrayUtility;

/**
 * Indexer command controller.
 *
 * @Flow\Scope("singleton")
 */
class IndexerCommandController extends CommandController
{
    /**
     * @Flow\InjectConfiguration
     * @var array
     */
    protected $configuration;

    /**
     * @var SiteRepository
     * @Flow\Inject()
     */
    protected $siteRepository;

    /** @var SearchBackend */
    protected $searchBackend;

    public function injectSearchBackend(SearchBackendFactory $factory): void
    {
        $this->searchBackend = $factory->getSearchBackendFromConfiguration();
    }

    /**
     * Updates the index based on registered indexers.
     *
     * @param bool $quiet
     * @param string $only
     * @return void
     */
    public function runCommand(bool $quiet = false, string $only = ''): void
    {
        $progressHandler = $quiet ? new NullHandler() : new ProgressBarHandler($this->output->getOutput());
        $scopes = $this->getScopes();
        $onlyIndexerTypes = ArrayUtility::emptyExplode(',', $only);

        $this->searchBackend->initialize();
        foreach ($this->configuration['indexers'] as $className => $configuration) {
            if (!$configuration['enable']) {
                continue;
            }
            if (count($onlyIndexerTypes)) {
                $indexerType = (new \ReflectionClass($className))->getShortName();
                if (!in_array($indexerType, $onlyIndexerTypes, true)) {
                    continue;
                }
            }

            $this->searchBackend->setSource($className);
            $this->runIndexer($className, $scopes, $configuration, $progressHandler, $this->searchBackend);
        }
    }

    public function clearCommand(): void
    {
        foreach ($this->configuration['indexers'] as $className => $configuration) {
            $this->output->outputLine($className);
            $this->searchBackend->setSource($className);
            $this->searchBackend->deleteObsoleteIndexEntries([]);
        }
    }

    private function runIndexer(
        string $className,
        array $scopes,
        array $configuration,
        ProgressHandler $progressHandler,
        SearchBackend $backend
    ): void {
        $indexer = $this->getIndexer($className, $configuration);

        $indexer->index($scopes, $backend, $progressHandler);
        $backend->afterIndexing();
        $indexer->postIndex($backend);

        $this->outputLine('');
    }


    private function getIndexer(string $className, array $configuration): IndexerInterface
    {
        $indexer = new $className;
        if (!($indexer instanceof IndexerInterface)) {
            throw new InvalidObjectException(
                'Indexers must implement ' . IndexerInterface::class . ' - ' . $className . ' does not.'
            );
        }
        if (array_key_exists('configuration', $configuration)) {
            $indexer->setConfiguration($configuration['configuration']);
        }
        return $indexer;
    }

    /** @return string[] */
    private function getScopes(): array
    {
        $scopes = [];
        foreach ($this->siteRepository->findAll() as $site) {
            /** @var Site $site */
            $scopes[] = '/sites/' . $site->getNodeName();
        }
        return $scopes;
    }
}
