<?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\MainBundle\CRUD\Controller;

use Chill\MainBundle\CRUD\Form\CRUDDeleteEntityForm;
use Chill\MainBundle\CRUD\Resolver\Resolver;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * The CRUDController class is an abstract class that provides basic CRUD (Create, Read, Update, Delete) functionality for entities in a Symfony application. It extends the Abstract
 *Controller class.
 *
 * For the class \Chill\MainBundle\CRUD\Controller\CRUDController, the dependency injection system does not work as in
 * the usual way, where dependencies are provided via the constructor method. Instead, it utilizes the capabilities
 * provided by the \Symfony\Contracts\Service\ServiceSubscriberInterface.
 *
 * The \Symfony\Contracts\Service\ServiceSubscriberInterface is an interface that is used by objects needing explicit
 * service references. It means this class has a direct dependency on the Symfony service container, allowing it to
 * request specific services it needs for execution.
 *
 * By implementing the ServiceSubscriberInterface, this class effectively defines a method called getSubscribedServices,
 * which returns an array mapping service IDs to their classes or interfaces. Symfony's service container uses this
 * method to preconfigure the services required, optimizing the dependency injection process.
 *
 * This model can be used to keep the class itself concise by preventing the need to define a constructor and inject
 * services through method calls. This approach also allows for more flexible service usage, as services can be fetched
 * as and when required, rather than all services being loaded whether they are used or not. This makes for a more
 * efficient and maintainable solution especially when dealing with larger services.
 *
 * For more details about how this works, you can refer to Symfony's Documentation
 * on Service Subscribers & Service Locators: https://symfony.com/doc/current/service_container/service_subscribers_locators.html
 */
class CRUDController extends AbstractController
{
    /**
     * The crud configuration.
     *
     * This configuration si defined by `chill_main['crud']`.
     */
    protected array $crudConfig;

    public function CRUD(?string $parameter): Response
    {
        return new Response($parameter);
    }

    public function delete(Request $request, $id): Response
    {
        return $this->deleteAction('delete', $request, $id);
    }

    /**
     * BAse method for edit action.
     *
     * IMplemented by the method formEditAction, with action as 'edit'
     */
    public function edit(Request $request, mixed $id): Response
    {
        return $this->formEditAction('edit', $request, $id);
    }

    /**
     * Get the context for the serialization.
     */
    public function getContextForSerialization(string $action, Request $request, mixed $entity, string $_format): array
    {
        return [];
    }

    public static function getSubscribedServices(): array
    {
        return \array_merge(
            parent::getSubscribedServices(),
            [
                'chill_main.paginator_factory' => PaginatorFactory::class,
                'translator' => TranslatorInterface::class,
                AuthorizationHelper::class => AuthorizationHelper::class,
                EventDispatcherInterface::class => EventDispatcherInterface::class,
                Resolver::class => Resolver::class,
                SerializerInterface::class => SerializerInterface::class,
                FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
            ]
        );
    }

    /**
     * Base method called by index action.
     *
     * @return type
     */
    public function index(Request $request)
    {
        return $this->indexEntityAction('index', $request);
    }

    /**
     * Base method for new action.
     *
     * Implemented by the method formNewAction, with action as 'new'
     */
    public function new(Request $request): Response
    {
        return $this->formCreateAction('new', $request);
    }

    public function setCrudConfig(array $config): void
    {
        $this->crudConfig = $config;
    }

    /**
     * Base method for the view action.
     *
     * Implemented by the method viewAction, with action as 'view'
     */
    public function view(Request $request, mixed $id): Response
    {
        return $this->viewAction('view', $request, $id);
    }

    /**
     * build a default role name, using the crud resolver.
     *
     * This method should not be overriden. Override `getRoleFor` instead.
     *
     * @param string $action
     *
     * @return string
     */
    protected function buildDefaultRole($action)
    {
        return $this->getCrudResolver()->buildDefaultRole(
            $this->getCrudName(),
            $action
        );
    }

    protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
    {
        return null;
    }

