<?php


namespace AutoAdmin\Bundle\Menu\Controller\Admin;

use AutoAdmin\Bundle\Admin\Controller\Admin\AbstractAdminController;
use AutoAdmin\Bundle\Admin\Exception\ForbiddenActionException;
use AutoAdmin\Bundle\Admin\Factory\ContentFactory;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use AutoAdmin\Bundle\Menu\Entity\Menu;
use AutoAdmin\Bundle\Menu\Entity\MenuItem;
use AutoAdmin\Bundle\Menu\Form\MenuItemType;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class MenuItemController extends AbstractAdminController
{
    public function __construct(
        protected ContentFactory $contentFactory,
    ) {
    }

    /**
     * @throws ForbiddenActionException
     */
    public function list(Request $request): Response
    {
        $this->ensureActionIsAllowed(MenuItem::class, 'list');

        /** @var Menu $menu */
        $menu = $this->entityHelper->getManager()->getRepository(Menu::class)->find($request->query->get('menuId'));
        $root = $menu->getRoot();

        return $this->render(
            '@AutoAdminMenu/admin/menu_item/list.html.twig',
            [
                'mainTitle' => sprintf('Modification du menu "%s"', $menu->getName()),
                'root' => $root,
                'menu' => $menu,
            ]
        );
    }

    public function create(Request $request): JsonResponse
    {
        return $this->update($request);
    }

    public function update(Request $request): JsonResponse
    {
        $em = $this->entityHelper->getManager();
        $menuRepo = $em->getRepository(Menu::class);
        $menuItemRepo = $em->getRepository(MenuItem::class);

        $menuId = $request->query->get('menuId');
        $menuItemId = $request->query->get('menuItemId');
        $parentMenuItemId = $request->query->get('parentMenuItemId');

        $root = $menuRepo->contentFindOneBy(['_.id' => $menuId])->getRoot();
        $this->createTranslationIfNotExists($root, $adminLocale = $request->get('_admin_locale'));

        $json = [
            'errors' => [],
            'saved' => null,
        ];

        if ($menuItemId) {
            $item = $menuItemRepo->contentFindOneBy(['_.id' => $menuItemId]);
            if ($item === null) {
                // if we matched some content here and not before, we are missing a translation for this translatable
                $translatableItem = $menuItemRepo->findOneBy(['id' => $menuItemId]);
                if ($translatableItem) {
                    $this->createTranslationIfNotExists($translatableItem, $adminLocale);
                }
            }
        } else {
            /** @var MenuItem $item */
            $item = $this->contentFactory->createEntity(MenuItem::class);
        }

        $form = $this->createForm(MenuItemType::class, $item);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            /** @var MenuItem $item */
            $item = $form->getData();
            // set parent and sequence
            if ($parentMenuItemId) {
                /** @var MenuItem|null $parentMenuItem */
                $parentMenuItem = $menuItemRepo->contentFindOneBy(['_.id' => $parentMenuItemId]);
                if ($parentMenuItem) {
                    $item->setParent($parentMenuItem);
                    if ($children = $parentMenuItem->getChildren()) {
                        if ($last = $children->last()) {
                            $item->setSequence($last->getSequence() + 1);
                        }
                    }
                }
            }

            $em->persist($item);
            $em->flush();

            // refresh both before using them in html and json
            $em->refresh($item);
            $em->refresh($root);

            $itemJson = $this->entityHelper->getSerializer()->serialize($item, 'json', ['groups' => 'group_ajax']);
            $json['saved'] = json_decode($itemJson, true);
            $json['tree'] = $this->renderView(
                '@AutoAdminMenu/admin/menu_item/ajax/tree.html.twig',
                [
                    'root' => $root,
                ]
            );

            return $this->json($json);
        }

        $json['form'] = $this->renderView(
            '@AutoAdminMenu/admin/menu_item/ajax/update.html.twig',
            [
                'form' => $form->createView(),
                'menuId' => $menuId,
                'menuItemId' => $menuItemId,
                'item' => $item,
            ]
        );

        return $this->json($json);
    }

    private function createTranslationIfNotExists(TranslatableInterface $translatable, string $locale)
    {
        $found = null;
        $translations = $translatable->getTranslations();
        /** @var TranslationInterface $translation */
        foreach ($translations as $translation) {
            if ($translation->getLocale()->getCode() == $locale) {
                $found = $translation;
            }
        }

        if (!$found) {
            $translatable = $this->contentFactory->createTranslationForEntity($translatable, $locale);
            $this->em->persist($translatable);
            $this->em->flush();
        }
    }

    public function delete(Request $request)
    {
        $menuItemId = $request->query->get('id');
        if (empty($menuItemId)) {
            exit(json_encode(['error' => 'Missing parameter "id".']));
        }

        $json = [
            'errors' => [],
            'successes' => [],
        ];
        $em = $this->entityHelper->getManager();
        $menuItemRepo = $em->getRepository(MenuItem::class);
        try {
            $entry = $menuItemRepo->find($menuItemId);
            if (!empty($entry)) {
                $em->remove($entry);
                $em->flush();
                $json['successes'][] = 'L\'entité a été supprimée.';
            } else {
                $json['errors'][] = 'Une erreur a empêché la suppression de l\'entité.';
            }
        } catch (Exception $e) {
            $json['errors'][] = 'Impossible de trouver l\'entité à supprimer';
            $json['exception'][] = $e->getMessage();
        }

        return new JsonResponse($json);
    }

    public function reorder(Request $request)
    {
        $id = $request->get('id');
        $rootId = $request->get('rootId');
        $serialized = $request->get('serialized');
        if (empty($id) || empty($rootId) || empty($serialized)) {
            exit(json_encode(['error' => 'At least one parameter is missing.']));
        }
        /** @var EntityManagerInterface $em */
        $em = $this->entityHelper->getManager();
        $itemRepo = $em->getRepository(MenuItem::class);

        $json = [
            'errors' => [],
            'successes' => [],
        ];

        try {
            $item = $itemRepo->find($id);
            $rootItem = $itemRepo->find($rootId);
            $hydrated = $this->hydrateItems($serialized, $itemRepo);
            // save item sequence/parent changes
            $this->processSequenceAndParent($hydrated, $em, $rootItem);
            $em->flush();
            $json['successes'][] = 'Sauvegarde OK';
        } catch (Exception $e) {
//            throw $e;
            $json['errors'][] = 'Un problème est survenu lors de la sauvegarde. ('.$e->getMessage().')';
        }

        return $this->json($json);
    }

    private function hydrateItems($serialized, $repo): array
    {
        foreach ($serialized as $idx => $item) {
            $serialized[$idx]['item'] = $repo->find($item['id']);
            if (!empty($item['children'])) {
                $serialized[$idx]['children'] = $this->hydrateItems($item['children'], $repo);
            }
        }

        return $serialized;
    }

    private function processSequenceAndParent($serialized, EntityManagerInterface $em, $parentItem)
    {
        foreach ($serialized as $idx => $item) {
            /** @var MenuItem $menuItem */
            $menuItem = $item['item'];
            $menuItem->setSequence($idx);
            $menuItem->setParent($parentItem);
            $em->persist($menuItem);
            if (!empty($item['children'])) {
                $serialized[$idx]['children'] = $this->processSequenceAndParent($item['children'], $em, $menuItem);
            }
        }
    }
}
