<?php


namespace AutoAdmin\Bundle\Menu\Provider;


use AutoAdmin\Bundle\Content\ContentReference\ContentReferenceResolver;
use AutoAdmin\Bundle\Content\Routing\ContentRouter;
use AutoAdmin\Bundle\Menu\Entity\Menu;
use AutoAdmin\Bundle\Menu\Entity\MenuItem;
use AutoAdmin\Bundle\Menu\Entity\MenuItemTranslation;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\Provider\MenuProviderInterface;

class MenuProvider implements MenuProviderInterface
{
    protected ?FactoryInterface $factory = null;
    private EntityManagerInterface $entityManager;
    private ContentRouter $contentRouter;
    private ContentReferenceResolver $contentReferenceResolver;

    /**
     * @param FactoryInterface $factory the menu factory used to create the menu item
     */
    public function __construct(
        FactoryInterface         $factory,
        EntityManagerInterface   $entityManager,
        ContentRouter            $contentRouter,
        ContentReferenceResolver $contentReferenceResolver
    )
    {
        $this->factory = $factory;
        $this->entityManager = $entityManager;
        $this->contentRouter = $contentRouter;
        $this->contentReferenceResolver = $contentReferenceResolver;
    }

    /**
     * Retrieves a menu by its name
     *
     * @throws InvalidArgumentException if the menu does not exists
     */
    public function get(string $name, array $options = []): ItemInterface
    {
        $menuRepo = $this->entityManager->getRepository(Menu::class);
        /** @var Menu $menu */
        $menu = $menuRepo->contentFindOneBySlug($name);

        if ($menu !== null && $root = $menu->getRoot()) {
            $item = $this->buildMenuItems($root);
        } else {
            throw new InvalidArgumentException(sprintf('The menu "%s" is not defined.', $name));
        }

        return $item;
    }

    protected function buildMenuItems(MenuItem $item, $parent = null): ItemInterface
    {
        if ($parent === null) {
            $parent = $this->factory->createItem('root');
        }

        $currentItemNode = $this->convert($item);
        $parent->addChild($currentItemNode);
        $parent = $currentItemNode;

        if (!empty($item->getChildren())) {
            foreach ($item->getChildren() as $child) {
                $this->buildMenuItems($child, $parent);
            }
        }

        return $parent;
    }

    protected function convert(MenuItem $item): ItemInterface
    {
        /** @var MenuItemTranslation $itemTranslation */
        $itemTranslation = $item->getCurrentTranslation();
        $nodeItem = $this->factory->createItem($itemTranslation->getLabel(), []);

        switch ($item->getType()) {
            case MenuItem::TYPE_TEXT:
                break;
            case MenuItem::TYPE_LINK_URL:
                $nodeItem->setUri($item->getUri());
                break;
            case MenuItem::TYPE_LINK_CONTENT:
                // resolve content urls using contentReference
                $content = $this->contentReferenceResolver->resolve($item->getContentReference());
                if ($content) {
                    $action = $item->getContentReference()->getReferencedAction();
                    $url = $this->contentRouter->generate($content, $action);
                    $nodeItem->setUri($url);
                    $routeName = $this->contentRouter->getRouteName($content, $action);
                    $routeParams = $this->contentRouter->buildRouteParams($content, $action);
                    if (!empty($routeName) && !empty($routeParams)) {
                        $nodeItem->setExtra('route', $routeName);
                        $nodeItem->setExtra('routeParameters', $routeParams);

                        // this index is used to flag element as active in menu item matcher
                        $nodeItem->setExtra('routes', [['route' => $routeName, 'parameters' => $routeParams,],]);
                    }
                }

                break;
        }

        $nodeItem->setDisplay($item->isDisplay());

        return $nodeItem;
    }

    /**
     * Checks whether a menu exists in this provider
     */
    public function has(string $name, array $options = []): bool
    {
        $menuRepo = $this->entityManager->getRepository(Menu::class);
        /** @var Menu $menu */
        $menu = $menuRepo->contentFindOneBySlug($name);

        return $menu !== null;
    }
}