    /**
     * Build the base query for listing all entities, normally use in a listing
     * page.
     *
     * This base query does not contains any `WHERE` or `SELECT` clauses. Those
     * are added by other methods, like `queryEntities` and `countQueries`.
     *
     * @return QueryBuilder
     */
    protected function buildQueryEntities(string $action, Request $request)
    {
        $query = $this->getManagerRegistry()
            ->getManager()
            ->createQueryBuilder()
            ->select('e')
            ->from($this->getEntityClass(), 'e');

        $this->customizeQuery($action, $request, $query);

        return $query;
    }

    /**
     * check the acl. Called by every action.
     *
     * By default, check the role given by `getRoleFor` for the value given in
     * $entity.
     *
     * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
     * if not accessible.
     *
     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
     */
    protected function checkACL(string $action, mixed $entity)
    {
        $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity);
    }

    /**
     * Count the number of entities.
     */
    protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
    {
        return $this->buildQueryEntities($action, $request)
            ->select('COUNT(e)')
            ->getQuery()
            ->getSingleScalarResult();
    }

    /**
     * Create an entity.
     */
    protected function createEntity(string $action, Request $request): object
    {
        $type = $this->getEntityClass();

        return new $type();
    }

    /**
     * Create a form.
     *
     * use the method `getFormClassFor`
     *
     * A hook is available: `customizeForm` allow you to customize the form
     * if needed.
     *
     * It is preferable to override customizeForm instead of overriding
     * this method.
     */
    protected function createFormFor(string $action, mixed $entity, ?string $formClass = null, array $formOptions = []): FormInterface
    {
        $formClass ??= $this->getFormClassFor($action);

        $form = $this->createForm($formClass, $entity, $formOptions);

        $this->customizeForm($action, $form);

        return $form;
    }

    /**
     * Customize the form created by createFormFor.
     */
    protected function customizeForm(string $action, FormInterface $form) {}

    protected function customizeQuery(string $action, Request $request, $query): void {}

    /**
     * @param null $formClass
     */
    protected function deleteAction(string $action, Request $request, $id, $formClass = null): Response
    {
        $this->onPreDelete($action, $request);

        $entity = $this->getEntity($action, $id, $request);

        $postFetch = $this->onPostFetchEntity($action, $request, $entity);

        if ($postFetch instanceof Response) {
            return $postFetch;
        }

        if (null === $entity) {
            throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
        }

        $response = $this->checkACL($action, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $form = $this->createFormFor($action, $entity, $formClass);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->onFormValid($action, $entity, $form, $request);
            $em = $this->getManagerRegistry()->getManager();

            $this->onPreRemove($action, $entity, $form, $request);
            $this->removeEntity($action, $entity, $form, $request);
            $this->onPostRemove($action, $entity, $form, $request);

            $this->onPreFlush($action, $entity, $form, $request);
            $em->flush();
            $this->onPostFlush($action, $entity, $form, $request);

            $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));

            $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);

            if ($result instanceof Response) {
                return $result;
            }

            return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]);
        }

        if ($form->isSubmitted()) {
            $this->addFlash('error', $this->generateFormErrorMessage($action, $form));
        }

        $defaultTemplateParameters = [
            'form' => $form->createView(),
            'entity' => $entity,
            'crud_name' => $this->getCrudName(),
        ];

        return $this->render(
            $this->getTemplateFor($action, $entity, $request),
            $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
        );
    }

    /**
     * Duplicate an entity.
     */
    protected function duplicateEntity(string $action, Request $request)
    {
        $id = $request->query->get('duplicate_id', 0);
        $originalEntity = $this->getEntity($action, $id, $request);

        $this->getManagerRegistry()->getManager()
            ->detach($originalEntity);

        return $originalEntity;
    }

    /**
     * The new (or creation) action.
     *
     * Some steps may be overriden during this process of rendering:
     *
     * This method:
     *
     * 1.  Create or duplicate an entity:
     *
     *     If the `duplicate` parameter is present, the entity is duplicated
     *     using the `duplicate` method.
     *
     *     If not, the entity is created using the `create` method.
     * 3.  check ACL using `checkACL` ;
     * 4.  launch `onPostCheckACL`. If the result is an instance of Response,
     *     this response is returned ;
     * 5.  generate a form using `createFormFor`, and handle request on this form;
     *
     *     If the form is valid, the entity is stored and flushed, and a redirection
     *     is returned.
     *
     *     In this case, those hooks are available:
     *
     *     * onFormValid
     *     * onPreFlush
     *     * onPostFlush
     *     * onBeforeRedirectAfterSubmission. If this method return an instance of
     *         Response, this response is returned.
     *
     * 5.  generate default template parameters:
     *
     *     * `entity`: the fetched entity ;
     *     * `crud_name`: the crud name ;
     *     * `form`: the formview instance.
     *
     * 6.  Launch rendering, the parameter is fetch using `getTemplateFor`
     *     The parameters may be personnalized using `generateTemplateParameter`.
     *
     * @param type $formClass
     */
    protected function formCreateAction(string $action, Request $request, $formClass = null): Response
    {
        if ($request->query->has('duplicate')) {
            $entity = $this->duplicateEntity($action, $request);
        } else {
            $entity = $this->createEntity($action, $request);
        }

        $response = $this->checkACL($action, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $form = $this->createFormFor($action, $entity, $formClass);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->onFormValid($action, $entity, $form, $request);
            $em = $this->getManagerRegistry()->getManager();

            $this->onPrePersist($action, $entity, $form, $request);
            $em->persist($entity);
            $this->onPostPersist($action, $entity, $form, $request);

            $this->onPreFlush($action, $entity, $form, $request);
            $em->flush();
            $this->onPostFlush($action, $entity, $form, $request);

            $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));

            $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);

            if ($result instanceof Response) {
                return $result;
            }

            return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]);
        }

        if ($form->isSubmitted()) {
            $this->addFlash('error', $this->generateFormErrorMessage($action, $form));
        }

        $defaultTemplateParameters = [
            'form' => $form->createView(),
            'entity' => $entity,
            'crud_name' => $this->getCrudName(),
        ];

        return $this->render(
            $this->getTemplateFor($action, $entity, $request),
            $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
        );
    }

    /**
     * The edit action.
     *
     * Some steps may be overriden during this process of rendering:
     *
     * This method:
     *
     * 1.  fetch the entity, using `getEntity`
     * 2.  launch `onPostFetchEntity`. If postfetch is an instance of Response,
     *     this response is returned.
     * 2.  throw an HttpNotFoundException if entity is null
     * 3.  check ACL using `checkACL` ;
     * 4.  launch `onPostCheckACL`. If the result is an instance of Response,
     *     this response is returned ;
     * 5.  generate a form using `createFormFor`, and handle request on this form;
     *
     *     If the form is valid, the entity is stored and flushed, and a redirection
     *     is returned.
     *
     *     In this case, those hooks are available:
     *
     *     * onFormValid
     *     * onPreFlush
     *     * onPostFlush
     *     * onBeforeRedirectAfterSubmission. If this method return an instance of
     *         Response, this response is returned.
     *
     * 5.  generate default template parameters:
     *
     *     * `entity`: the fetched entity ;
     *     * `crud_name`: the crud name ;
     *     * `form`: the formview instance.
     *
     * 6.  Launch rendering, the parameter is fetch using `getTemplateFor`
     *     The parameters may be personnalized using `generateTemplateParameter`.
     *
     * @param class-string $formClass
     *
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    protected function formEditAction(string $action, Request $request, mixed $id, ?string $formClass = null, array $formOptions = []): Response
    {
        $entity = $this->getEntity($action, $id, $request);

        if (null === $entity) {
            throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
        }

        $response = $this->checkACL($action, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $form = $this->createFormFor($action, $entity, $formClass, $formOptions);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->onFormValid($action, $entity, $form, $request);
            $em = $this->getManagerRegistry()->getManager();

            $this->onPreFlush($action, $entity, $form, $request);
            $em->flush();
            $this->onPostFlush($action, $entity, $form, $request);

            $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));

            $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);

            if ($result instanceof Response) {
                return $result;
            }

            return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index');
        }

        if ($form->isSubmitted()) {
            $this->addFlash('error', $this->generateFormErrorMessage($action, $form));
        }

        $defaultTemplateParameters = [
            'form' => $form->createView(),
            'entity' => $entity,
            'crud_name' => $this->getCrudName(),
        ];

        return $this->render(
            $this->getTemplateFor($action, $entity, $request),
            $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
        );
    }

    /**
     * Generate a message which explains an error about the form.
     *
     * Used in form actions
     */
    protected function generateFormErrorMessage(string $action, FormInterface $form): string
    {
        $msg = 'This form contains errors';

        return $this->getTranslator()->trans($msg);
    }

    /**
     * Generate a success message when a form could be flushed successfully.
     *
     * @param string $action
     */
    protected function generateFormSuccessMessage($action, mixed $entity): string
    {
        $msg = match ($action) {
            'edit' => 'crud.edit.success',
            'new' => 'crud.new.success',
            'delete' => 'crud.delete.success',
            default => 'crud.default.success',
        };

        return $this->getTranslator()->trans($msg);
    }

    /**
     * Customize template parameters.
     *
     * @return array
     */
    protected function generateTemplateParameter(
        string $action,
        mixed $entity,
        Request $request,
        array $defaultTemplateParameters = [],
    ) {
        return $defaultTemplateParameters;
    }

    /**
     *  Include services.
     */
    protected function getActionConfig(string $action)
    {
        return $this->crudConfig['actions'][$action];
    }

    protected function getAuthorizationHelper(): AuthorizationHelper
    {
        return $this->container->get(AuthorizationHelper::class);
    }

    /**
     * @return string the crud name
     */
    protected function getCrudName(): string
    {
        return $this->crudConfig['name'];
    }

    protected function getCrudResolver(): Resolver
    {
        return $this->get(Resolver::class);
    }

    protected function getDefaultDeleteFormClass($action)
    {
        return CRUDDeleteEntityForm::class;
    }

    /**
     * get the instance of the entity with the given id.
     *
     * @param string $id
     */
    protected function getEntity(mixed $action, $id, Request $request): ?object
    {
        return $this->getManagerRegistry()
            ->getRepository($this->getEntityClass())
            ->find($id);
    }

    /**
     * @return string the complete fqdn of the class
     */
    protected function getEntityClass(): string
    {
        return $this->crudConfig['class'];
    }

    protected function getEventDispatcher(): EventDispatcherInterface
    {
        return $this->get(EventDispatcherInterface::class);
    }

    protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
    {
        return $this->get(FilterOrderHelperFactoryInterface::class);
    }

    /**
     * get the default form class from config.
     *
     * @param string $action
     *
     * @return string the FQDN of the form class
     */
    protected function getFormClassFor($action)
    {
        if ('delete' === $action) {
            return $this->crudConfig[$action]['form_class']
            ?? $this->getDefaultDeleteFormClass($action);
        }

        return $this->crudConfig[$action]['form_class']
            ?? $this->crudConfig['form_class'];
    }

    /**
     * @todo (check how to do this with dependency injection and make changes...)
     */
    protected function getPaginatorFactory(): PaginatorFactory
    {
        return $this->container->get('chill_main.paginator_factory');
    }

    protected function getManagerRegistry(): ManagerRegistry
    {
        return $this->container->get('doctrine');
    }

    /**
     * Get the result of the query.
     */
    protected function getQueryResult(
        string $action,
        Request $request,
        int $totalItems,
        PaginatorInterface $paginator,
        ?FilterOrderHelper $filterOrder = null,
    ) {
        $query = $this->queryEntities($action, $request, $paginator, $filterOrder);

        return $query->getQuery()->getResult();
    }

    /**
     * @return \Chill\MainBundle\Entity\Center[]
     */
    protected function getReachableCenters(string $role, ?Scope $scope = null)
    {
        return $this->getAuthorizationHelper()
            ->getReachableCenters($this->getUser(), $role, $scope);
    }

    /**
     * get the role given from the config.
     *
     * @param string $action
     *
     * @return string
     */
    protected function getRoleFor($action)
    {
        if (\array_key_exists('role', $this->getActionConfig($action))) {
            return $this->getActionConfig($action)['role'];
        }

        return $this->buildDefaultRole($action);
    }

    /**
     * Get the template for the current crud.
     *
     * This template may be defined in configuration. If any template are
     * defined, return the default template for the actions new, edit, index,
     * and view.
     *
     * @param string $action
     *
     * @return string the path to the template
     *
     * @throws \LogicException if no template are available
     */
    protected function getTemplateFor($action, mixed $entity, Request $request)
    {
        if ($this->hasCustomTemplate($action, $entity, $request)) {
            return $this->getActionConfig($action)['template'];
        }

        return match ($action) {
            'new' => '@ChillMain/CRUD/new.html.twig',
            'edit' => '@ChillMain/CRUD/edit.html.twig',
            'index' => '@ChillMain/CRUD/index.html.twig',
            'view' => '@ChillMain/CRUD/view.html.twig',
            'delete' => '@ChillMain/CRUD/delete.html.twig',
            default => throw new \LogicException("the view for action {$action} is not ".'defined. You should override '.__METHOD__.' to add this action'),
        };
    }

    protected function getTranslator(): TranslatorInterface
    {
        return $this->container->get('translator');
    }

    protected function hasCustomTemplate($action, $entity, Request $request): bool
    {
        return !empty($this->getActionConfig($action)['template']);
    }

    /**
     * Build an index page.
     *
     * Some steps may be overriden during this process of rendering.
     *
     * This method:
     *
     * 1.  Launch `onPreIndex`
     * 2.  check acl. If it does return a response instance, return it
     * 3.  launch `onPostCheckACL`. If it does return a response instance, return it
     * 4.  count the items, using `countEntities`
     * 5.  build a paginator element from the the number of entities ;
     * 6.  Launch `onPreIndexQuery`. If it does return a response instance, return it
     * 7.  fetch the results, using `getQueryResult`
     *
     *     Internally, this build a query, using `queryEntities`
     *
     * 8.  Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
     * 9.  create default parameters:
     *
     *     The default parameters are:
     *
     *         * entities: the list en entities ;
     *         * crud_name: the name of the crud ;
     *         * paginator: a paginator element ;
     * 10. Launch rendering, the parameter is fetch using `getTemplateFor`
     *     The parameters may be personnalized using `generateTemplateParameter`.
     *
     * @param string $action
     */
    protected function indexEntityAction($action, Request $request)
    {
        $this->onPreIndex($action, $request);

        $response = $this->checkACL($action, null);

        if ($response instanceof Response) {
            return $response;
        }

        $entity = '';

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $filterOrder = $this->buildFilterOrderHelper($action, $request);
        $totalItems = $this->countEntities($action, $request, $filterOrder);
        $paginator = $this->getPaginatorFactory()->create($totalItems);

        $response = $this->onPreIndexBuildQuery(
            $action,
            $request,
            $totalItems,
            $paginator
        );

        if ($response instanceof Response) {
            return $response;
        }

        $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);

        $response = $this->onPostIndexFetchQuery(
            $action,
            $request,
            $totalItems,
            $paginator,
            $entities
        );

        if ($response instanceof Response) {
            return $response;
        }

        $defaultTemplateParameters = [
            'entities' => $entities,
            'crud_name' => $this->getCrudName(),
            'paginator' => $paginator,
            'filter_order' => $filterOrder,
        ];

        return $this->render(
            $this->getTemplateFor($action, $entities, $request),
            $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters)
        );
    }

    /**
     * Return a redirect response depending on the value of submit button.
     *
     * The handled values are :
     *
     * * save-and-close: return to index of current crud ;
     * * save-and-new: return to new page of current crud ;
     * * save-and-view: return to view page of current crud ;
     */
    protected function onBeforeRedirectAfterSubmission(string $action, mixed $entity, FormInterface $form, Request $request): ?Response
    {
        $next = $request->request->get('submit', 'save-and-close');

        return match ($next) {
            'save-and-close' => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'),
            'save-and-new' => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', $request->query->all()),
            default => $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [
                'id' => $entity->getId(),
            ]),
        };
    }

    protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request) {}

    protected function onPostCheckACL($action, Request $request, $entity): ?Response
    {
        return null;
    }

    protected function onPostFetchEntity($action, Request $request, $entity): ?Response
    {
        return null;
    }

    protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) {}

    /**
     * method used by indexAction.
     */
    protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, mixed $query) {}

    /**
     * method used by indexAction.
     */
    protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, mixed $entities) {}

    protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) {}

    protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {}

    protected function onPreDelete(string $action, Request $request) {}

    protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) {}

    protected function onPreIndex(string $action, Request $request) {}

    /**
     * method used by indexAction.
     */
    protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) {}

    protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) {}

    protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {}

    /**
     * Add ordering fields in the query build by self::queryEntities.
     *
     * @param mixed|QueryBuilder $query by default, an instance of QueryBuilder
     *
     * @return mixed|QueryBuilder
     */
    protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
    {
        return $query;
    }

    /**
     * Query the entity.
     *
     * By default, get all entities.
     *
     * The method `orderEntity` is called internally to order entities.
     *
     * It returns, by default, a query builder.
     *
     * @return type
     */
    protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
    {
        $query = $this->buildQueryEntities($action, $request)
            ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
            ->setMaxResults($paginator->getItemsPerPage());

        // allow to order queries and return the new query
        return $this->orderQuery($action, $query, $request, $paginator);
    }

    protected function removeEntity(string $action, $entity, FormInterface $form, Request $request)
    {
        $this->getManagerRegistry()
            ->getManager()
            ->remove($entity);
    }

    /**
     * The view action.
     *
     * Some steps may be overriden during this process of rendering:
     *
     * This method:
     *
     * 1.  fetch the entity, using `getEntity`
     * 2.  launch `onPostFetchEntity`. If postfetch is an instance of Response,
     *     this response is returned.
     * 2.  throw an HttpNotFoundException if entity is null
     * 3.  check ACL using `checkACL` ;
     * 4.  launch `onPostCheckACL`. If the result is an instance of Response,
     *     this response is returned ;
     * 5.  generate default template parameters:
     *
     *     * `entity`: the fetched entity ;
     *     * `crud_name`: the crud name
     * 6.  Launch rendering, the parameter is fetch using `getTemplateFor`
     *     The parameters may be personnalized using `generateTemplateParameter`.
     */
    protected function viewAction(string $action, Request $request, mixed $id, mixed $_format = 'html'): Response
    {
        $entity = $this->getEntity($action, $id, $request);

        $postFetch = $this->onPostFetchEntity($action, $request, $entity);

        if ($postFetch instanceof Response) {
            return $postFetch;
        }

        if (null === $entity) {
            throw $this->createNotFoundException(sprintf('The %s with id %s is not found', $this->getCrudName(), $id));
        }

        $response = $this->checkACL($action, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        $response = $this->onPostCheckACL($action, $request, $entity);

        if ($response instanceof Response) {
            return $response;
        }

        if ('html' === $_format) {
            $defaultTemplateParameters = [
                'entity' => $entity,
                'crud_name' => $this->getCrudName(),
            ];

            return $this->render(
                $this->getTemplateFor($action, $entity, $request),
                $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
            );
        }

        if ('json' === $_format) {
            $context = $this->getContextForSerialization($action, $request, $entity, $_format);

            return $this->json($entity, Response::HTTP_OK, [], $context);
        }

        throw new BadRequestHttpException('This format is not implemented');
    }
}
