function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
export async function mapBounded(items, fn, opts) {
    const concurrency = Math.max(1, Math.floor(opts.concurrency));
    const stagger = Math.max(0, opts.staggerMs ?? 0);
    const results = new Array(items.length);
    if (items.length === 0)
        return results;
    let cursor = 0;
    // Shared gate: the earliest time the next task is allowed to start. Updated
    // atomically (no await between read and write) so parallel workers each claim
    // a distinct slot spaced `stagger` apart.
    let nextStart = 0;
    async function worker() {
        for (;;) {
            const i = cursor++;
            if (i >= items.length)
                return;
            if (stagger > 0) {
                const now = performance.now();
                const wait = Math.max(0, nextStart - now);
                nextStart = Math.max(now, nextStart) + stagger;
                if (wait > 0)
                    await delay(wait);
            }
            results[i] = await fn(items[i], i);
        }
    }
    const workers = Math.min(concurrency, items.length);
    await Promise.all(Array.from({ length: workers }, () => worker()));
    return results;
}
