<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\AdminSidebar;

use Application\Admin\Utilities\PonghoUrl;
use Application\Core\Model\Site;
use Pongho\Http\Request;

class Sidebar
{
    public const POS_DASHBOARD = 0;

    public const POS_TOP = 10;

    public const POS_MODULES = 20;

    public const POS_APPS = 30;

    public const POS_BOTTOM = 40;

    public const POS_SETTINGS = 50;

    public const POS_PANEL_ACTIONS = 0;

    public const POS_PANEL_FEATURES = 10;

    public const POS_PANEL_SETTINGS = 20;

    /**
     * @var array
     */
    protected $panels = [];

    protected $panels_positions = [];

    protected $items = [];

    protected $items_panels = [];

    protected $linked_items = [];

    /**
     * @var \Application\Core\Model\Site
     */
    protected $site;

    /**
     * @var \Pongho\Http\Request
     */
    protected $request;

    /**
     * @var \Application\Admin\Utilities\PonghoUrl
     */
    protected $pongho_url;

    /**
     * @var string
     */
    protected $path;

    /**
     * @var Item
     */
    protected $active_item;

    public function __construct(Site $site, Request $request, PonghoUrl $pongho_url)
    {
        $this->site = $site;
        $this->request = $request;
        $this->pongho_url = $pongho_url;
    }

    /**
     * Aggiunge una rotta che non compare nella sidebar
     *
     * @param      $path
     * @param      $controller
     * @param Item $linked_item
     * @return Item
     */
    public function addRoute($path, $controller, $module_id = null, ?Item $linked_item = null)
    {
        $path = $this->normalizePath($path);

        if (array_key_exists($path, $this->items)) {
            throw new \InvalidArgumentException(sprintf('The item %s already exists', $path));
        }

        $item = new Item('', $path, $this->pongho_url->url($path), $controller, $module_id);

        $this->items[$path] = $item;

        if ($linked_item instanceof Item) {
            $this->linked_items[$item->getPath()] = $linked_item->getPath();
        }

        return $item;
    }

    /**
     * @param       $label
     * @param       $name
     * @param       $class
     * @param       $position
     * @param array $options
     * @throws \InvalidArgumentException
     * @return Panel
     *
     * @api
     */
    public function addPanel($label, $name, $class, $position, $module = null, $options = [])
    {
        if ($this->hasPanel($name)) {
            throw new \InvalidArgumentException(sprintf('The panel %s already exists', $name));
        }

        $panel = new Panel($label, $name, $class, $module);
        $panel->setOptions($options);
        $this->panels[$position][$name] = $panel;
        $this->panels_positions[$name] = $position;

        return $panel;
    }

    /**
     * @return Panel[]
     */
    public function getPanels()
    {
        $panels = [];

        ksort($this->panels);

        foreach ($this->panels as $positions) {
            foreach ($positions as $name => $panel) {
                $panels[$name] = $panel;
            }
        }

        return $panels;
    }

    /**
     * @param string $name
     * @return bool
     *
     * @api
     */
    public function hasPanel($name)
    {
        return array_key_exists($name, $this->panels_positions);
    }

    /**
     * @param string $name
     * @return \Application\Admin\AdminSidebar\Panel
     * @throws \InvalidArgumentException
     */
    public function getPanel($name)
    {
        if (!$this->hasPanel($name)) {
            throw new \InvalidArgumentException(sprintf('The panel %s does not exist', $name));
        }

        return $this->panels[$this->panels_positions[$name]][$name];
    }

    /**
     * @param string $name
     * @return $this
     *
     * @api
     */
    public function removePanel($name)
    {
        if (!$this->hasPanel($name)) {
            return $this;
        }

        $position = $this->panels_positions[$name];

        unset($this->panels_positions[$name]);
        unset($this->panels[$position][$name]);

        return $this;
    }

    /**
     * @param string $label
     * @param string $path
     * @param string $panel_name
     * @param int    $position
     * @param string $controller
     * @param int    $module_id
     * @throws \InvalidArgumentException
     * @return Item
     * @api
     */
    public function addItem($label, $path, $panel_name, $position, $controller, $module_id = null)
    {
        $path = $this->normalizePath($path);

        if (array_key_exists($path, $this->items)) {
            throw new \InvalidArgumentException(sprintf('The item %s already exists', $path));
        }

        if (!array_key_exists($panel_name, $this->panels_positions)) {
            throw new \InvalidArgumentException(sprintf('The panel %s does not exists', $panel_name));
        }

        $item = new Item($label, $path, $this->pongho_url->url($path), $controller, $module_id);

        $this->items[$path] = $item;

        $this->items_panels[$path] = [
            'panel'    => $panel_name,
            'position' => $position,
        ];

        return $item;
    }

