<?php
/**
 * Atomic-ish rate limiting for REST and trigger deduplication.
 *
 * @package CartCue
 */

namespace CartCue;

defined( 'ABSPATH' ) || exit;

final class RateLimiter {

	private const CACHE_GROUP = 'cartcue_rl';

	/**
	 * Increment a counter and return whether the request is within the limit.
	 *
	 * @param string $key             Unique bucket key.
	 * @param int    $limit           Max events allowed in the window.
	 * @param int    $window_seconds  Window length in seconds.
	 */
	public static function allow( string $key, int $limit, int $window_seconds ): bool {
		if ( $limit <= 0 || $window_seconds <= 0 ) {
			return true;
		}

		$cache_key = 'cartcue_rl_' . md5( $key );

		if ( function_exists( 'wp_cache_incr' ) ) {
			$count = wp_cache_get( $cache_key, self::CACHE_GROUP );
			if ( false === $count ) {
				wp_cache_set( $cache_key, 1, self::CACHE_GROUP, $window_seconds );
				return true;
			}
			$next = wp_cache_incr( $cache_key, 1, self::CACHE_GROUP );
			if ( false === $next ) {
				wp_cache_set( $cache_key, 1, self::CACHE_GROUP, $window_seconds );
				return true;
			}
			return $next <= $limit;
		}

		return self::allow_with_transient( $cache_key, $limit, $window_seconds );
	}

	/**
	 * Set a short-lived flag (dedup window). Returns false if the key already exists.
	 *
	 * @param string $key             Unique bucket key.
	 * @param int    $window_seconds  TTL in seconds.
	 */
	public static function claim_once( string $key, int $window_seconds ): bool {
		if ( $window_seconds <= 0 ) {
			return true;
		}

		$cache_key = 'cartcue_rl_once_' . md5( $key );

		if ( wp_cache_add( $cache_key, 1, self::CACHE_GROUP, $window_seconds ) ) {
			return true;
		}

		$transient_key = substr( $cache_key, 0, 172 );
		if ( false === get_transient( $transient_key ) ) {
			set_transient( $transient_key, 1, $window_seconds );
			return true;
		}

		return false;
	}

	private static function allow_with_transient( string $cache_key, int $limit, int $window_seconds ): bool {
		$lock_key = $cache_key . '_lock';
		$attempts = 0;
		while ( ! wp_cache_add( $lock_key, 1, self::CACHE_GROUP, 5 ) && $attempts < 20 ) {
			usleep( 5000 );
			++$attempts;
		}

		$transient_key = substr( $cache_key, 0, 172 );
		$count         = (int) get_transient( $transient_key );
		if ( $count >= $limit ) {
			wp_cache_delete( $lock_key, self::CACHE_GROUP );
			return false;
		}

		set_transient( $transient_key, $count + 1, $window_seconds );
		wp_cache_delete( $lock_key, self::CACHE_GROUP );
		return ( $count + 1 ) <= $limit;
	}
}
