"""Shared limit on expensive calls: at most n in flight across every machine on one key.
Python 3.8+, standard library only. Set CASCADE_API_KEY (and CASCADE_API for a local server).

Cells slot/<pool>/0..n-1. Each grant and each renewal is a metered decision; the first
hold a caller gets on each busy cell is metered, repeat polls of it are free until its
holder changes.
"""
import random, threading, time

from once_only import cascade, keep_held


def with_slot(pool, n, agent, fn, ttl_ms=30000, poll_s=0.2, max_wait_s=None):
    """fn(lost_event, slot) -> result, run while holding one of n slots."""
    start = random.randrange(n)
    t0 = time.monotonic()
    while True:
        for i in range(n):
            slot = (start + i) % n
            cell = f"slot/{pool}/{slot}"
            if not cascade("/claim", {"cell": cell, "agent": agent, "ttl_ms": ttl_ms}).get("ok"):
                continue
            lost = threading.Event()
            stop = keep_held(cell, agent, ttl_ms, lost)
            try:
                return fn(lost, slot)
            finally:
                stop()
                try:
                    cascade("/release", {"cell": cell, "agent": agent})
                except Exception:
                    pass
        if max_wait_s is not None and time.monotonic() - t0 > max_wait_s:
            raise TimeoutError(f"no free slot in {pool}")
        time.sleep(poll_s + random.random() * poll_s)
