<?php

declare(strict_types=1);

namespace CliqThemes\StoreLocator\Support;

defined('ABSPATH') || exit;

use WP_REST_Response;

/**
 * Per-caller rate limiting for the endpoints that need no authentication.
 *
 * Those endpoints are reachable by anyone, so each keeps its own counter — a
 * visitor searching hard must not use up the budget for submitting a form. The
 * caller is identified by a salted hash of its address, never the address
 * itself, so nothing here is personal data at rest.
 *
 * A caller whose address cannot be read is not limited: refusing an entire
 * class of request because the server did not report REMOTE_ADDR would take the
 * locator down behind some proxies.
 */
final class RequestThrottle
{
    /**
     * Count this request against a bucket.
     *
     * @param string $bucket what is being limited — 'search', 'events', 'leads'
     * @param int    $max    requests allowed within the window
     * @param int    $window seconds the allowance covers
     *
     * @return WP_REST_Response|null a 429 to return as-is, or null to continue
     */
    public static function exceeded(string $bucket, int $max, int $window): ?WP_REST_Response
    {
        $caller = self::caller();
        if ($caller === null) {
            return null;
        }

        $key = 'cliq_sl_rl_' . $bucket . '_' . substr($caller, 0, 32);
        $count = (int) get_transient($key);
        if ($count >= $max) {
            return new WP_REST_Response(
                ['message' => __('Too many requests. Please try again shortly.', 'cliq-store-locator')],
                429
            );
        }

        set_transient($key, $count + 1, $window);

        return null;
    }

    /** A salted hash of the caller's address, or null when there isn't one. */
    public static function caller(): ?string
    {
        $ip = isset($_SERVER['REMOTE_ADDR'])
            ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))
            : '';

        return $ip === '' ? null : hash('sha256', $ip . wp_salt());
    }
}
