<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Ferienpass\CoreBundle\Entity\ActivityNotification;
use Ferienpass\CoreBundle\Entity\User;

/**
 * @extends ServiceEntityRepository<ActivityNotification>
 */
class ActivityNotificationRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, ActivityNotification::class);
    }

    public function countUnread(User $recipient): int
    {
        return (int) $this->createQueryBuilder('n')
            ->select('COUNT(n.uuid)')
            ->andWhere('n.recipient = :recipient')
            ->andWhere('n.readAt IS NULL')
            ->setParameter('recipient', $recipient)
            ->getQuery()
            ->getSingleScalarResult()
        ;
    }

    /**
     * @return array<int, ActivityNotification>
     */
    public function findRecent(User $recipient, int $limit = 12): array
    {
        return $this->createQueryBuilder('n')
            ->andWhere('n.recipient = :recipient')
            ->setParameter('recipient', $recipient)
            ->orderBy('n.createdAt', 'DESC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult()
        ;
    }

    /**
     * The unread notifications of a recipient among the given conversation items — used to
     * mark which comments in a thread still need the viewer's attention.
     *
     * @param array<int, \Symfony\Component\Uid\Uuid> $commentUuids
     *
     * @return array<int, ActivityNotification>
     */
    public function findUnreadForItems(User $recipient, array $commentUuids): array
    {
        if ([] === $commentUuids) {
            return [];
        }

        return $this->createQueryBuilder('n')
            ->andWhere('n.recipient = :recipient')
            ->andWhere('n.readAt IS NULL')
            ->andWhere('IDENTITY(n.conversationItem) IN (:comments)')
            ->setParameter('recipient', $recipient)
            ->setParameter('comments', $commentUuids)
            ->getQuery()
            ->getResult()
        ;
    }

    /**
     * Every notification for the given conversation items (any recipient), used
     * to aggregate per-comment "delivered / read" status for the activity feed.
     *
     * @param array<int, \Symfony\Component\Uid\Uuid> $commentUuids
     *
     * @return array<int, ActivityNotification>
     */
    public function findForComments(array $commentUuids): array
    {
        if ([] === $commentUuids) {
            return [];
        }

        return $this->createQueryBuilder('n')
            ->andWhere('IDENTITY(n.conversationItem) IN (:comments)')
            ->setParameter('comments', $commentUuids)
            ->getQuery()
            ->getResult()
        ;
    }

    /**
     * Base58 uuids of the subjects (offer/participant/host/…) the recipient has
     * unread activity on — used to highlight rows in the admin lists. The set is
     * small (a person's own unread items), so resolving subjects in PHP is fine.
     *
     * @return array<int, string>
     */
    public function findUnreadSubjectUuids(User $recipient): array
    {
        $notifications = $this->createQueryBuilder('n')
            ->join('n.conversationItem', 'c')
            ->addSelect('c')
            ->andWhere('n.recipient = :recipient')
            ->andWhere('n.readAt IS NULL')
            ->setParameter('recipient', $recipient)
            ->getQuery()
            ->getResult()
        ;

        $uuids = [];
        foreach ($notifications as $notification) {
            $subject = $notification->getConversationItem()->getSubject();
            if (method_exists($subject, 'getUuid')) {
                $uuids[$subject->getUuid()->toBase58()] = true;
            }
        }

        return array_keys($uuids);
    }

    public function markAllRead(User $recipient): int
    {
        return $this->createQueryBuilder('n')
            ->update()
            ->set('n.readAt', ':now')
            ->andWhere('n.recipient = :recipient')
            ->andWhere('n.readAt IS NULL')
            ->setParameter('now', new \DateTimeImmutable())
            ->setParameter('recipient', $recipient)
            ->getQuery()
            ->execute()
        ;
    }

    /**
     * Mark every unread notification for the given conversation items read. Used when a
     * colleague reads (or marks read) the whole thread of a subject.
     *
     * @param array<int, \Symfony\Component\Uid\Uuid> $commentUuids
     */
    public function markReadForItems(User $recipient, array $commentUuids): int
    {
        if ([] === $commentUuids) {
            return 0;
        }

        return $this->createQueryBuilder('n')
            ->update()
            ->set('n.readAt', ':now')
            ->andWhere('n.recipient = :recipient')
            ->andWhere('n.readAt IS NULL')
            ->andWhere('IDENTITY(n.conversationItem) IN (:comments)')
            ->setParameter('now', new \DateTimeImmutable())
            ->setParameter('recipient', $recipient)
            ->setParameter('comments', $commentUuids)
            ->getQuery()
            ->execute()
        ;
    }
}
