<?php

declare(strict_types=1);

namespace PpPhp\Core\Search;

/**
 * Dialect-aware full-text search clause builder (platform feature A12).
 *
 * Builds a WHERE + ORDER BY (with bound params) that ranks records by relevance
 * across one or more text columns — used by a search page and by "similar
 * questions" on ask. It must work on all supported engines, so:
 *
 *   • PostgreSQL / CockroachDB → `to_tsvector … @@ websearch_to_tsquery` ranked
 *     by `ts_rank` (works without a pre-built index; add a GIN index for speed).
 *   • Everyone else (MySQL/MariaDB/TiDB, SQLite, SQL Server, Oracle) → a portable
 *     LIKE search: every term must match at least one column (AND of ORs), ranked
 *     by how many columns contain the whole phrase. Needs no full-text index.
 *
 * Pure and side-effect-free → unit-testable per dialect. Column names must be
 * validated identifiers supplied by the caller (they are interpolated); the
 * search text is always bound as a parameter.
 *
 * Caller usage:
 *   $c = FullText::clause($dbType, ['title','body'], $q);
 *   $sql = "SELECT id FROM t" . ($c['where'] !== '' ? " WHERE {$c['where']}" : '')
 *        . ($c['order'] !== '' ? " ORDER BY {$c['order']}" : '');
 *   $rows = dbAll($sql, array_merge($c['params'], $c['orderParams']));
 *
 * @return array{where:string, order:string, params:array<int,string>, orderParams:array<int,string>}
 */
final class FullText
{
    private const EMPTY = ['where' => '', 'order' => '', 'params' => [], 'orderParams' => []];

    /**
     * @param array<int,string> $columns validated identifiers
     */
    public static function clause(string $type, array $columns, string $query): array
    {
        $q = trim($query);
        $columns = array_values(array_filter($columns, static fn ($c): bool => $c !== ''));
        if ($q === '' || $columns === []) {
            return self::EMPTY;
        }
        $type = strtolower($type);

        if (in_array($type, ['pgsql', 'postgres', 'postgresql', 'cockroachdb', 'cockroach', 'crdb'], true)) {
            $doc = implode(" || ' ' || ", array_map(static fn (string $c): string => "coalesce({$c}::text,'')", $columns));
            $vec = "to_tsvector('simple', {$doc})";
            return [
                'where'       => "{$vec} @@ websearch_to_tsquery('simple', ?)",
                'order'       => "ts_rank({$vec}, websearch_to_tsquery('simple', ?)) DESC",
                'params'      => [$q],
                'orderParams' => [$q],
            ];
        }

        // Portable LIKE fallback.
        $terms = array_slice(array_values(array_filter(preg_split('/\s+/', $q) ?: [], static fn ($t): bool => $t !== '')), 0, 6);
        if ($terms === []) {
            return self::EMPTY;
        }
        $where = [];
        $params = [];
        foreach ($terms as $term) {
            $ors = [];
            foreach ($columns as $c) {
                $ors[] = "{$c} LIKE ?";
                $params[] = '%' . $term . '%';
            }
            $where[] = '(' . implode(' OR ', $ors) . ')';
        }
        $rank = [];
        $rankParams = [];
        foreach ($columns as $c) {
            $rank[] = "(CASE WHEN {$c} LIKE ? THEN 1 ELSE 0 END)";
            $rankParams[] = '%' . $q . '%';
        }
        return [
            'where'       => implode(' AND ', $where),
            'order'       => '(' . implode(' + ', $rank) . ') DESC',
            'params'      => $params,
            'orderParams' => $rankParams,
        ];
    }
}
