/**
 * Async Utilities Module
 *
 * Production-ready async patterns for rate limiting, throttling,
 * concurrency control, and caching with TTL.
 *
 * Principal Investigator: Bo Shang
 * Framework: agi-cli
 */
// ============================================================================
// Rate Limiter (Token Bucket)
// ============================================================================
export class RateLimiter {
    tokens;
    lastRefill;
    maxTokens;
    refillRate;
    constructor(config) {
        this.maxTokens = config.maxRequests;
        this.tokens = config.maxRequests;
        this.refillRate = config.maxRequests / config.windowMs;
        this.lastRefill = Date.now();
    }
    refill() {
        const now = Date.now();
        const elapsed = now - this.lastRefill;
        const tokensToAdd = elapsed * this.refillRate;
        this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
        this.lastRefill = now;
    }
    async acquire() {
        this.refill();
        if (this.tokens >= 1) {
            this.tokens -= 1;
            return;
        }
        // Wait for a token to become available
        const waitTime = (1 - this.tokens) / this.refillRate;
        await sleep(waitTime);
        this.refill();
        this.tokens -= 1;
    }
    tryAcquire() {
        this.refill();
        if (this.tokens >= 1) {
            this.tokens -= 1;
            return true;
        }
        return false;
    }
    get availableTokens() {
        this.refill();
        return Math.floor(this.tokens);
    }
}
// ============================================================================
// Throttle & Debounce
// ============================================================================
export function throttle(fn, config) {
    let lastCall = 0;
    let lastResult;
    let pendingCall = null;
    let pendingArgs = null;
    const { intervalMs, leading = true, trailing = true } = config;
    return function throttled(...args) {
        const now = Date.now();
        const elapsed = now - lastCall;
        if (elapsed >= intervalMs) {
            // Enough time has passed
            if (leading || lastCall !== 0) {
                lastCall = now;
                lastResult = fn(...args);
                return lastResult;
            }
        }
        // Store args for potential trailing call
        if (trailing) {
            pendingArgs = args;
            if (!pendingCall) {
                pendingCall = setTimeout(() => {
                    if (pendingArgs) {
                        lastCall = Date.now();
                        lastResult = fn(...pendingArgs);
                        pendingArgs = null;
                    }
                    pendingCall = null;
                }, intervalMs - elapsed);
            }
        }
        return lastResult;
    };
}
export function debounce(fn, delayMs) {
    let timeoutId = null;
    return function debounced(...args) {
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
            fn(...args);
            timeoutId = null;
        }, delayMs);
    };
}
// ============================================================================
// TTL Cache
// ============================================================================
export class TTLCache {
    cache = new Map();
    ttlMs;
    maxSize;
    constructor(config) {
        this.ttlMs = config.ttlMs;
        this.maxSize = config.maxSize ?? Infinity;
    }
    get(key) {
        const entry = this.cache.get(key);
        if (!entry)
            return undefined;
        if (Date.now() > entry.expiresAt) {
            this.cache.delete(key);
            return undefined;
        }
        return entry.value;
    }
    set(key, value, ttlMs) {
        // Evict oldest if at capacity
        if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
            const oldestKey = this.cache.keys().next().value;
            if (oldestKey !== undefined) {
                this.cache.delete(oldestKey);
            }
        }
        this.cache.set(key, {
            value,
            expiresAt: Date.now() + (ttlMs ?? this.ttlMs),
        });
    }
    has(key) {
        return this.get(key) !== undefined;
    }
    delete(key) {
        return this.cache.delete(key);
    }
    clear() {
        this.cache.clear();
    }
    get size() {
        this.prune();
        return this.cache.size;
    }
    prune() {
        const now = Date.now();
        const keysToDelete = [];
        this.cache.forEach((entry, key) => {
            if (now > entry.expiresAt) {
                keysToDelete.push(key);
            }
        });
        keysToDelete.forEach(key => this.cache.delete(key));
    }
}
// ============================================================================
// Memoize with TTL
// ============================================================================
export function memoize(fn, config) {
    const cache = new TTLCache(config);
    const keyFn = config.keyFn ?? ((...args) => JSON.stringify(args));
    return function memoized(...args) {
        const key = keyFn(...args);
        const cached = cache.get(key);
        if (cached !== undefined) {
            return cached;
        }
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
}
// ============================================================================
// Concurrency Pool
// ============================================================================
export class ConcurrencyPool {
    running = 0;
    queue = [];
    maxConcurrent;
    timeout;
    constructor(config) {
        this.maxConcurrent = config.maxConcurrent;
        this.timeout = config.timeout;
    }
    async run(fn) {
        await this.acquire();
        try {
            if (this.timeout) {
                return await withTimeout(fn(), this.timeout);
            }
            return await fn();
        }
        finally {
            this.release();
        }
    }
    async acquire() {
        if (this.running < this.maxConcurrent) {
            this.running++;
            return;
        }
        return new Promise((resolve, reject) => {
            this.queue.push({ resolve, reject });
        });
    }
    release() {
        this.running--;
        const next = this.queue.shift();
        if (next) {
            this.running++;
            next.resolve();
        }
    }
    get pending() {
        return this.queue.length;
    }
    get active() {
        return this.running;
    }
}
// ============================================================================
// Parallel Map with Concurrency Limit
// ============================================================================
export async function parallelMap(items, fn, concurrency = 5) {
    const pool = new ConcurrencyPool({ maxConcurrent: concurrency });
    return Promise.all(items.map((item, index) => pool.run(() => fn(item, index))));
}
// ============================================================================
// Utility Functions
// ============================================================================
export function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
export async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
    let timeoutId;
    const timeoutPromise = new Promise((_, reject) => {
        timeoutId = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
    });
    try {
        return await Promise.race([promise, timeoutPromise]);
    }
    finally {
        clearTimeout(timeoutId);
    }
}
export async function retry(fn, config = {}) {
    const { maxRetries = 3, baseDelayMs = 1000, maxDelayMs = 30000, backoffMultiplier = 2, shouldRetry = () => true, } = config;
    let lastError;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        }
        catch (error) {
            lastError = error;
            if (attempt === maxRetries || !shouldRetry(error)) {
                throw error;
            }
            const delay = Math.min(baseDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
            await sleep(delay);
        }
    }
    throw lastError;
}
// ============================================================================
// Exports
// ============================================================================
export default {
    RateLimiter,
    TTLCache,
    ConcurrencyPool,
    throttle,
    debounce,
    memoize,
    parallelMap,
    sleep,
    withTimeout,
    retry,
};
//# sourceMappingURL=asyncUtils.js.map