/**
 * CW API Concurrency Limiter
 *
 * Limits the number of simultaneous in-flight requests to the ConnectWise
 * API. CW responds significantly slower under high concurrency (observed
 * ~3× slower at 9 concurrent vs 5–6 concurrent), so bounding the
 * parallelism actually reduces total wall-clock time.
 *
 * Implemented as an Axios request interceptor that gates on a simple
 * counting semaphore. When the limit is reached, new requests queue and
 * resolve in FIFO order as earlier requests complete.
 */

import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";

// ---------------------------------------------------------------------------
// Semaphore
// ---------------------------------------------------------------------------

class Semaphore {
  private _current = 0;
  private _queue: (() => void)[] = [];

  constructor(private _max: number) {}

  /** Acquire a slot — resolves immediately if under the limit, else waits. */
  acquire(): Promise<void> {
    if (this._current < this._max) {
      this._current++;
      return Promise.resolve();
    }
    return new Promise<void>((resolve) => {
      this._queue.push(resolve);
    });
  }

  /** Release a slot — wakes the next queued caller, if any. */
  release(): void {
    const next = this._queue.shift();
    if (next) {
      // Hand the slot directly to the next waiter (don't decrement)
      next();
    } else {
      this._current--;
    }
  }
}

// ---------------------------------------------------------------------------
// Interceptor attachment
// ---------------------------------------------------------------------------

/**
 * Attach a concurrency-limiting interceptor to an Axios instance.
 *
 * @param api  - The Axios instance to limit.
 * @param max  - Maximum concurrent in-flight requests (default: 6).
 */
export function attachCwConcurrencyLimiter(api: AxiosInstance, max = 6): void {
  const sem = new Semaphore(max);

  // Request interceptor: wait for a slot before the request fires
  api.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
    await sem.acquire();
    return config;
  });

  // Response interceptor: release the slot on success or failure
  api.interceptors.response.use(
    (response) => {
      sem.release();
      return response;
    },
    (error) => {
      sem.release();
      return Promise.reject(error);
    },
  );
}
