<?php

declare(strict_types=1);

namespace Apexl\Io\Menu\Factories;

use Apexl\Io\Exceptions\RouteNotFoundException;
use Apexl\Io\Menu\MenuItem;
use Apexl\Io\Routing\DeclaresRoutePermission;
use Apexl\Io\Routing\IoRoute;
use Apexl\Io\Services\RouteUrls;
use Apexl\Io\User\Exceptions\MasqueradeException;
use Apexl\Io\User\Exceptions\PermissionException;
use Apexl\Io\User\Services\CurrentUserFactory;
use DI\Container;
use DI\DependencyException;
use DI\NotFoundException;
use Slim\App;

final readonly class MenuItemFactory
{
    public function __construct(
        private CurrentUserFactory $currentUserFactory,
        private Container $container,
        private RouteUrls $routeUrls,
        private App $application,
    ) {}

    /**
     * @throws PermissionException
     * @throws NotFoundException
     * @throws MasqueradeException
     * @throws DependencyException
     */
    public function make(array $item): MenuItem
    {
        $path = null;

        try {
            $path = $this->routeUrls->url($item['routeName'], $item['replacements'] ?? []);

            // Ask the route what it requires. The permission is the middleware that enforces
            // it, so what the menu hides and what a request is refused cannot drift apart —
            // there is one declaration, not a copy of one.
            //
            // Every gate, not the first: a route can sit in a group that requires one thing
            // and require another itself, and at dispatch both have to pass. Checking one
            // would offer a page the caller is then refused.
            $route = $this->application->getRouteCollector()->getNamedRoute($item['routeName']);
            $gates = $route instanceof IoRoute
                ? $route->getMiddleware()->findAll(DeclaresRoutePermission::class)
                : [];

            // A route that requires nothing is not a route nobody may see.
            foreach ($gates as $gate) {
                if (!$this->currentUserFactory->get()->isAllowed($gate->getPermission())) {
                    throw new PermissionException('You do not have permission to access this page.');
                }
            }
        } catch (RouteNotFoundException) {
            // A name that matches no route is how a heading is currently declared: the item
            // gets no href and menuItem renders it as a parent.
            //
            // A MissingRouteValueException is not caught with it. An item naming a real
            // route and failing to say which record it means is a mistake in the
            // declaration, and it should be heard. A heading should be asked for by passing
            // no href, not arrived at by failing to build one.
        }

        return $this->container->make(MenuItem::class, ['item' => $item, 'href' => $path]);
    }
}