    /**
     * @param string $path
     * @return Item
     * @throws \InvalidArgumentException
     */
    public function getItem($path)
    {
        $path = $this->normalizePath($path);

        if (!array_key_exists($path, $this->items)) {
            throw new \InvalidArgumentException(sprintf('The item %s does not exist', $path));
        }

        return $this->items[$path];
    }

    /**
     * @param string $path
     * @return $this
     *
     * @api
     */
    public function removeItem($path)
    {
        $path = $this->normalizePath($path);

        if (!array_key_exists($path, $this->items)) {
            return $this;
        }

        unset($this->items_panels[$path]);
        unset($this->items[$path]);

        return $this;
    }

    /**
     * @param string $panel_name
     * @return Item[]
     * @throws \InvalidArgumentException
     */
    public function getItems($panel_name)
    {
        if (!$this->hasPanel($panel_name)) {
            throw new \InvalidArgumentException();
        }

        $panels = [];
        foreach ($this->items_panels as $path => $details) {
            if ($details['panel'] === $panel_name) {
                $panels[$details['position']][$path] = $this->getItem($path);
            }
        }

        ksort($panels);

        $result = [];
        foreach ($panels as $items) {
            foreach ($items as $path => $item) {
                $result[$path] = $item;
            }
        }

        return $result;
    }

    /**
     * Restituisce l'path normalizzato (con gli slash corretti)
     *
     * @param string $path
     * @return string
     */
    protected function normalizePath($path)
    {
        // Rimuove il dominio del sito
        $domain = $this->site->getDomain();
        if (str_starts_with($path, $domain)) {
            $path = substr($path, strlen($domain));
        }

        // Rimuove la query string
        if (($pos = strpos('?', $path)) !== false) {
            $path = substr($path, 0, $pos);
        }

        // Corregge gli slash
        $slug = trim($path, '/');

        if ($slug === '') {
            return '/';
        }

        return '/' . $slug . '/';
    }

    /**
     * @param $path
     * @return string
     */
    public function getController($path)
    {
        return $this->getItem($path)->getController();
    }

    /**
     * @return null|Item
     * @throws \Pongho\Http\Exception\HttpNotFoundException
     */
    public function getActiveItem()
    {
        if ($this->active_item === null) {
            $request_path = $this->request->getPathInfo();
            $request_path = $this->normalizePath($request_path);

            if (str_starts_with($request_path, '/pongho')) {
                $request_path = substr($request_path, strlen('/pongho'));
            }

            $paths = $this->items;

            // Gestione della Dashboard
            if (isset($paths['/'])) {
                if ($request_path !== '/') {
                    unset($paths['/']);
                } else {
                    $this->active_item = $this->getItem('/');
                    $this->active_item->setActive();

                    return $this->active_item;
                }
            }

            // Ordino i percorsi per lunghezza
            uksort(
                $paths,
                function ($a, $b) {
                    if (strlen($a) === strlen($b)) {
                        return 0;
                    }

                    if (strlen($a) > strlen($b)) {
                        return -1;
                    }

                    return 1;
                }
            );

            foreach (array_keys($paths) as $path) {
                if (str_starts_with($request_path, (string) $path)) {
                    $this->active_item = $this->getItem($path);

                    break;
                }
            }

            if ($this->active_item === null) {
                return null;
            }

            // Se l'item è una rotta con un item linkato, deve attivarlo
            if (isset($this->linked_items[$this->active_item->getPath()])) {
                /** @var Item $item */
                $item = $this->items[$this->linked_items[$this->active_item->getPath()]];

                $item->setActive();
            }

            $this->active_item->setActive();
        }

        return $this->active_item;
    }

    /**
     * Renderizza il menu
     *
     * @return string
     */
    public function render()
    {
        // Serve solo ad impostare come attivo l'item corrispondente al percorso corrente
        $this->getActiveItem();

        $code = '';
        foreach ($this->getPanels() as $panel_name => $panel) {
            $items = $this->getItems($panel_name);
            $count = count($items);

            // Niente da renderizzare
            if ($count === 0) {
                continue;
            }

            // Aggiungo gli item al panel per poter utilizzare il render() del Menu
            foreach ($items as $item) {
                $panel->add($item);
            }

            if ($count > 1 || $panel->isDropdownForced()) {
                $panel->addClass('dropdown');
                $code .= $panel->renderOpen() . $panel->renderItem() . '<ul class="menu-depth-' . $panel->getDepth() . '">' . $panel->render() . '</ul>' . $panel->renderClose();
            } elseif ($count === 1) {
                $item = reset($items);
                $item->addClass(implode(' ', $panel->getClasses()));
                $code .= $panel->render();
            }
        }

        return $code;
    }
}
