<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\AdminBundle\Twig\Components;

use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\Pagination\PaginationInterface;
use Serenity\CoreBundle\Search\SearchInterface;
use Serenity\CoreBundle\Shared\Doctrine\Repository\CrudRepositoryInterface;
use Serenity\CoreBundle\Shared\Feature\SortableInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Http\Attribute\IsGranted;
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\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsLiveComponent(template: '@components/SerenityAdmin/CrudForm.html.twig')]
#[IsGranted('ROLE_MODERATOR')]
class CrudForm
{
    use ComponentToolsTrait;
    use ComponentWithFormTrait;
    use DefaultActionTrait;

    /**
     * @var array<string, mixed>
     */
    #[LiveProp(writable: false, useSerializerForHydration: true)]
    public array $searchBy = [];

    #[LiveProp(writable: true)]
    public int $page = 1;
    /**
     * @var class-string<SearchInterface>|null
     */
    #[LiveProp(writable: false)]
    public ?string $searchClass = null;

    /**
     * @var class-string
     */
    #[LiveProp(writable: false)]
    public string $parentClass;
    /**
     * @var class-string
     */
    #[LiveProp(writable: false)]
    public string $childClass;

    /**
     * @var class-string<FormTypeInterface>
     */
    #[LiveProp(writable: false)]
    public string $searchType;

    #[LiveProp(writable: false)]
    public ?string $modalButton = null;

    #[LiveProp(writable: false)]
    public ?string $modalTitle = null;

    #[LiveProp(writable: false)]
    public ?string $modalIcon = null;

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

    #[LiveProp(writable: false, hydrateWith: 'hydrateObjectDoctrine', dehydrateWith: 'dehydrateObjectDoctrine')]
    public mixed $modalComponentData;

    #[LiveProp(writable: true, hydrateWith: 'hydrateSearch', dehydrateWith: 'dehydrateSearch', url: false)]
    public ?SearchInterface $search = null;

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

    /**
     * @param array<string> $data
     *
     * @return array<string>
     */
    #[PreMount]
    public function preMount(array $data): array
    {
        $resolver = new OptionsResolver();
        $resolver->setIgnoreUndefined(true);
        $resolver->setDefined('parentClass');
        $resolver->setDefined('childClass');
        $resolver->setDefined('searchType');
        $resolver->setDefined('searchClass');
        $resolver->setDefined('searchBy');
        $resolver->setDefined('modalComponentData');
        $resolver->setAllowedTypes('parentClass', 'string');
        $resolver->setAllowedTypes('searchBy', 'array');
        $resolver->setAllowedTypes('childClass', 'string');
        $resolver->setAllowedTypes('searchType', ['string', 'null']);
        $resolver->setAllowedTypes('searchClass', ['string', 'null']);

        return $resolver->resolve($data) + $data;
    }

    /**
     * @return PaginationInterface<mixed>
     */
    #[ExposeInTemplate]
    public function getItems(): PaginationInterface
    {
        /* @var CrudRepositoryInterface $repository */
        $repository = $this->entityManager->getRepository($this->childClass);
        \assert($repository instanceof CrudRepositoryInterface);
        $queryBuilder = $repository->searchQuery($this->search, $this->searchBy);

        return $repository->findAllPagination($this->page, queryBuilder: $queryBuilder);
    }

    #[LiveAction]
    public function filter(): void
    {
        $this->submitForm();
        if ($this->form instanceof FormInterface) {
            $this->search = $this->form->getData();
        }
    }

    #[LiveAction]
    public function sortable(#[LiveArg] Uuid $id, #[LiveArg] int $newPosition): void
    {
        $repository = $this->entityManager->getRepository($this->childClass);
        $object = $repository->find($id);
        if (!$object instanceof SortableInterface) {
            return;
        }
        $object->setPosition($newPosition);
        $this->entityManager->persist($object);
        $this->entityManager->flush();
    }

    #[LiveAction]
    public function remove(#[LiveArg] Uuid $id): void
    {
        $repository = $this->entityManager->getRepository($this->childClass);
        $object = $repository->find($id);
        if (!$object) {
            return;
        }
        $this->entityManager->remove($object);
        $this->entityManager->flush();
    }

    protected function instantiateForm(): FormInterface
    {
        return $this->formFactory->create($this->searchType, $this->search);
    }

    /**
     * @return array<string, mixed>
     */
    public function dehydrateSearch(?SearchInterface $search = null): array
    {
        if (null === $search) {
            return [];
        }

        return $search->toArray();
    }

    public function hydrateSearch(mixed $data): ?SearchInterface
    {
        if (!\is_array($data)) {
            return null;
        }

        /** @var SearchInterface $reflection */
        $reflection = new ($this->searchClass);

        return $reflection::fromArray($data);
    }

    public function dehydrateObjectDoctrine(mixed $data): ?Uuid
    {
        // Si c’est déjà un Uuid, on le retourne tel quel
        if ($data instanceof Uuid) {
            return $data;
        }

        // Si c’est un objet Doctrine avec une méthode getId()
        if (\is_object($data) && method_exists($data, 'getId')) {
            $id = $data->getId();

            return $id instanceof Uuid ? $id : null;
        }

        // Si c’est un objet avec une propriété publique id
        if (\is_object($data) && property_exists($data, 'id')) {
            $id = $data->id;

            return $id instanceof Uuid ? $id : null;
        }

        // Rien trouvé
        return null;
    }

    public function hydrateObjectDoctrine(mixed $data): mixed
    {
        $repository = $this->entityManager->getRepository($this->parentClass);

        return $repository->find($data);
    }
}
