<?php

namespace AutoAdmin\Bundle\Admin\Twig\Extension;

use AutoAdmin\Bundle\Admin\Configuration\ConfigManager;
use AutoAdmin\Bundle\Admin\Controller\Admin\AbstractAdminController;
use AutoAdmin\Bundle\Admin\Controller\Admin\EntityController;
use AutoAdmin\Bundle\Admin\Entity\Administrator;
use AutoAdmin\Bundle\Admin\Helper\AccessHelper;
use AutoAdmin\Bundle\Admin\Router\AdminRouter;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Exception;
use ReflectionClass;

class SidebarExtension extends AbstractExtension
{
    public function __construct(
        private ConfigManager $configManager,
        private AdminRouter $adminRouter,
        private AccessHelper $accessHelper,
        private TokenStorageInterface $tokenStorage,
        private RequestStack $requestStack,
        private SiteContextHelper $contextHelper
    ) {
    }

    public function getFunctions(): array
    {
        return [
            new TwigFunction('aa_admin_sidebar', [$this, 'getSidebar']),
            new TwigFunction('aa_admin_sitename', [$this, 'getWebsiteName']),
            new TwigFunction('aa_admin_sitelogo', [$this, 'getSiteLogo']),
        ];
    }

    public function getWebsiteName(): string
    {
        return $this->configManager->getConfig('website_name');
    }

    public function getSiteLogo()
    {
        return $this->contextHelper->getSite()->getLogo() ?? null;
    }

    public function getSidebar(): array
    {
        $menuConfig = $this->configManager->getConfig('menu');
        $entitiesConfig = $this->configManager->getConfig('entities');
        /** @var Administrator $admin */
        $admin = $this->tokenStorage->getToken()->getUser();
        $request = $this->requestStack->getCurrentRequest();

        if (!is_array($menuConfig) || empty($admin)) {
            return [];
        }

        $items = [];
        foreach ($menuConfig as $menuItem) {
            // defaults
            $current = array_merge($menuItem, ['icon' => $menuItem['icon'] ?? 'fa fa-circle', 'active' => false,]);
            switch ($menuItem['type']) {
                case 'entity':
                    $entityConfig = $this->configManager->getEntityConfigByClass($menuItem['class']);
                    $actionSlug = $menuItem['action'] ?? $entityConfig['default_action'] ?? 'list';
                    $actionConfig = $this->configManager->getActionConfig($menuItem['class'], $actionSlug);
                    $allowed = $this->accessHelper->isActionAllowed($menuItem['class'], $actionConfig);

                    if (!$allowed) {
                        // continue to the next iteration of the outer loop (foreach)
                        // cf. https://www.php.net/manual/en/control-structures.switch.php
                        continue 2;
                    }

                    $controller = $entityConfig['controller'];
                    $controllerRefl = new ReflectionClass($controller);
                    if (
                        (
                            $controller === EntityController::class
                            || $controllerRefl->isSubclassOf(EntityController::class)
                        ) || (
                            $controller === AbstractAdminController::class
                            || $controllerRefl->isSubclassOf(AbstractAdminController::class)
                        )
                    ) {
                        $link = $this->adminRouter->generate($menuItem['class'], $actionSlug);
                        $current['link'] = $link;
                        $current['label'] = $menuItem['label'] ?? $entityConfig['name'] ?? null;
                    } else {
                        $this->triggerError('Bad config for "entity" menu item (%s)', $menuItem);
                    }

                    // is menu active ?
                    $currentEntity = $request->get('entitySlug');
                    $entityConfig = $entitiesConfig[$menuItem['class']];
                    if ($entityConfig['slug'] == $currentEntity) {
                        $current['active'] = true;
                    }
                    break;
                case 'controller':
                    $link = null;

                    if (empty($menuItem['controller']) || empty($menuItem['method'])) {
                        $this->triggerError('Bad config for "controller" menu item (%s)', $menuItem);
                    }

                    $actionConfig = $this->configManager->getControllerActionConfig(
                        $menuItem['controller'],
                        $menuItem['method']
                    );

                    $allowed = $this->accessHelper->isActionAllowed($menuItem['controller'], $actionConfig);
                    if (!$allowed) {
                        continue 2;
                    }

                    $link = $this->adminRouter->generateUrlFromRoute($menuItem['route'], $menuItem['routeParameters']);
                    $current['link'] = $link;

                    // is menu active ?
                    if (
                        $request->get('_route') === $menuItem['route']
                        && $request->get('controller') === $menuItem['routeParameters']['controller']
                        && $request->get('method') === $menuItem['routeParameters']['method']
                    ) {
                        $current['active'] = true;
                    }
                    break;
                case 'route':
                    $current['link'] = $this->adminRouter
                        ->generateUrlFromRoute($menuItem['route'], $menuItem['routeParameters']);

                    // is menu active ?
                    $route = $request->get('_route');
                    if ($menuItem['route'] === $route) {
                        $current['active'] = true;
                    }
                    break;
                case 'submenu':
                    // will later be filled with its children
                    $current['children'] = [];
                    break;
                case 'endsubmenu':
                    // will only close previously declared submenu
                    break;
                case 'section':
                    // feature now unsupported, prefer using submenus
                    break;
                default:
                    throw new Exception(sprintf('Could not load menu item with type "%s"', $menuItem['type']));
                    break;
            }

            // defaults
            $current['label'] = $current['label'] ?? 'no label';
            $items[] = $current;
        }

        $subMenu = null;
        $multiLevelMenu = [];
        foreach ($items as $k => $item) {
            if ($item['type'] === 'endsubmenu' && $subMenu !== null) {
                // if we do not find any sub items in menu (because none are defined or removed because forbidden action)
                if (count($subMenu['children']) > 0) {
                    $multiLevelMenu[] = $subMenu;
                }
                $subMenu = null;
                continue;
            } elseif ($item['type'] === 'submenu') {
                // stack last sub
                if ($subMenu !== null) {
                    $multiLevelMenu[] = $subMenu;
                }

                $subMenu = $item;
                continue;
            } else {
                if ($subMenu) {
                    $subMenu['children'][] = $item;
                    if (!empty($item['active'])) {
                        $subMenu['active'] = true;
                    }
                } else {
                    $multiLevelMenu[] = $item;
                }
            }

            if ($k === array_key_last($items) && $subMenu !== null) {
                $multiLevelMenu[] = $subMenu;
            }
        }

        return $multiLevelMenu;
    }

    /**
     * @throws Exception
     */
    private function triggerError($errorMessage): void
    {
        $callArgs = func_get_args();
        unset($callArgs[0]);
        $args = [$errorMessage];
        foreach ($callArgs as $k => $variable) {
            if (is_array($variable) || is_object($variable)) {
                $variable = str_replace("\n", "", print_r($variable, true));
            }
            $args[] = $variable;
        }

        throw new Exception(call_user_func_array('sprintf', $args));
    }
}
