<?php
declare(strict_types=1);

namespace Celena\Core\Template\Tags;

/**
 * Универсальный блок-тег для вывода списков (новости, товары и т.п.) в шаблонах.
 *
 * Регистрируется как блок-тег с произвольным именем — тег принимает callable-fetcher,
 * который по параметрам тега возвращает массив элементов, и итерирует тело блока,
 * подставляя поля каждого элемента в переменные шаблона.
 *
 * Пример регистрации:
 *
 *   $tags->registerBlock('news', new ContentListTag(
 *       fn (array $params): array => $newsRepo->fetch($params),
 *       defaults: ['limit' => 10, 'sort' => 'published_at', 'order' => 'desc'],
 *   ));
 *
 * И в шаблоне:
 *
 *   [news category="tech" limit="5"]
 *     <a href="{url}"><img src="{cover|thumb}"> {title}</a>
 *   [/news]
 *
 * Поля элемента доступны напрямую как переменные ({title}, {url}, ...). Если задан
 * параметр `as="item"` — также через `{item.field}`. Внутри тела доступны служебные:
 *   {_index}  — 0-based индекс
 *   {_total}  — общее количество
 *   {_first}, {_last} — "1" если первый/последний, иначе ""
 *
 * Если fetcher вернул пустой массив — тело не рендерится. Для пустого состояния
 * используйте соседний `[if {название_var}]` с флагом из контроллера, или просто
 * посчитайте через {async block="..."} с сервер-сайд проверкой.
 */
final class ContentListTag
{
    /** @var callable(array): array<int, array<string,mixed>> */
    private $fetcher;

    /** @var array<string, mixed> */
    private array $defaults;

    /**
     * @param callable(array): array<int, array<string,mixed>> $fetcher
     * @param array<string, mixed> $defaults
     */
    public function __construct(callable $fetcher, array $defaults = [])
    {
        $this->fetcher = $fetcher;
        $this->defaults = $defaults;
    }

    public function __invoke(array $params, array $vars, callable $renderBody): string
    {
        $merged = array_merge($this->defaults, $params);
        $items = ($this->fetcher)($merged);
        if (!is_array($items) || $items === []) {
            return '';
        }

        $alias = isset($params['as']) && is_string($params['as']) && $params['as'] !== ''
            ? $params['as']
            : null;

        $items = array_values($items);
        $total = count($items);
        $out = '';
        foreach ($items as $i => $item) {
            $loopVars = $vars;
            // Поля элемента подставляются как переменные первого уровня — {title}, {price_fmt}, …
            if (is_array($item)) {
                foreach ($item as $k => $v) {
                    if (is_string($k)) {
                        $loopVars[$k] = $v;
                    }
                }
            }
            // Альтернативный доступ через alias (как в [foreach as="x"])
            if ($alias !== null) {
                $loopVars[$alias] = $item;
            }
            $loopVars['_index'] = $i;
            $loopVars['_total'] = $total;
            $loopVars['_first'] = $i === 0 ? '1' : '';
            $loopVars['_last']  = $i === $total - 1 ? '1' : '';
            $out .= (string) $renderBody($loopVars);
        }
        return $out;
    }
}
