<?php

namespace App\Services;

/**
 * Builds canonical haversine SQL fragments for distance and within-radius
 * filtering. Centralises the float-clamping (GREATEST/LEAST) and the column
 * naming convention used across prediction and station search queries.
 *
 * Assumes the joined/queried table exposes columns `lat` and `lng`.
 */
final class HaversineQuery
{
    private const string DISTANCE_KM_SQL =
        '(6371 * acos(GREATEST(-1.0, LEAST(1.0, '
        .'cos(radians(?)) * cos(radians(lat)) * cos(radians(lng) - radians(?)) '
        .'+ sin(radians(?)) * sin(radians(lat))))))';

    /**
     * Bare distance-in-km expression. Caller adds aliasing or comparison.
     *
     * @return array{0: string, 1: array{float, float, float}}
     */
    public static function distanceKm(float $lat, float $lng): array
    {
        return [self::DISTANCE_KM_SQL, [$lat, $lng, $lat]];
    }

    /**
     * `<= {km}` predicate suitable for whereRaw. The radius is embedded as a
     * numeric literal because PDO + SQLite's whereRaw binds floats as strings
     * by default, which breaks numeric comparison against the haversine
     * expression. The `float` parameter is type-checked and not user input.
     *
     * @return array{0: string, 1: array{float, float, float}}
     */
    public static function withinKm(float $lat, float $lng, float $km): array
    {
        return [self::DISTANCE_KM_SQL.' <= '.sprintf('%F', $km), [$lat, $lng, $lat]];
    }
}
