<?php
declare(strict_types=1);

namespace Celena\Module\Categories;

use Celena\Core\Database\Connection;

/**
 * Источник для блок-тега [categories ...]. Возвращает список категорий по фильтрам:
 *   - entity="news|page|shop_product|..." — сущность (по умолчанию "news")
 *   - parent="0|<id>"                     — фильтр по родителю (0 — только верхний уровень)
 *   - limit="N"                           — сколько (по умолчанию 10, максимум 100)
 *   - offset="N"                          — пропустить
 *   - sort="news_count|title|sort_order|id" — сортировка (по умолчанию news_count)
 *   - order="asc|desc"
 *   - ids="1,5,7"                         — точечный список (приоритетнее всего)
 *
 * Для каждой категории добавляются обогащённые поля:
 *   url        — /category/{slug} (для entity="news"); для других сущностей пусто
 *   news_count — количество опубликованных новостей в категории (только при entity="news")
 *   cover      — обложка первой новости в категории (для красивых превью на главной)
 */
final class CategoriesContentSource
{
    private const ALLOWED_SORT = ['news_count', 'title', 'sort_order', 'id'];

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

    /**
     * @param array<string, mixed> $params
     * @return array<int, array<string, mixed>>
     */
    public function fetch(array $params): array
    {
        $entity = (string) ($params['entity'] ?? 'news');
        $entity = preg_replace('/[^a-z0-9_\-]/i', '', $entity) ?: 'news';

        $q = $this->conn->builder('celena_categories')->where('entity_type', '=', $entity);

        if (!empty($params['ids'])) {
            $ids = $this->parseIds((string) $params['ids']);
            if ($ids === []) return [];
            $q->whereIn('id', $ids);
        } elseif (isset($params['parent']) && $params['parent'] !== '') {
            $parent = (int) $params['parent'];
            if ($parent <= 0) {
                $q->whereNull('parent_id');
            } else {
                $q->where('parent_id', '=', $parent);
            }
        }

        $rows = $q->get();
        if ($rows === []) {
            return [];
        }

        // Обогащаем news_count и cover, если entity = news
        $catIds = array_map(static fn ($r) => (int) $r['id'], $rows);
        $countByCat = [];
        $coverByCat = [];
        if ($entity === 'news' && $catIds !== []) {
            $rawRows = $this->conn->builder('celena_news')
                ->select('category_id', 'cover', 'published_at')
                ->where('status', '=', 'published')
                ->whereIn('category_id', $catIds)
                ->orderBy('published_at', 'desc')
                ->get();
            foreach ($rawRows as $n) {
                $cid = (int) ($n['category_id'] ?? 0);
                if ($cid <= 0) continue;
                $countByCat[$cid] = ($countByCat[$cid] ?? 0) + 1;
                if (!isset($coverByCat[$cid]) && !empty($n['cover'])) {
                    $coverByCat[$cid] = (string) $n['cover'];
                }
            }
        }

        foreach ($rows as &$r) {
            $cid = (int) $r['id'];
            $r['news_count'] = $countByCat[$cid] ?? 0;
            $r['cover'] = $coverByCat[$cid] ?? '';
            $r['url'] = $entity === 'news' ? '/category/' . (string) ($r['slug'] ?? '') : '';
        }
        unset($r);

        // Сортировка (после обогащения, чтобы сортировать по news_count)
        $sort = (string) ($params['sort'] ?? 'news_count');
        if (!in_array($sort, self::ALLOWED_SORT, true)) {
            $sort = 'news_count';
        }
        $orderAsc = strtolower((string) ($params['order'] ?? 'desc')) === 'asc';
        usort($rows, static function (array $a, array $b) use ($sort, $orderAsc): int {
            $av = $a[$sort] ?? null;
            $bv = $b[$sort] ?? null;
            if (is_numeric($av) && is_numeric($bv)) {
                $cmp = ((float) $av) <=> ((float) $bv);
            } else {
                $cmp = strcmp((string) $av, (string) $bv);
            }
            return $orderAsc ? $cmp : -$cmp;
        });

        $offset = max(0, (int) ($params['offset'] ?? 0));
        $limit = (int) ($params['limit'] ?? 10);
        if ($limit < 1) $limit = 10;
        if ($limit > 100) $limit = 100;
        if ($offset > 0) $rows = array_slice($rows, $offset);
        if (count($rows) > $limit) $rows = array_slice($rows, 0, $limit);

        return array_values($rows);
    }

    /** @return int[] */
    private function parseIds(string $raw): array
    {
        $out = [];
        foreach (explode(',', $raw) as $piece) {
            $n = (int) trim($piece);
            if ($n > 0) $out[] = $n;
        }
        return array_values(array_unique($out));
    }
}
