<?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\Offer;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\AdminBundle\Section\SectionComponentInterface;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
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\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

/**
 * The "Kontaktmöglichkeiten" (contact) offer-edit section: a two-pane editor
 * for the offer's published contact persons ({@see OfferInterface::getContact()})
 * with a live preview of the public "Kontakt & Auskunft" block on the offer
 * page.
 *
 * Replaces the legacy `Contact` LiveCollection form field. The working copy of
 * the contacts lives in the {@see $contacts} LiveProp (not persisted until
 * save), so the preview reacts to every keystroke without touching the
 * database. On save the blank rows are dropped and the list is written back
 * via {@see OfferInterface::setContact()}.
 *
 * These contacts are PUBLISHED on the offer page, so the preview mirrors the
 * public rendering (name + phone + email, with mailto/tel links).
 *
 * The whole section is locked for variant offers (`contact` is a variant
 * field) and for users without the 'edit' voter.
 */
#[AsLiveComponent(template: '@FerienpassAdmin/components/Offer/Contact.html.twig', route: 'live_component_admin')]
class Contact extends AbstractController implements SectionComponentInterface
{
    use ComponentToolsTrait;
    use DefaultActionTrait;

    #[LiveProp]
    public OfferInterface $offer;

    /**
     * Working copy of the contact persons, each `{uuid, name, phone, email}`.
     * Writable so all three fields ride along via `data-model`; add / remove go
     * through LiveActions. The `uuid` keeps the editor card stable across
     * add/remove and is dropped on save (the persisted shape is
     * `{name, phone, email}`, as read by the frontend).
     *
     * @var list<array{uuid: string, name: string, phone: string, email: string}>
     */
    #[LiveProp(writable: true)]
    public array $contacts = [];

    #[LiveProp]
    public array $sections = [];

    #[LiveProp]
    public ?string $section = null;

    #[LiveProp]
    public bool $isReadonly = false;

    public bool $success = false;
    public string $message = '';

    public function __construct(private readonly EntityManagerInterface $entityManager)
    {
    }

    public static function getSectionName(): string
    {
        return 'contact';
    }

    public static function getSectionScope(): string
    {
        return 'offer';
    }

    public function mount(OfferInterface $offer): void
    {
        // A prop consumed by mount() is NOT auto-assigned to its matching
        // property by TwigComponent, so set $this->offer explicitly.
        $this->offer = $offer;

        $this->contacts = array_map(static fn (array $c): array => [
            'uuid' => Uuid::v7()->toRfc4122(),
            'name' => (string) ($c['name'] ?? ''),
            'phone' => (string) ($c['phone'] ?? ''),
            'email' => (string) ($c['email'] ?? ''),
        ], $offer->getContact() ?? []);
    }

    /**
     * Offer-level read-only: variant offers (contact is a variant field) and
     * users without the 'edit' voter. Gates the editor + the save button.
     */
    public function isOfferReadonly(): bool
    {
        return $this->isReadonly || $this->offer->isVariant();
    }

    /**
     * Preview-strip data for the offer-page mock (title + first date + age range).
     *
     * @return array{title: string, date: ?\DateTimeInterface, minAge: ?int, maxAge: ?int}
     */
    public function previewStrip(): array
    {
        $firstDate = $this->offer->getDates()->first() ?: null;

        return [
            'title' => (string) $this->offer->getName(),
            'date' => $firstDate?->getBegin(),
            'minAge' => $this->offer->getMinAge(),
            'maxAge' => $this->offer->getMaxAge(),
        ];
    }

    #[LiveAction]
    public function addContact(): void
    {
        $this->denyAccessUnlessEditable();

        $this->contacts[] = ['uuid' => Uuid::v7()->toRfc4122(), 'name' => '', 'phone' => '', 'email' => ''];
        $this->contacts = array_values($this->contacts);
    }

    #[LiveAction]
    public function removeContact(#[LiveArg] int $index): void
    {
        $this->denyAccessUnlessEditable();

        unset($this->contacts[$index]);
        $this->contacts = array_values($this->contacts);
    }

    #[LiveAction]
    public function save(#[LiveArg] ?string $nextSection = null, #[LiveArg] ?string $redirect = null): ?Response
    {
        $this->denyAccessUnlessEditable();

        // Drop blank rows (no name, phone or email) so empty editors don't
        // persist — mirrors the legacy collection's delete_empty behaviour —
        // and strip the editor-only uuid down to the persisted shape.
        $this->offer->setContact(array_values(array_map(
            static fn (array $c): array => [
                'name' => (string) ($c['name'] ?? ''),
                'phone' => (string) ($c['phone'] ?? ''),
                'email' => (string) ($c['email'] ?? ''),
            ],
            array_filter(
                $this->contacts,
                static fn (array $c): bool => '' !== trim((string) ($c['name'] ?? ''))
                    || '' !== trim((string) ($c['phone'] ?? ''))
                    || '' !== trim((string) ($c['email'] ?? '')),
            ),
        )));

        $this->entityManager->flush();
        $this->entityManager->refresh($this->offer);

        // Reflect the cleaned list back into the editor.
        $this->mount($this->offer);

        $this->success = true;
        $this->dispatchBrowserEvent('live:saved', ['success' => true]);

        if ($nextSection || $redirect) {
            $this->addFlash('success', ['type' => 'success', 'title' => 'Daten gespeichert', 'message' => 'Die Änderungen wurden erfolgreich übernommen.']);
        } else {
            $this->message = 'Die Änderungen wurden erfolgreich übernommen.';
        }

        if ($nextSection) {
            return $this->redirectToRoute('admin_offers_edit', ['uuid' => $this->offer->getUuid()->toBase58(), 'section' => $nextSection]);
        }
        if ($redirect) {
            return $this->redirectToRoute($redirect, ['uuid' => $this->offer->getUuid()->toBase58()]);
        }

        return null;
    }

    /**
     * Offer-level write guard (save + add/remove): variant offers are read-only
     * and the user needs the 'edit' voter. Re-checks the offer rather than
     * trusting the {@see $isReadonly} LiveProp, which the client could flip.
     */
    private function denyAccessUnlessEditable(): void
    {
        if ($this->offer->isVariant()) {
            throw new BadRequestHttpException('Cannot modify the contact persons on a variant offer.');
        }

        $this->denyAccessUnlessGranted('edit', $this->offer);
    }
}
