<?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\AdminBundle\Components;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Comment\ConversationItemUrlGenerator;
use Ferienpass\CoreBundle\Entity\ActivityNotification;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\ConversationItem;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Repository\ActivityNotificationRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Uid\Uuid;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

/**
 * The header notification bell: unread badge plus a dropdown of the colleague's
 * recent activity notifications (new comments and email replies that concern
 * them). Rendered through an ESI fragment so it escapes the year-long page cache.
 */
#[AsLiveComponent(template: '@FerienpassAdmin/components/NotificationBell.html.twig', route: 'live_component_admin')]
class NotificationBell extends AbstractController
{
    use DefaultActionTrait;

    public function __construct(
        private readonly ActivityNotificationRepository $notifications,
        private readonly ConversationItemUrlGenerator $urls,
    ) {
    }

    #[ExposeInTemplate('unread_count')]
    public function unreadCount(): int
    {
        $user = $this->getUser();

        return $user instanceof User ? $this->notifications->countUnread($user) : 0;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    #[ExposeInTemplate('rows')]
    public function rows(): array
    {
        $user = $this->getUser();
        if (!$user instanceof User) {
            return [];
        }

        return array_map(fn (ActivityNotification $n): array => $this->row($n), $this->notifications->findRecent($user, 12));
    }

    #[LiveAction]
    public function markAllRead(): void
    {
        $user = $this->getUser();
        if ($user instanceof User) {
            $this->notifications->markAllRead($user);
        }
    }

    #[LiveAction]
    public function open(#[LiveArg] string $uuid, EntityManagerInterface $em): ?Response
    {
        $user = $this->getUser();
        if (!$user instanceof User) {
            return null;
        }

        try {
            $notification = $this->notifications->find(Uuid::fromBase58($uuid));
        } catch (\InvalidArgumentException) {
            return null;
        }

        if (!$notification instanceof ActivityNotification || $notification->getRecipient()->getId() !== $user->getId()) {
            return null;
        }

        $notification->markRead();
        $em->flush();

        $url = $this->urlFor($notification->getConversationItem());

        return null !== $url ? $this->redirect($url) : null;
    }

    /**
     * @return array<string, mixed>
     */
    private function row(ActivityNotification $n): array
    {
        $comment = $n->getConversationItem();
        $author = $comment->getAuthor();

        return [
            'uuid' => $n->getUuid()->toBase58(),
            'unread' => !$n->isRead(),
            'actor' => $comment->getAuthorLabel() ?? 'Unbekannt',
            'initials' => $author
                ? mb_strtoupper(mb_substr((string) $author->getFirstname(), 0, 1).mb_substr((string) $author->getLastname(), 0, 1))
                : mb_strtoupper(mb_substr($comment->getAuthorLabel() ?? '@', 0, 1)),
            'role' => $author ? ($author->isAdmin() ? 'admin' : ($author->isHost() ? 'host' : ($author->isMember() ? 'account' : 'admin'))) : null,
            'verb' => match ($n->getReason()) {
                ActivityNotification::REASON_MENTION => 'erwähnte dich',
                ActivityNotification::REASON_REPLY => 'antwortete',
                default => 'hinterließ einen Kommentar',
            },
            'quote' => $this->excerpt($comment->getBody()),
            'entity' => $this->subjectLabel($comment),
            'via' => $comment->getChannel(),
            'createdAt' => $comment->getCreatedAt(),
            'url' => $this->urlFor($comment),
        ];
    }

    private function excerpt(string $body, int $length = 90): string
    {
        $body = trim(preg_replace('/\s+/', ' ', $body) ?? $body);

        return mb_strlen($body) > $length ? mb_substr($body, 0, $length).'…' : $body;
    }

    private function subjectLabel(ConversationItem $comment): string
    {
        $subject = $comment->getSubject();

        return match (true) {
            $subject instanceof Attendance => (string) $subject->getOffer()->getName(),
            $subject instanceof OfferInterface => (string) $subject->getName(),
            default => (string) (method_exists($subject, 'getName') ? $subject->getName() : $subject),
        };
    }

    private function urlFor(ConversationItem $comment): ?string
    {
        return $this->urls->adminUrl($comment);
    }
}
