<?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\ActivityBundle\Form;

use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Form\Type\PickActivityReasonType;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Form\CollectionStoredObjectType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class ActivityType extends AbstractType
{
    protected User $user;

    public function __construct(
        TokenStorageInterface $tokenStorage,
        protected AuthorizationHelper $authorizationHelper,
        protected ObjectManager $om,
        protected TranslatableStringHelper $translatableStringHelper,
        protected array $timeChoices,
        protected SocialIssueRender $socialIssueRender,
        protected SocialActionRender $socialActionRender,
    ) {
        if (!$tokenStorage->getToken()->getUser() instanceof User) {
            throw new \RuntimeException('you should have a valid user');
        }

        $this->user = $tokenStorage->getToken()->getUser();
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        // handle times choices
        $timeChoices = [];

        foreach ($this->timeChoices as $e) {
            $timeChoices[$e['label']] = $e['seconds'];
        }

        $durationTimeTransformer = new DateTimeToTimestampTransformer('GMT', 'GMT');
        $durationTimeOptions = [
            'choices' => $timeChoices,
            'placeholder' => 'Choose the duration',
        ];

        /** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */
        $activityType = $options['activityType'];

        if (null !== $options['data']->getPerson()) {
            $builder->add('scope', ScopePickerType::class, [
                'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
                'center' => $options['center'],
                'required' => true,
            ]);
        }

        /** @var AccompanyingPeriod|null $accompanyingPeriod */
        $accompanyingPeriod = null;

        if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) {
            $accompanyingPeriod = $options['accompanyingPeriod'];
        }

        if ($activityType->isVisible('socialIssues') && null !== $accompanyingPeriod) {
            $builder->add('socialIssues', HiddenType::class, [
                'required' => 2 === $activityType->getSocialIssuesVisible(),
            ]);
            $builder->get('socialIssues')
                ->addModelTransformer(new CallbackTransformer(
                    static function (iterable $socialIssuesAsIterable): string {
                        $socialIssueIds = [];

                        foreach ($socialIssuesAsIterable as $value) {
                            $socialIssueIds[] = $value->getId();
                        }

                        return implode(',', $socialIssueIds);
                    },
                    function (?string $socialIssuesAsString): array {
                        if (null === $socialIssuesAsString) {
                            return [];
                        }

                        return array_map(
                            fn (string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]),
                            explode(',', $socialIssuesAsString)
                        );
                    }
                ));
        }

        if ($activityType->isVisible('socialActions') && null !== $accompanyingPeriod) {
            $builder->add('socialActions', HiddenType::class, [
                'required' => 2 === $activityType->getSocialActionsVisible(),
            ]);
            $builder->get('socialActions')
                ->addModelTransformer(new CallbackTransformer(
                    static function (iterable $socialActionsAsIterable): string {
                        $socialActionIds = [];

                        foreach ($socialActionsAsIterable as $value) {
                            $socialActionIds[] = $value->getId();
                        }

                        return implode(',', $socialActionIds);
                    },
                    function (?string $socialActionsAsString): array {
                        if (null === $socialActionsAsString) {
                            return [];
                        }

                        return array_map(
                            fn (string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]),
                            explode(',', $socialActionsAsString)
                        );
                    }
                ));
        }

        if ($activityType->isVisible('date')) {
            $builder->add('date', ChillDateType::class, [
                'label' => $activityType->getLabel('date'),
                'required' => $activityType->isRequired('date'),
            ]);
        }

        if ($activityType->isVisible('durationTime')) {
            $durationTimeOptions['label'] = $activityType->getLabel('durationTime');
            $durationTimeOptions['required'] = $activityType->isRequired('durationTime');

            $builder->add('durationTime', ChoiceType::class, $durationTimeOptions);
        }

        if ($activityType->isVisible('travelTime')) {
            $durationTimeOptions['label'] = $activityType->getLabel('travelTime');
            $durationTimeOptions['required'] = $activityType->isRequired('travelTime');

            $builder->add('travelTime', ChoiceType::class, $durationTimeOptions);
        }

        if ($activityType->isVisible('attendee')) {
            $builder->add('attendee', EntityType::class, [
                'label' => $activityType->getLabel('attendee'),
                'required' => $activityType->isRequired('attendee'),
                'placeholder' => false,
                'expanded' => true,
                'class' => ActivityPresence::class,
                'choice_label' => fn (ActivityPresence $activityPresence) => $this->translatableStringHelper->localize($activityPresence->getName()),
                'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('a')
                    ->where('a.active = true'),
            ]);
        }

        if ($activityType->isVisible('user')) {
            $builder->add('user', PickUserDynamicType::class, [
                'label' => $activityType->getLabel('user'),
                'required' => $activityType->isRequired('user'),
                'multiple' => false,
            ]);
        }

        if ($activityType->isVisible('reasons')) {
            $builder->add('reasons', PickActivityReasonType::class, [
                'label' => $activityType->getLabel('reasons'),
                'required' => $activityType->isRequired('reasons'),
                'multiple' => true,
            ]);
        }

        if ($activityType->isVisible('comment')) {
            $builder->add('comment', CommentType::class, [
                'label' => empty($activityType->getLabel('comment'))
                    ? 'activity.comment' : $activityType->getLabel('comment'),
                'required' => $activityType->isRequired('comment'),
            ]);
        }

        if ($activityType->isVisible('privateComment')) {
            $builder->add('privateComment', PrivateCommentType::class, [
                'label' => '' === $activityType->getLabel('privateComment') ? 'private comment' : $activityType->getPrivateCommentLabel(),
                'required' => false,
            ]);
        }

        if ($activityType->isVisible('persons')) {
            $builder->add('persons', HiddenType::class);
            $builder->get('persons')
                ->addModelTransformer(new CallbackTransformer(
                    static function (iterable $personsAsIterable): string {
                        $personIds = [];

                        foreach ($personsAsIterable as $value) {
                            $personIds[] = $value->getId();
                        }

                        return implode(',', $personIds);
                    },
                    function (?string $personsAsString): array {
                        if (null === $personsAsString) {
                            return [];
                        }

                        return array_map(
                            fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
                            explode(',', $personsAsString)
                        );
                    }
                ));
        }

        if ($activityType->isVisible('thirdParties')) {
            $builder->add('thirdParties', HiddenType::class);
            $builder->get('thirdParties')
                ->addModelTransformer(new CallbackTransformer(
                    static function (iterable $thirdpartyAsIterable): string {
                        $thirdpartyIds = [];

                        foreach ($thirdpartyAsIterable as $value) {
                            $thirdpartyIds[] = $value->getId();
                        }

                        return implode(',', $thirdpartyIds);
                    },
                    function (?string $thirdpartyAsString): array {
                        if (null === $thirdpartyAsString) {
                            return [];
                        }

                        return array_map(
                            fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
                            explode(',', $thirdpartyAsString)
                        );
                    }
                ));
        }

        if ($activityType->isVisible('documents')) {
            $builder->add('documents', CollectionStoredObjectType::class, [
                'label' => $activityType->getLabel('documents'),
                'required' => $activityType->isRequired('documents'),
            ]);
        }

        if ($activityType->isVisible('users')) {
            $builder->add('users', HiddenType::class);
            $builder->get('users')
                ->addModelTransformer(new CallbackTransformer(
                    static function (iterable $usersAsIterable): string {
                        $userIds = [];

                        foreach ($usersAsIterable as $value) {
                            $userIds[] = $value->getId();
                        }

                        return implode(',', $userIds);
                    },
                    function (?string $usersAsString): array {
                        if (null === $usersAsString) {
                            return [];
                        }

                        return array_map(
                            fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
                            explode(',', $usersAsString)
                        );
                    }
                ));
        }

        if ($activityType->isVisible('location')) {
            $builder->add('location', HiddenType::class, [
                'required' => 2 === $activityType->getLocationVisible(),
            ])
                ->get('location')
                ->addModelTransformer(new CallbackTransformer(
                    static function (?Location $location): string {
                        if (null === $location) {
                            return '';
                        }

                        return (string) $location->getId();
                    },
                    fn (?string $id): ?Location => $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id])
                ));
        }

        if ($activityType->isVisible('emergency')) {
            $builder->add('emergency', CheckboxType::class, [
                'label' => $activityType->getLabel('emergency'),
                'required' => $activityType->isRequired('emergency'),
            ]);
        }

        if ($activityType->isVisible('sentReceived')) {
            $builder->add('sentReceived', ChoiceType::class, [
                'label' => $activityType->getLabel('sentReceived'),
                'required' => $activityType->isRequired('sentReceived'),
                'choices' => [
                    'Received' => Activity::SENTRECEIVED_RECEIVED,
                    'Sent' => Activity::SENTRECEIVED_SENT,
                ],
            ]);
        }

        foreach (['durationTime', 'travelTime'] as $fieldName) {
            if (!$activityType->isVisible($fieldName)) {
                continue;
            }

            $builder->get($fieldName)
                ->addModelTransformer($durationTimeTransformer);

            $builder->get($fieldName)
                ->addEventListener(FormEvents::PRE_SET_DATA, static function (FormEvent $formEvent) use (
                    $timeChoices,
                    $builder,
                    $durationTimeTransformer,
                    $durationTimeOptions,
                    $fieldName
                ) {
                    // set the timezone to GMT, and fix the difference between current and GMT
                    // the datetimetransformer will then handle timezone as GMT
                    $timezoneUTC = new \DateTimeZone('GMT');
                    /** @var \DateTime $data */
                    $data = $formEvent->getData() ?? \DateTime::createFromFormat('U', '300');
                    $seconds = $data->getTimezone()->getOffset($data);
                    $data->setTimeZone($timezoneUTC);
                    $data->add(new \DateInterval('PT'.$seconds.'S'));

                    // test if the timestamp is in the choices.
                    // If not, recreate the field with the new timestamp
                    if (!\in_array($data->getTimestamp(), $timeChoices, true)) {
                        // the data are not in the possible values. add them
                        $timeChoices[$data->format('H:i')] = $data->getTimestamp();
                        $form = $builder->create($fieldName, ChoiceType::class, array_merge(
                            $durationTimeOptions,
                            [
                                'choices' => $timeChoices,
                                'auto_initialize' => false,
                            ]
                        ));
                        $form->addModelTransformer($durationTimeTransformer);
                        $formEvent->getForm()->getParent()->add($form->getForm());
                    }
                });
        }
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Activity::class,
        ]);

        $resolver
            ->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod'])
            ->setAllowedTypes('center', ['null', Center::class, 'array'])
            ->setAllowedTypes('role', ['string'])
            ->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class)
            ->setAllowedTypes('accompanyingPeriod', [AccompanyingPeriod::class, 'null']);
    }

    public function getBlockPrefix(): string
    {
        return 'chill_activitybundle_activity';
    }
}
