/**
 * parallel-emit.ts — W0056 prototype A (client-side parallelism).
 *
 * Bounded-concurrency helper for fanning out artifact emits. The baseline
 * measurement (see `docs/design-docs/artifact-upload-throughput.md`) shows
 * producer loops call `await writer.emit(...)` serially, and per-artifact
 * wall clock is dominated by GCS response latency. A simple `p-limit(N)`
 * turns that into a batched-parallel flow against the existing writers.
 *
 * Gated on `AILF_PARALLEL_UPLOAD`:
 *   - unset → use the per-writer default set at composition time.
 *   - "0" → forced serial (override when default-on is undesirable).
 *   - "1" → parallel, default concurrency 8.
 *   - "<N>" (N > 1 integer) → parallel with concurrency N.
 *
 * The per-writer default is set by the composition root via
 * `setDefaultUploadConcurrency`. Writers with measured safe parallelism
 * (GCS direct) set 8; writers still on serial (API Gateway, until the
 * batching rollout completes) leave it at the module default of 1.
 */
const DEFAULT_CONCURRENCY = 8;
let moduleDefault = 1;
/**
 * Set the default concurrency used when `AILF_PARALLEL_UPLOAD` is unset.
 * Composition root calls this once per run based on the selected remote
 * writer. Tests reset by passing 1.
 */
export function setDefaultUploadConcurrency(n) {
    moduleDefault = n >= 1 ? n : 1;
}
/** Exposed for tests — returns the current module default. */
export function getDefaultUploadConcurrency() {
    return moduleDefault;
}
/**
 * Resolve the configured concurrency. Returns 1 (serial) when parallelism is
 * explicitly disabled or the env value is invalid; otherwise returns the
 * per-writer module default when `AILF_PARALLEL_UPLOAD` is unset.
 */
export function resolveUploadConcurrency() {
    const raw = process.env.AILF_PARALLEL_UPLOAD ?? "";
    if (raw === "0")
        return 1;
    if (raw === "")
        return moduleDefault;
    if (raw === "1")
        return DEFAULT_CONCURRENCY;
    const parsed = Number.parseInt(raw, 10);
    if (Number.isFinite(parsed) && parsed > 1)
        return parsed;
    return moduleDefault;
}
/**
 * Run `fn` against every item with at most `concurrency` active at once.
 * Preserves input order in the result array. Rejections propagate — callers
 * with non-blocking semantics should catch inside `fn`.
 *
 * When `concurrency <= 1`, runs strictly serially (drop-in equivalent of a
 * `for … await` loop).
 */
export async function parallelMap(items, concurrency, fn) {
    if (items.length === 0)
        return [];
    if (concurrency <= 1) {
        const out = [];
        for (let i = 0; i < items.length; i++) {
            out.push(await fn(items[i], i));
        }
        return out;
    }
    const results = new Array(items.length);
    let cursor = 0;
    async function worker() {
        while (true) {
            const i = cursor++;
            if (i >= items.length)
                return;
            results[i] = await fn(items[i], i);
        }
    }
    const width = Math.min(concurrency, items.length);
    await Promise.all(Array.from({ length: width }, () => worker()));
    return results;
}
