<?php

namespace Lc\SovBundle\Form\Common;

use EasyCorp\Bundle\EasyAdminBundle\Form\DataTransformer\StringToFileTransformer;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Model\FileUploadState;
use Lc\SovBundle\Model\File\FileInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\File;
use Lc\SovBundle\Doctrine\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\String\Slugger\AsciiSlugger;
use Symfony\Component\Uid\Ulid;
use Symfony\Component\Uid\Uuid;

class FileUploadType extends AbstractType implements DataMapperInterface, DataTransformerInterface
{
    protected string $projectDir;
    protected EntityManager $entityManager;

    public function __construct(EntityManager $entityManager, ParameterBagInterface $parameterBag)
    {
        $this->projectDir = $parameterBag->get('kernel.project_dir');
        $this->entityManager = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $uploadDir = $options['upload_dir'];
        $uploadFilename = $options['upload_filename'];
        $uploadValidate = $options['upload_validate'];
        $allowAdd = $options['allow_add'];
        unset($options['upload_dir'], $options['upload_new'], $options['upload_delete'], $options['upload_filename'], $options['upload_validate'], $options['download_path'], $options['allow_add'], $options['allow_delete'], $options['compound']);

        $builder->add('path', FileType::class, $options);
        $builder->add('legend', TextType::class, array(
                'attr' => [
                        "placeholder" => 'Légende'
                ],
                'label' => false
        ));
        $builder->add('delete', CheckboxType::class, ['required' => false, 'mapped' => false]);

        $builder->setDataMapper($this);
        $builder->setAttribute('state', new FileUploadState($allowAdd));
        $builder->addModelTransformer(new StringToFileTransformer($uploadDir, $uploadFilename, $uploadValidate, $options['multiple']));

        /*
        $builder->add('path', FileType::class, array(
                'block_prefix' => 'file_manager_image',
                'label' => false
        ));

        $builder->add('legend', TextType::class, array(
                'block_prefix' => 'file_upload_legend',
                'attr' => array(
                        "placeholder" => 'Légende'
                ),
                'label' => false
        ));

        $builder->add('position', HiddenType::class, array(
                'block_prefix' => 'file_upload_position',
                'empty_data' => 0,
                'required' => true,
                'attr' => array(
                        'class' => 'field-position'
                ),
                'label' => false
        ));
        */
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $uploadNew = static function (UploadedFile $file, string $uploadDir, string $fileName) {
            $file->move($uploadDir, $fileName);
        };

        $uploadDelete = static function (File $file) {
            unlink($file->getPathname());
        };

        $uploadFilename = static function (UploadedFile $file): string {
            return $file->getClientOriginalName();
        };

        $uploadValidate = static function (string $filename): string {
            if (!file_exists($filename)) {
                return $filename;
            }

            $index = 1;
            $pathInfo = pathinfo($filename);
            while (file_exists($filename = sprintf('%s/%s_%d.%s', $pathInfo['dirname'], $pathInfo['filename'], $index, $pathInfo['extension']))) {
                ++$index;
            }

            return $filename;
        };

        $downloadPath = function (Options $options) {
            return mb_substr($options['upload_dir'], mb_strlen($this->projectDir.'/public/'));
        };

        $allowAdd = static function (Options $options) {
            return $options['multiple'];
        };

        $dataClass = static function (Options $options) {
            return $options['multiple'] ? null : File::class;
        };

        $emptyData = static function (Options $options) {
            return $options['multiple'] ? [] : null;
        };

        $resolver->setDefaults([
                                       'upload_dir' => $this->projectDir.'/public/uploads/files/',
                                       'upload_new' => $uploadNew,
                                       'upload_delete' => $uploadDelete,
                                       'upload_filename' => $uploadFilename,
                                       'upload_validate' => $uploadValidate,
                                       'download_path' => $downloadPath,
                                       'allow_add' => $allowAdd,
                                       'allow_delete' => true,
                                       //'data_class' => $dataClass,
                                       'data_class' => $this->entityManager->getEntityName(FileInterface::class),
                                       'empty_data' => $emptyData,
                                       'multiple' => false,
                                       'required' => false,
                                       'error_bubbling' => false,
                                       'allow_file_upload' => true,
                               ]);

        $resolver->setAllowedTypes('upload_dir', 'string');
        $resolver->setAllowedTypes('upload_new', 'callable');
        $resolver->setAllowedTypes('upload_delete', 'callable');
        $resolver->setAllowedTypes('upload_filename', ['string', 'callable']);
        $resolver->setAllowedTypes('upload_validate', 'callable');
        $resolver->setAllowedTypes('download_path', ['null', 'string']);
        $resolver->setAllowedTypes('allow_add', 'bool');
        $resolver->setAllowedTypes('allow_delete', 'bool');

        $resolver->setNormalizer('upload_dir', function (Options $options, string $value): string {
            if (\DIRECTORY_SEPARATOR !== mb_substr($value, -1)) {
                $value .= \DIRECTORY_SEPARATOR;
            }

            if (0 !== mb_strpos($value, $this->projectDir)) {
                $value = $this->projectDir.'/'.$value;
            }

            if ('' !== $value && (!is_dir($value) || !is_writable($value))) {
                throw new InvalidArgumentException(sprintf('Invalid upload directory "%s" it does not exist or is not writable.', $value));
            }

            return $value;
        });
        $resolver->setNormalizer('upload_filename', static function (Options $options, $fileNamePatternOrCallable) {
            if (\is_callable($fileNamePatternOrCallable)) {
                return $fileNamePatternOrCallable;
            }

            return static function (UploadedFile $file) use ($fileNamePatternOrCallable) {
                return strtr($fileNamePatternOrCallable, [
                        '[contenthash]' => sha1_file($file->getRealPath()),
                        '[day]' => date('d'),
                        '[extension]' => $file->guessClientExtension(),
                        '[month]' => date('m'),
                        '[name]' => pathinfo($file->getClientOriginalName(), \PATHINFO_FILENAME),
                        '[randomhash]' => bin2hex(random_bytes(20)),
                        '[slug]' => (new AsciiSlugger())
                                ->slug(pathinfo($file->getClientOriginalName(), \PATHINFO_FILENAME))
                                ->lower()
                                ->toString(),
                        '[timestamp]' => time(),
                        '[uuid]' => Uuid::v4()->toRfc4122(),
                        '[ulid]' => new Ulid(),
                        '[year]' => date('Y'),
                ]);
            };
        });
        $resolver->setNormalizer('allow_add', static function (Options $options, string $value): bool {
            if ($value && !$options['multiple']) {
                throw new InvalidArgumentException('Setting "allow_add" option to "true" when "multiple" option is "false" is not supported.');
            }

            return $value;
        });
    }

    /**
     * {@inheritdoc}
     */
    public function mapDataToForms($currentFiles, $forms): void
    {
        /** @var FormInterface $fileForm */
        $fileForm = current(iterator_to_array($forms));
        $fileForm->setData($currentFiles);
    }

    /**
     * {@inheritdoc}
     */
    public function mapFormsToData($forms, &$currentFiles): void
    {
        /** @var FormInterface[] $children */
        $children = iterator_to_array($forms);
        $uploadedFiles = $children['path']->getData();

        /** @var FileUploadState $state */
        $state = $children['path']->getParent()->getConfig()->getAttribute('state');
        $state->setCurrentFiles($currentFiles);
        $state->setUploadedFiles($uploadedFiles);
        $state->setDelete($children['delete']->getData());

        if (!$state->isModified()) {
            return;
        }

        if ($state->isAddAllowed() && !$state->isDelete()) {
            $currentFiles = array_merge($currentFiles, $uploadedFiles);
        } else {
            $currentFiles = $uploadedFiles;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function transform($data)
    {
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function reverseTransform($data)
    {
        return null === $data ? '' : $data;
    }
}
