<?php
declare(strict_types=1);

namespace Celena\Core\Template\Tags;

use Celena\Core\Database\Connection;

/**
 * {menu name="header" class="..."}
 *
 * Выводит меню по slug-у из таблиц celena_menus + celena_menu_items.
 * По умолчанию рисует двухуровневый <ul> с поддержкой dropdown через CSS:
 * первый уровень — пункты с parent_id = NULL, второй — их дочерние пункты.
 *
 * Параметры:
 *   - name      (обязательный) — slug меню (header, footer, mobile…)
 *   - class     CSS-класс корневого <ul> (default: "menu menu-{slug}")
 *   - id        id корневого <ul>
 *   - sub_class класс вложенного <ul> (default: "menu-sub")
 *   - active    URL текущей страницы — пункт с совпавшим url получит .is-active
 *   - empty     текст-фоллбэк, если меню пусто (по умолчанию ничего не выводит)
 *
 * Активный пункт определяется по совпадению url с текущим путём (берётся из
 * $_SERVER['REQUEST_URI'], можно переопределить параметром active).
 *
 * Если меню с таким slug не существует или пусто — возвращается строка из
 * `empty` или пустая, чтобы темы безопасно использовали тег даже до настройки.
 */
final class MenuTag
{
    /** Кэш на время одного запроса: slug → массив пунктов. */
    private array $cache = [];

    public function __construct(private readonly Connection $conn)
    {
    }

    public function __invoke(array $params): string
    {
        $slug = trim((string) ($params['name'] ?? ''));
        if ($slug === '') return '';

        $items = $this->load($slug);
        if ($items === []) {
            return (string) ($params['empty'] ?? '');
        }

        $rootClass = (string) ($params['class']    ?? 'menu menu-' . $this->safeSlug($slug));
        $rootId    = (string) ($params['id']       ?? '');
        $subClass  = (string) ($params['sub_class'] ?? 'menu-sub');
        $activeUrl = trim((string) ($params['active'] ?? $this->currentUrl()));

        // Группируем по parent_id для O(1) построения дерева.
        $byParent = [];
        foreach ($items as $i) {
            $byParent[(int) ($i['parent_id'] ?? 0)][] = $i;
        }
        $top = $byParent[0] ?? [];
        if ($top === []) return (string) ($params['empty'] ?? '');

        $idAttr = $rootId !== '' ? ' id="' . htmlspecialchars($rootId, ENT_QUOTES, 'UTF-8') . '"' : '';
        $html = '<ul class="' . htmlspecialchars($rootClass, ENT_QUOTES, 'UTF-8') . '"' . $idAttr . '>';

        foreach ($top as $item) {
            $children = $byParent[(int) $item['id']] ?? [];
            $html .= $this->renderItem($item, $children, $subClass, $activeUrl);
        }

        return $html . '</ul>';
    }

    /** @return list<array<string,mixed>> */
    private function load(string $slug): array
    {
        if (isset($this->cache[$slug])) return $this->cache[$slug];
        try {
            $menu = $this->conn->builder('celena_menus')->where('slug', '=', $slug)->first();
            if ($menu === null) return $this->cache[$slug] = [];
            $items = $this->conn->builder('celena_menu_items')
                ->where('menu_id', '=', (int) $menu['id'])
                ->where('is_active', '=', true)
                ->orderBy('sort_order', 'asc')
                ->orderBy('id', 'asc')
                ->get();
            return $this->cache[$slug] = $items;
        } catch (\Throwable) {
            return $this->cache[$slug] = [];
        }
    }

    /** @param list<array<string,mixed>> $children */
    private function renderItem(array $item, array $children, string $subClass, string $activeUrl): string
    {
        $kind = (string) ($item['kind'] ?? 'external');
        if ($kind === 'divider') {
            return '<li class="menu-divider" role="separator"></li>';
        }

        $title  = (string) ($item['title'] ?? '');
        $url    = (string) ($item['url']   ?? '#');
        $target = (string) ($item['target'] ?? '_self');
        $icon   = trim((string) ($item['icon'] ?? ''));
        $hasChildren = $children !== [];

        $itemClass = 'menu-item';
        if ($hasChildren) $itemClass .= ' has-children';
        if ($activeUrl !== '' && $this->isActive($url, $activeUrl)) $itemClass .= ' is-active';

        $iconHtml = $icon !== '' ? '<span class="menu-icon menu-icon-' . htmlspecialchars($icon, ENT_QUOTES, 'UTF-8') . '"></span>' : '';
        $targetAttr = $target === '_blank' ? ' target="_blank" rel="noopener"' : '';

        $li  = '<li class="' . $itemClass . '">';
        $li .= '<a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '"' . $targetAttr . '>'
             . $iconHtml
             . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
             . '</a>';

        if ($hasChildren) {
            $li .= '<ul class="' . htmlspecialchars($subClass, ENT_QUOTES, 'UTF-8') . '">';
            foreach ($children as $ch) {
                // Внутри dropdown больше не пускаем внуков — два уровня достаточно.
                $li .= $this->renderItem($ch, [], $subClass, $activeUrl);
            }
            $li .= '</ul>';
        }

        return $li . '</li>';
    }

    private function currentUrl(): string
    {
        $uri = (string) ($_SERVER['REQUEST_URI'] ?? '');
        $q = strpos($uri, '?');
        return $q === false ? $uri : substr($uri, 0, $q);
    }

    /** Совпадение по url: точное равенство ИЛИ префикс «/news» матчит «/news/...». */
    private function isActive(string $itemUrl, string $current): bool
    {
        if ($itemUrl === '' || $itemUrl === '#') return false;
        if ($itemUrl === $current) return true;
        // «/» матчит только сам себя, иначе любая страница была бы активна.
        if ($itemUrl === '/') return $current === '/';
        return str_starts_with($current, rtrim($itemUrl, '/') . '/');
    }

    private function safeSlug(string $s): string
    {
        return preg_replace('/[^a-z0-9_-]/i', '-', $s) ?? 'menu';
    }
}
