<?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\ConsentManager\ConsentManager;
use Ferienpass\CoreBundle\ConsentManager\ConsentState;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Enum\AttendanceTransition;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

/**
 * The "Anmeldung ansehen" drawer: a per-attendance view with the participant &
 * contact, offer, activity trail, and — the reason it is a live component — the
 * inline editing of the offer's consents and extra questions. Consent decisions
 * an admin makes here are recorded in the name of the guardian (their own name is
 * stamped as the signee), so the UI keeps that behind an explicit edit toggle plus
 * a legal reminder.
 */
#[AsLiveComponent(route: 'live_component_admin')]
class AttendanceShow extends AbstractController
{
    use DefaultActionTrait;

    #[LiveProp]
    public Attendance $attendance;

    #[LiveProp(writable: true)]
    public bool $editingConsents = false;

    #[LiveProp(writable: true)]
    public bool $editingQuestions = false;

    /** @var array<string, string> consent-form id → 'grant'|'revoke' while editing */
    #[LiveProp(writable: true)]
    public array $consentDecisions = [];

    /** @var array<string, string> question uuid → answer while editing */
    #[LiveProp(writable: true)]
    public array $answers = [];

    public function __construct(
        private readonly ConsentManager $consentManager,
        private readonly EntityManagerInterface $entityManager,
        private readonly WorkflowInterface $attendanceStateMachine,
    ) {
    }

    /**
     * All consents the offer/season requires for this participant, signed and open
     * alike — the drawer reviews the full set, not only the outstanding ones.
     *
     * @return ConsentState[]
     */
    #[ExposeInTemplate]
    public function consentStates(): array
    {
        $participant = $this->attendance->getParticipant();
        if (!$participant instanceof ParticipantInterface) {
            return [];
        }

        return array_values($this->consentManager->forOfferAndParticipants($this->attendance->getOffer(), [$participant]));
    }

    /**
     * The offer's extra questions paired with this attendance's stored answers, in
     * a fully-populated shape (legacy rows read from the DB can miss keys such as
     * `required`, which the entity only fills in on save).
     *
     * @return list<array{uuid: string, question: string, type: string, required: bool, answer: string}>
     */
    #[ExposeInTemplate]
    public function questions(): array
    {
        $questions = [];
        foreach ($this->attendance->getOffer()->getApplicationExtra() ?? [] as $q) {
            if ('' === trim((string) ($q['question'] ?? ''))) {
                continue;
            }

            $uuid = (string) ($q['uuid'] ?? '');
            $questions[] = [
                'uuid' => $uuid,
                'question' => (string) $q['question'],
                'type' => \in_array($q['type'] ?? null, ['yn', 'int', 'txt'], true) ? $q['type'] : 'txt',
                'required' => (bool) ($q['required'] ?? false),
                'answer' => (string) ($this->attendance->getExtra($uuid) ?? ''),
            ];
        }

        return $questions;
    }

    #[ExposeInTemplate]
    public function canWithdraw(): bool
    {
        return $this->attendanceStateMachine->can($this->attendance, AttendanceTransition::WITHDRAW);
    }

    #[LiveAction]
    public function editConsents(): void
    {
        $this->editingConsents = true;
        $this->consentDecisions = [];
        foreach ($this->consentStates() as $state) {
            $id = (string) $state->getConsentForm()?->getId();
            $this->consentDecisions[$id] = $this->currentDecision($state) ?? 'open';
        }
    }

    #[LiveAction]
    public function cancelConsents(): void
    {
        $this->editingConsents = false;
        $this->consentDecisions = [];
    }

    #[LiveAction]
    public function saveConsents(): void
    {
        $participant = $this->attendance->getParticipant();
        if (!$participant instanceof ParticipantInterface) {
            $this->cancelConsents();

            return;
        }

        $account = $participant->getUser();
        $adminUser = $this->getUser();
        $signedBy = $adminUser instanceof User ? $adminUser->getName() : '';

        $changed = false;
        foreach ($this->consentStates() as $state) {
            $form = $state->getConsentForm();
            if (null === $form) {
                continue;
            }

            $decision = $this->consentDecisions[(string) $form->getId()] ?? 'open';
            if (!\in_array($decision, ['grant', 'revoke'], true) || $decision === $this->currentDecision($state)) {
                continue;
            }

            // Same subject binding as the frontend/create flow (per-participant vs
            // account-level, and everything on the participant when account-less).
            $subject = (null === $account || $form->isPerParticipant()) ? $participant : null;
            $signState = 'grant' === $decision
                ? $this->consentManager->sign($form, $account, $subject)
                : $this->consentManager->decline($form, $account, $subject);

            // Stamp the admin as the signee. sign()/decline() already flushed the
            // decision; this needs the extra flush below. The stored name is the
            // visible audit of who set it and when (Consent.signedBy/createdAt).
            if ('' !== $signedBy) {
                $signState->getSignature()?->setSignedBy($signedBy);
            }
            $changed = true;
        }

        if ($changed) {
            $this->entityManager->flush();
        }

        $this->cancelConsents();
    }

    #[LiveAction]
    public function editQuestions(): void
    {
        $this->editingQuestions = true;
        $this->answers = [];
        foreach ($this->questions() as $q) {
            $this->answers[$q['uuid']] = $q['answer'];
        }
    }

    #[LiveAction]
    public function cancelQuestions(): void
    {
        $this->editingQuestions = false;
        $this->answers = [];
    }

    #[LiveAction]
    public function saveQuestions(): void
    {
        $extra = [];
        foreach ($this->questions() as $q) {
            $value = trim((string) ($this->answers[$q['uuid']] ?? $q['answer']));
            if ('' !== $value) {
                $extra[$q['uuid']] = $value;
            }
        }

        $this->attendance->setExtra($extra);
        $this->entityManager->flush();

        $this->cancelQuestions();
    }

    #[LiveAction]
    public function withdraw(): void
    {
        if (!$this->canWithdraw()) {
            return;
        }

        $this->attendanceStateMachine->apply($this->attendance, AttendanceTransition::WITHDRAW, ['notify' => false]);
        $this->entityManager->flush();
    }

    /**
     * The consent's current decision as one of 'grant'|'revoke'|null (open),
     * so a save only touches the ones the admin actually flipped.
     */
    private function currentDecision(ConsentState $state): ?string
    {
        if ($state->isSigned()) {
            return 'grant';
        }

        if ($state->isRejected() || $state->isRevoked()) {
            return 'revoke';
        }

        return null;
    }
}
