<?php
declare(strict_types=1);

namespace Celena\Module\News;

use Celena\Core\Database\Connection;

/**
 * Источник для блок-тега [news ...]. Возвращает список новостей по фильтрам:
 *   - category="slug" или category="slug1,slug2" или category="3" (id) — фильтр по категории
 *   - limit="N" — сколько вернуть (по умолчанию 10, максимум 100)
 *   - offset="N" — пропустить (для пагинации в отдельных подключениях)
 *   - sort="published_at|views|comments_count|id|title" — поле сортировки
 *   - order="asc|desc"
 *   - status="published|draft|all" — по умолчанию published
 *   - ids="1,5,7" — точечный список (перебивает category/sort)
 *
 * Для каждой новости в результат добавляется готовое поле `url` (=/news/{slug}).
 */
final class NewsContentSource
{
    private const ALLOWED_SORT = ['published_at', 'views', 'comments_count', 'id', 'title', 'updated_at'];

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

    /**
     * @param array<string, mixed> $params
     * @return array<int, array<string, mixed>>
     */
    public function fetch(array $params): array
    {
        $q = $this->conn->builder('celena_news');

        // Точечный список ids — приоритетнее всего
        if (!empty($params['ids'])) {
            $ids = $this->parseIds((string) $params['ids']);
            if ($ids === []) return [];
            $q->whereIn('id', $ids);
        } else {
            // Статус
            $status = strtolower((string) ($params['status'] ?? 'published'));
            if ($status !== 'all') {
                $q->where('status', '=', in_array($status, ['published', 'draft'], true) ? $status : 'published');
            }
            // Категория: по slug или id, поддержка списка через запятую
            if (!empty($params['category'])) {
                $catIds = $this->resolveCategoryIds((string) $params['category']);
                if ($catIds === []) return [];
                $q->whereIn('category_id', $catIds);
            }
        }

        // Сортировка
        $sort = (string) ($params['sort'] ?? 'published_at');
        if (!in_array($sort, self::ALLOWED_SORT, true)) $sort = 'published_at';
        $order = strtolower((string) ($params['order'] ?? 'desc')) === 'asc' ? 'asc' : 'desc';
        $q->orderBy($sort, $order);

        // Лимиты
        $limit = (int) ($params['limit'] ?? 10);
        if ($limit < 1) $limit = 10;
        if ($limit > 100) $limit = 100;
        $q->limit($limit);
        $offset = (int) ($params['offset'] ?? 0);
        if ($offset > 0) $q->offset($offset);

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

        // Обогащение полей: url, дата, custom_fields, category — всё батч-запросами,
        // чтобы избежать N+1 при выводе списков новостей в темах.
        $newsIds = array_map(static fn ($r) => (int) $r['id'], $rows);
        $valuesByNews = (new \Celena\Module\CustomFields\FieldRepository($this->conn))
            ->valuesForMany('news', $newsIds);

        // Категории: один запрос по уникальным category_id
        $catIdsNeeded = array_values(array_unique(array_filter(array_map(
            static fn ($r) => (int) ($r['category_id'] ?? 0),
            $rows
        ))));
        $categoryById = [];
        if ($catIdsNeeded !== []) {
            $catRows = $this->conn->builder('celena_categories')
                ->whereIn('id', $catIdsNeeded)
                ->get();
            foreach ($catRows as $c) {
                $categoryById[(int) $c['id']] = [
                    'id'    => (int) $c['id'],
                    'title' => (string) $c['title'],
                    'slug'  => (string) $c['slug'],
                    'url'   => '/category/' . (string) $c['slug'],
                ];
            }
        }

        foreach ($rows as &$r) {
            $r['url']  = '/news/' . (string) ($r['slug'] ?? '');
            $r['date'] = (string) ($r['published_at'] ?? $r['created_at'] ?? '');
            $r['fields'] = $valuesByNews[(int) $r['id']] ?? [];
            $cid = (int) ($r['category_id'] ?? 0);
            $r['category'] = $cid > 0 && isset($categoryById[$cid])
                ? $categoryById[$cid]
                : ['id' => 0, 'title' => '', 'slug' => '', 'url' => ''];
        }
        unset($r);
        return $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));
    }

    /**
     * Принимает строку «slug1,slug2» (slug или id), возвращает список id категорий.
     * @return int[]
     */
    private function resolveCategoryIds(string $raw): array
    {
        $tokens = array_filter(array_map('trim', explode(',', $raw)), fn ($t) => $t !== '');
        if ($tokens === []) return [];
        $idsByNum = [];
        $slugs = [];
        foreach ($tokens as $t) {
            if (ctype_digit($t)) $idsByNum[] = (int) $t;
            else $slugs[] = $t;
        }
        $found = $idsByNum;
        if ($slugs !== []) {
            $rows = $this->conn->builder('celena_categories')->whereIn('slug', $slugs)->get();
            foreach ($rows as $r) $found[] = (int) $r['id'];
        }
        return array_values(array_unique($found));
    }
}
