-- Fixed window quota grant script (the fairness partial is prepended by RuleExecutor).
-- KEYS[1]: quota key (counter; the key name includes the window boundary)
-- KEYS[2]: waiters key (sorted set of waiters, see partials/fairness.lua) — deliberately does
--          NOT include the window boundary, so the queue survives window rollovers.
-- ARGV[1]: limit
-- ARGV[2]: window TTL (seconds)
-- ARGV[3]: waiterId ('' for a fresh, non-waiting caller)
-- ARGV[4]: maxWait (ms) of the caller
-- ARGV[5]: mayWait (1 = register as waiter on denial, 0 = never)
-- Returns: {granted (0/1), currentCount, retryIn (ms until the window resets, -1 = no hint)}

local limit = tonumber(ARGV[1])
local now = now_ms()

local currentCount = tonumber(redis.call('GET', KEYS[1]) or '0')
local free = limit - currentCount
local ahead = waiters_ahead(KEYS[2], ARGV[3], now)

if ahead < free then
    currentCount = redis.call('INCR', KEYS[1])
    if currentCount == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[2])
    end
    deregister_waiter(KEYS[2], ARGV[3], ARGV[4])
    return { 1, currentCount, -1 }
end

if tonumber(ARGV[5]) == 1 then
    register_waiter(KEYS[2], ARGV[3], ARGV[4])
end

-- The window boundary frees all capacity at once, so PTTL of the counter key is the exact
-- retry hint for every waiter regardless of rank; the fairness gate re-serializes the race
-- at the boundary (the oldest `limit` waiters win, the rest get a fresh hint).
local ttl = redis.call('PTTL', KEYS[1])
return { 0, currentCount, ttl > 0 and ttl or -1 }
