<?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\CustomFieldsBundle\CustomFields;

use Chill\CustomFieldsBundle\Entity\CustomField;
use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Twig\Environment;

/**
 * Create a custom date number.
 *
 * The date may have a min and max value.
 *
 * The date is stored as an unix timestamp.
 */
class CustomFieldDate extends AbstractCustomField
{
    final public const DATE_FORMAT = \DateTime::RFC3339;

    final public const FORMAT = 'format';

    final public const MAX = 'max';

    /**
     * key for the minimal value of the field.
     */
    final public const MIN = 'min';

    public function __construct(
        private readonly Environment $templating,
        private readonly TranslatableStringHelper $translatableStringHelper,
    ) {}

    public function buildForm(FormBuilderInterface $builder, CustomField $customField)
    {
        $fieldOptions = $this->prepareFieldOptions($customField);

        $builder->add(
            $builder
                ->create(
                    $customField->getSlug(),
                    ChillDateType::class,
                    $fieldOptions
                )
                ->addModelTransformer(
                    new CustomFieldDataTransformer($this, $customField)
                )
        );
    }

    public function buildOptionsForm(FormBuilderInterface $builder)
    {
        $validatorFunction = static function ($value, ExecutionContextInterface $context) {
            try {
                $date = new \DateTime((string) $value);
            } catch (\Exception) {
                $context->buildViolation('The expression "%expression%" is invalid', [
                    '%expression%' => $value,
                ])
                    ->addViolation();
            }
        };

        return $builder
            ->add(self::MIN, TextType::class, [
                'label' => 'Greater or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd)',
                'required' => false,
                'constraints' => [new Callback($validatorFunction)],
            ])
            ->add(self::MAX, TextType::class, [
                'label' => 'Lesser or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd)',
                'required' => false,
                'constraints' => [new Callback($validatorFunction)],
            ])
            ->add(self::FORMAT, ChoiceType::class, [
                'label' => 'Format',
                'choices' => [
                    'medium' => 'medium',
                    'long' => 'long',
                    'short' => 'short',
                ],
            ]);
    }

    public function deserialize($serialized, CustomField $customField)
    {
        if (empty($serialized)) {
            return null;
        }

        return \DateTime::createFromFormat(self::DATE_FORMAT, $serialized);
    }

    public function getName()
    {
        return 'Date field';
    }

    public function render($value, CustomField $customField, $documentType = 'html')
    {
        switch ($documentType) {
            case 'csv':
                $date = $this->deserialize($value, $customField);

                if (null === $date) {
                    return null;
                }

                return $date->format('Y-m-d');

            default:
                $template = '@ChillCustomFields/CustomFieldsRendering/date.'
                        .$documentType.'.twig';

                return $this->templating
                    ->render($template, [
                        'value' => $this->deserialize($value, $customField),
                        'format' => $customField->getOptions()[self::FORMAT],
                    ]);
        }
    }

    public function serialize($date, CustomField $customField)
    {
        if (null === $date) {
            return null;
        }

        return $date->format(self::DATE_FORMAT);
    }

    /**
     * prepare the options'form field.
     *
     * @return mixed[]
     */
    private function prepareFieldOptions(CustomField $customField)
    {
        $options = $customField->getOptions();

        /**
         * @var mixed[] the formField options
         */
        $fieldOptions = [];

        // add required
        $fieldOptions['required'] = false;

        // add label
        $fieldOptions['label'] = $this->translatableStringHelper->localize($customField->getName());

        // add constraints if required
        if (null !== $options[self::MIN]) {
            $fieldOptions['constraints'][] = new Callback(
                static function ($timestamp, ExecutionContextInterface $context) use ($options) {
                    if (null === $timestamp) {
                        return;
                    }

                    $value = \DateTime::createFromFormat(self::DATE_FORMAT, $timestamp);
                    $after = new \DateTime($options[self::MIN]);

                    if ($value < $after) {
                        $context
                            ->buildViolation('This date must be after or equal to %date%', [
                                '%date%' => $after->format('d-m-Y'),
                            ])
                            ->addViolation();
                    }
                }
            );
        }

        if (null !== $options[self::MAX]) {
            $fieldOptions['constraints'][] = new Callback(
                static function ($timestamp, ExecutionContextInterface $context) use ($options) {
                    if (null === $timestamp) {
                        return;
                    }

                    $value = \DateTime::createFromFormat(self::DATE_FORMAT, $timestamp);
                    $before = new \DateTime($options[self::MAX]);

                    if ($value > $before) {
                        $context
                            ->buildViolation('This date must be before or equal to %date%', [
                                '%date%' => $before->format('d-m-Y'),
                            ])
                            ->addViolation();
                    }
                }
            );
        }

        return $fieldOptions;
    }
}
