<?php

declare(strict_types=1);

namespace apexl\Io\modules\tabulator;

use apexl\Config\ConfigurationInterface;
use apexl\Io\enums\HttpMethod;
use apexl\Io\exceptions\RouteNotFoundException;
use apexl\Io\includes\HookManager;
use apexl\Io\includes\Module;
use apexl\Io\includes\RouteManager;
use apexl\Io\modules\menu\interfaces\HasMenuEntryInterface;
use apexl\Io\modules\system\interfaces\ProvidesRoutesInterface;
use apexl\Io\modules\tabulator\Collections\HasTabulatorViewInterfaceClassCollection;
use apexl\Io\modules\tabulator\Factories\TabulatorAjaxControllerFactory;
use apexl\Io\modules\tabulator\Factories\TabulatorControllerFactory;
use apexl\Io\modules\tabulator\Factories\TabulatorDisplayControllerFactory;
use apexl\Io\modules\tabulator\Interfaces\HasTabulatorViewInterface;
use apexl\Io\services\InstallChecker;
use app\vendor\apexl\utils\src\Utils\Strings\DashCase;
use DI\DependencyException;
use DI\NotFoundException;

final class tabulatorModule extends Module implements ProvidesRoutesInterface
{
    public const string HOOK__COLLECT_ENTITIES = 'hook.collect_entities';

    private ?HasTabulatorViewInterfaceClassCollection $entitiesCache = null;

    public function __construct(
        InstallChecker $installChecker,
        ConfigurationInterface $config,
        readonly private HookManager $hookManager,
        readonly private TabulatorControllerFactory $tabulatorControllerFactory,
        readonly private TabulatorAjaxControllerFactory $tabulatorAjaxControllerFactory,
        readonly private TabulatorDisplayControllerFactory $tabulatorDisplayControllerFactory,
    ) {
        parent::__construct($installChecker, $config);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException|RouteNotFoundException
     */
    public function routes(RouteManager $routeManager): void
    {
        $this->tabulatableRoutes($routeManager);
        $this->accountRoutes($routeManager);
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     */
    private function tabulatableRoutes(RouteManager $routeManager): void
    {
        $this->entities()->each(fn(string $entityClass) => $this->tabulatableRoute(
            $routeManager,
            $entityClass
        ));
    }

    /**
     * Collected once per request and memoised — the collect hook constructs nothing
     * (it yields class-strings), and both route passes share the same collection.
     */
    private function entities(): HasTabulatorViewInterfaceClassCollection
    {
        return $this->entitiesCache ??= $this->hookManager->processHook(
            self::HOOK__COLLECT_ENTITIES,
            new HasTabulatorViewInterfaceClassCollection()
        );
    }

    /**
     * @param class-string<HasTabulatorViewInterface> $entityClass
     * @throws DependencyException
     * @throws NotFoundException
     */
    private function tabulatableRoute(RouteManager $routeManager, string $entityClass): void
    {
        $name = DashCase::dashCase($entityClass::shortName());

        $this->addRoute(
            $routeManager,
            HttpMethod::GET,
            $entityClass::tabulatorRouteName(),
            sprintf('data/tabulator/%s', $name),
            $this->tabulatorControllerFactory->make($entityClass),
        );

        $this->addRoute(
            $routeManager,
            HttpMethod::GET,
            $entityClass::tabulatorAjaxRouteName(),
            sprintf('data/tabulator/ajax/%s', $name),
            $this->tabulatorAjaxControllerFactory->make($entityClass),
        );
    }

    /**
     * @throws DependencyException
     * @throws NotFoundException
     * @throws RouteNotFoundException
     */
    private function accountRoutes(RouteManager $routeManager): void
    {
        foreach ($this->entities() as $entityClass) {
            // Only entities that opt into the menu surface get an account
            // route — accountRoute reads menu metadata, so it only applies to
            // entities implementing the menu interface. Guard here so
            // downstream modules can register entities without implementing
            // the menu interface.
            if (is_a($entityClass, HasMenuEntryInterface::class, true)) {
                $this->accountRoute($routeManager, $entityClass);
            }
        }
    }

    /**
     * @param class-string<HasMenuEntryInterface> $entityClass
     * @throws DependencyException
     * @throws NotFoundException
     * @throws RouteNotFoundException
     */
    private function accountRoute(RouteManager $routeManager, string $entityClass): void
    {
        $menu = $entityClass::menuMetadata();

        $this->rootRoute(
            $routeManager,
            HttpMethod::GET,
            $menu->route,
            $menu->pattern,
            $this->tabulatorDisplayControllerFactory->make($entityClass),
        );

        $routeManager->addRouteArg($menu->route, 'permission', serialize($menu->permission));
    }

}
