<?php declare(strict_types=1);
namespace Newland\Toubiz\Events\Neos\Filter\Items;

/*
 * This file is part of the "toubiz-events-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Doctrine\ORM\QueryBuilder;
use Neos\Flow\Configuration\Exception\InvalidConfigurationException;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Controller\ControllerContext;
use Neos\Flow\Persistence\Generic\Query as NeosQuery;
use Neos\Flow\Utility\Now;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Service\ContentContext;
use Neos\Neos\Service\LinkingService;
use Newland\NeosFiltering\Items\PaginationInput;
use Newland\NeosFiltering\Items\Root;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Events\Neos\Filter\EventClientFilterService;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventDateRepository;

class EventDateRoot extends Root
{
    /**
     * @var EventDateRepository
     * @Flow\Inject()
     */
    protected $eventDateRepository;

    /**
     * @var EventClientFilterService
     * @Flow\Inject()
     */
    protected $clientFilterService;

    /**
     * @var array
     * @Flow\InjectConfiguration(path="linkTargets.lists")
     */
    protected $listLinkTargets;

    /**
     * @var LinkingService
     * @Flow\Inject()
     */
    protected $linkingService;

    /**
     * @var Now
     * @Flow\Inject()
     */
    protected $now;

    public function initializeForFilteredLists(ActionRequest $request): self
    {
        if ($this->pluginNode !== null) {
            $this->addFormAttributesForAjax(
                sprintf(
                    '/toubiz-events/filtered-list?node=%s&format=ajax',
                    $this->pluginNode->getContextPath()
                )
            );
        }
        $this->fillStateFromRootRequestArguments($request)
            ->setAction(
                [
                    '@action' => 'show',
                    '@controller' => 'FilteredLists',
                    '@package' => 'Newland.Toubiz.Events.Neos',
                ],
                $request
            );

        return $this;
    }

    protected function applyOrder(QueryBuilder $query): QueryBuilder
    {
        if ($this->orderBy === null) {
            return $query;
        }

        $order = $this->sanitizeOrder();

        if ($this->sortNullAtTheEnd) {
            $query->orderBy('entity.hasDate', NeosQuery::ORDER_DESCENDING)
                ->addOrderBy($this->orderBy, $order);
        } else {
            $query->orderBy($this->orderBy, $order);
        }

        return $query;
    }

    public function getEventQuery(array $additionalWhere = [], bool $withHidden = false): QueryBuilder
    {
        $queryGetter = function () use ($additionalWhere, $withHidden) {
            $query = $this->eventDateRepository->createQueryBuilder('entity', null, $withHidden);
            $query = $this->applyBaseConditions($query, $additionalWhere);
            return $this->applyToQuery($query);
        };

        if ($this->pluginNode) {
            $language = $this->pluginNode->getDimensions()['language'][0] ?? null;
            return $this->eventDateRepository->withLanguage($language, $queryGetter);
        }

        return $queryGetter();
    }

    public function countResults(array $additionalWhere = []): int
    {
        $this->enablePagination(false);
        $count = $this->getResultCount($this->getEventQuery($additionalWhere));
        $this->enablePagination(true);
        return $count;
    }

    public function getListUri(ControllerContext $context, array $overrides): string
    {
        $targetUri = $this->listLinkTargets['default'] ?? '';
        if ($targetUri === '') {
            return $targetUri;
        }

        if (strpos($targetUri, 'node://') === 0) {
            $node = $this->linkingService->convertUriToObject($targetUri, $this->pluginNode);
            if ($node === null) {
                throw new InvalidConfigurationException(sprintf('No node matching "%s" found', $targetUri));
            }
            $arguments = $this->buildQueryStringFromOverrides($overrides);

            return $this->linkingService->createNodeUri(
                $context,
                $node,
                $this->pluginNode,
                null,
                false,
                $arguments
            );
        }

        return $this->removePlaceholders($targetUri);
    }

    public function setPageSize(int $pageSize): EventDateRoot
    {
        foreach ($this->flattenChildren($this) as $child) {
            if ($child instanceof PaginationInput) {
                $config = $child->getConfiguration();
                $config['pageSize'] = $pageSize;
                $child->setConfiguration($config);
            }
        }
        return $this;
    }

    public function getPagination(): ?PaginationInput
    {
        foreach ($this->flattenChildren($this) as $child) {
            if ($child instanceof PaginationInput) {
                return $child;
            }
        }
        return null;
    }


    protected function removePlaceholders(string $url): string
    {
        $uri = \Safe\preg_replace('/\{.*\}(\/)?/', '', $url);

        if (is_array($uri)) {
            $uri[0] = $uri[0] ?? '';
            $uri = $uri[0];
        }
        return $uri;
    }

    private function applyBaseConditions(QueryBuilder $query, array $additionalWhere = []): QueryBuilder
    {
        $site = $this->getSite();
        if ($site !== null) {
            // The client is a property of the related event.
            $this->clientFilterService->addClientWhereClause($query, $site, 'event');
        }

        // Full-day events may start at midnight
        $midnight = $this->now->setTime(0, 0, 0);
        $query->andWhere(
            $query->expr()->orX(
                $query->expr()->eq('entity.beginsAt', ':midnight'),
                $query->expr()->gte('entity.endsAt', ':now'),
                $query->expr()->isNull('entity.beginsAt')
            )
        )->setParameters(
            [
                'midnight' => $midnight->format('Y-m-d H:i:s'),
                'now' => $this->now->format('Y-m-d H:i:s'),
            ]
        );

        foreach ($additionalWhere as $where) {
            $query->andWhere($where);
        }

        return $query;
    }

    private function getSite(): ?Site
    {
        if ($this->pluginNode === null) {
            return null;
        }

        $context = $this->pluginNode->getContext();
        if (!($context instanceof ContentContext)) {
            return null;
        }

        return $context->getCurrentSite();
    }
}
