<?php
declare(strict_types=1);

namespace Celena\Module\Search;

use Celena\Core\Database\Connection;
use Celena\Core\Http\Pagination;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Plugin\Hook;
use Celena\Core\Security\Filter;
use Celena\Core\Template\Engine;
use Celena\Module\Settings\Options;

/**
 * Простой поиск по новостям и страницам: LOWER(title) LIKE ? OR LOWER(body) LIKE ?.
 * Работает и на PG, и на MySQL.
 */
final class SearchController
{
    public function __construct(
        private readonly Engine $engine,
        private readonly Connection $conn,
        private readonly Options $options,
    ) {
    }

    public function search(Request $request): Response
    {
        $q = Filter::string($request->query('q', ''), 200);
        $items = [];
        $total = 0;
        $page = Filter::int($request->query('page', 1), 1);
        $perPage = 20;

        if (mb_strlen($q, 'UTF-8') >= 2) {
            $pattern = '%' . mb_strtolower($q, 'UTF-8') . '%';
            $sqlNews = 'SELECT id, slug, title, short AS snippet, published_at AS date, '
                . "'news' AS kind FROM " . $this->conn->table_q('celena_news')
                . " WHERE status = 'published' AND ("
                . 'LOWER(' . $this->conn->quoteId('title') . ') LIKE ? OR '
                . 'LOWER(' . $this->conn->quoteId('body')  . ') LIKE ?)';
            $sqlPages = 'SELECT id, slug, title, NULL AS snippet, updated_at AS date, '
                . "'page' AS kind FROM " . $this->conn->table_q('celena_pages')
                . " WHERE status = 'published' AND ("
                . 'LOWER(' . $this->conn->quoteId('title') . ') LIKE ? OR '
                . 'LOWER(' . $this->conn->quoteId('body')  . ') LIKE ?)';
            $sql = '(' . $sqlNews . ') UNION ALL (' . $sqlPages . ') ORDER BY date DESC NULLS LAST';
            // MySQL не поддерживает NULLS LAST — но не ломает выражение фатально,
            // для кроссплатформенности сделаем отдельные запросы и объединим в PHP.
            $driver = $this->conn->driver()->name();
            if ($driver === 'mysql') {
                $sql = '(' . $sqlNews . ') UNION ALL (' . $sqlPages . ') ORDER BY date DESC';
            }
            $rows = $this->conn->run($sql, [$pattern, $pattern, $pattern, $pattern])->fetchAll();
            foreach ($rows as &$it) {
                $it['url'] = $it['kind'] === 'news' ? '/news/' . $it['slug'] : '/p/' . $it['slug'];
                $it['kind_label'] = $it['kind'] === 'news' ? 'Новость' : 'Страница';
            }
            unset($it);

            // Хук для плагинов: каждый может добавить свой источник результатов.
            // Контракт элемента: ['id','slug','title','snippet','date','kind',
            // 'kind_label','url'] + опц. поля плагина (cover, price_fmt и т.д.).
            $rows = (array) Hook::filter('search.sources', $rows, $q);

            // Пересортировка по дате (после слияния источников).
            usort($rows, static function ($a, $b): int {
                $da = !empty($a['date']) ? strtotime((string) $a['date']) : 0;
                $db = !empty($b['date']) ? strtotime((string) $b['date']) : 0;
                return ($db ?: 0) <=> ($da ?: 0);
            });

            $total = count($rows);
            $items = array_slice($rows, ($page - 1) * $perPage, $perPage);
        }

        $p = new Pagination($page, $perPage, $total);
        $this->engine->setTheme((string) $this->options->get('theme', 'default'));
        $html = $this->engine->render('search', [
            'site' => ['name' => (string) $this->options->get('site_name', '')],
            'page' => ['title' => $q !== '' ? 'Поиск: ' . $q : 'Поиск', 'seo_title' => 'Поиск', 'seo_description' => '', 'og_image' => ''],
            'query' => $q,
            'results' => $items,
            'total' => $total,
            'pagination' => $p->toArray('/search?q=' . rawurlencode($q) . '&page=:page'),
        ]);
        return Response::html($html);
    }
}
