<?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\Routing;

use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * This class permit to build menu from the routing information
 * stored in each bundle.
 */
final readonly class MenuComposer
{
    public function __construct(
        private RouterInterface $router,
        private FactoryInterface $menuFactory,
        private TranslatorInterface $translator,
        /**
         * @var iterable<LocalMenuBuilderInterface>
         */
        private iterable $localMenuBuilders,
    ) {}

    public function getMenuFor($menuId, array $parameters = []): ItemInterface
    {
        $routes = $this->getRoutesForInternal($menuId, $parameters);
        /** @var MenuItem $menu */
        $menu = $this->menuFactory->createItem($menuId);

        // build menu from routes
        foreach ($routes as $order => $route) {
            $menu->addChild($this->translator->trans($route['label']), [
                'route' => $route['key'],
                'routeParameters' => $parameters,
                'order' => $order,
            ])
                ->setExtras([
                    // 'icon' => $route['icon'],
                    // sf4 check: commented to avoid error: `An exception has been thrown during the rendering of a template ("Notice: Undefined index: icon").`
                    'order' => $order,
                ]);
        }

        foreach ($this->localMenuBuilders as $builder) {
            if (in_array($menuId, $builder::getMenuIds(), true)) {
                $builder->buildMenu($menuId, $menu, $parameters);
            }
        }

        $this->reorderMenu($menu);

        return $menu;
    }

    /**
     * Return an array of routes added to $menuId,
     * The array is aimed to build route with MenuTwig.
     *
     * @deprecated
     *
     * @param array $parameters see https://redmine.champs-libres.coop/issues/179
     */
    public function getRoutesFor(string $menuId, array $parameters = []): array
    {
        return $this->getRoutesForInternal($menuId, $parameters);
    }

    private function getRoutesForInternal(string $menuId, array $parameters = []): array
    {
        $routes = [];
        $routeCollection = $this->router->getRouteCollection();

        foreach ($routeCollection->all() as $routeKey => $route) {
            if ($route->hasOption('menus')) {
                if (\array_key_exists($menuId, $route->getOption('menus'))) {
                    $route = $route->getOption('menus')[$menuId];

                    $route['key'] = $routeKey;

                    $order = $this->resolveOrder($routes, $route['order']);
                    // we do not want to duplicate order information
                    unset($route['order']);
                    $routes[$order] = $route;
                }
            }
        }

        ksort($routes);

        return $routes;
    }

    /**
     * Return true if the menu has at least one builder.
     *
     * This function is a helper to determine if the method `getMenuFor`
     * should be used, or `getRouteFor`. The method `getMenuFor` should be used
     * if the result is true (it **does** exists at least one menu builder.
     *
     * @deprecated
     */
    public function hasLocalMenuBuilder(string $menuId): bool
    {
        foreach ($this->localMenuBuilders as $localMenuBuilder) {
            if (in_array($menuId, $localMenuBuilder::getMenuIds(), true)) {
                return true;
            }
        }

        return false;
    }

    private function reorderMenu(ItemInterface $menu)
    {
        $ordered = [];
        $unordered = [];

        foreach ($menu->getChildren() as $name => $item) {
            $order = $item->getExtra('order');

            if (null !== $order) {
                $ordered[$this->resolveOrder($ordered, $order)] = $name;
            } else {
                $unordered = $name;
            }
        }

        ksort($ordered);

        $menus = \array_merge(\array_values($ordered), $unordered);
        $menu->reorderChildren($menus);
    }

    /**
     * recursive function to resolve the order of a array of routes.
     * If the order chosen in routing.yml is already in used, find the
     * first next order available.
     *
     * @param array $routes the routes previously added
     * @param int   $order
     *
     * @return int
     */
    private function resolveOrder($routes, $order)
    {
        if (isset($routes[$order])) {
            return $this->resolveOrder($routes, $order + 1);
        }

        return $order;
    }
}
