<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\EventBundle\Search;

use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Repository\EventRepository;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;

/**
 * Search within Events.
 *
 * available terms :
 * - name: to search within the name
 * - date: search the event at a the given date
 * - date-from: search the event after the given date
 * - date-to : search the event before the given date
 *
 * Default terms search within the name, but the term "name" has precedence. This
 * means that if the search string is `@event xyz name:"abc"`, the searched
 * string will be "abc" and not xyz
 */
class EventSearch extends AbstractSearch
{
    final public const NAME = 'event_regular';

    public function __construct(
        private readonly Security $security,
        private readonly EventRepository $eventRepository,
        private readonly AuthorizationHelper $authorizationHelper,
        private readonly \Twig\Environment $templating,
        private readonly PaginatorFactory $paginatorFactory,
    ) {}

    public function getOrder(): int
    {
        return 3000;
    }

    public function isActiveByDefault()
    {
        return true;
    }

    public function renderResult(
        array $terms,
        $start = 0,
        $limit = 50,
        array $options = [],
        $format = 'html',
    ) {
        $total = $this->count($terms);
        $paginator = $this->paginatorFactory->create($total);

        if ('html' === $format) {
            return $this->templating->render(
                '@ChillEvent/Event/list.html.twig',
                [
                    'events' => $this->search($terms, $start, $limit, $options),
                    'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']),
                    'total' => $total,
                    'start' => $start,
                    'preview' => $options[SearchInterface::SEARCH_PREVIEW_OPTION],
                    'paginator' => $paginator,
                    'search_name' => self::NAME,
                ]
            );
        }
        // format is "json"
        $results = [];
        $search = $this->search($terms, $start, $limit, $options);

        foreach ($search as $item) {
            $results[] = [
                'id' => $item->getId(),
                'text' => $item->getDate()->format('d/m/Y, H:i').' → '.
                          // $item->getType()->getName()['fr'] . ':  ' .    // display the type of event
                          $item->getName(),
            ];
        }

        return [
            'results' => $results,
            'more' => $paginator->hasNextPage(),
        ];
    }

    public function supports($domain, $format)
    {
        return 'event' === $domain || 'events' === $domain;
    }

    protected function composeQuery(QueryBuilder &$qb, $terms)
    {
        // add security clauses
        $reachableCenters = $this->authorizationHelper->getReachableCenters(
            $this->security->getUser(),
            'CHILL_EVENT_SEE'
        );

        if (0 === \count($reachableCenters)) {
            // add a clause to block all events
            $where = $qb->expr()->isNull('e.center');
            $qb->andWhere($where);
        } else {
            $n = 0;
            $orWhere = $qb->expr()->orX();

            foreach ($reachableCenters as $center) {
                ++$n;
                $circles = $this->authorizationHelper->getReachableScopes(
                    $this->security->getUser(),
                    'CHILL_EVENT_SEE',
                    $center
                );
                $where = $qb->expr()->andX(
                    $qb->expr()->eq('e.center', ':center_'.$n),
                    $qb->expr()->in('e.circle', ':circle_'.$n)
                );
                $qb->setParameter('center_'.$n, $center);
                $qb->setParameter('circle_'.$n, $circles);
                $orWhere->add($where);
            }

            $qb->andWhere($orWhere);
        }

        if (
            (isset($terms['name']) || isset($terms['_default']))
            && (!empty($terms['name']) || !empty($terms['_default']))
        ) {
            // the form with name:"xyz" has precedence
            $name = $terms['name'] ?? $terms['_default'];

            $where = $qb->expr()->like('UNACCENT(LOWER(e.name))', ':name');
            $qb->setParameter('name', '%'.$name.'%');
            $qb->andWhere($where);
        }

        if (isset($terms['date'])) {
            $date = $this->parseDate($terms['date']);

            $where = $qb->expr()->eq('e.date', ':date');
            $qb->setParameter('date', $date);
            $qb->andWhere($where);
        }

        if (isset($terms['date-from'])) {
            $date = $this->parseDate($terms['date-from']);

            $where = $qb->expr()->gte('e.date', ':datefrom');
            $qb->setParameter('datefrom', $date);
            $qb->andWhere($where);
        }

        if (isset($terms['date-to'])) {
            $date = $this->parseDate($terms['date-to']);

            $where = $qb->expr()->lte('e.date', ':dateto');
            $qb->setParameter('dateto', $date);
            $qb->andWhere($where);
        }

        return $qb;
    }

    protected function count(array $terms)
    {
        $qb = $this->eventRepository->createQueryBuilder('e');
        $qb->select('COUNT(e)');
        $this->composeQuery($qb, $terms);

        return $qb->getQuery()->getSingleScalarResult();
    }

    protected function getAvailableTerms()
    {
        return ['date-from', 'date-to', 'name', 'date'];
    }

    protected function search(array $terms, $start, $limit, $options)
    {
        $qb = $this->eventRepository->createQueryBuilder('e');
        $qb->select('e');
        $this->composeQuery($qb, $terms)
            ->setMaxResults($limit)
            ->setFirstResult($start)
            ->orderBy('e.date', 'DESC');

        return $qb->getQuery()->getResult();
    }
}
