/**
 * @file Per-lane serialization lock. Ensures that only one async operation runs
 * on a lane at a time, preventing concurrent git checkout races and other
 * worktree collisions. Implemented as a chain of promises keyed by lane ID;
 * acquiring a lock waits for the previous holder to settle (success or throw),
 * then runs the new work, and releases for the next waiter.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const chains = new Map();

/**
 * Acquire the per-lane lock, run fn, and release.
 * The lock serialises work per lane_id: the next waiter runs only after the
 * previous holder settles (success or error). If fn throws, the exception
 * propagates to the caller; the chain is *not* poisoned — the next waiter
 * still gets a fresh attempt.
 *
 * @param {number|string} id - Lane ID, converted to string for deduplication.
 * @param {() => Promise<T>} fn - Async function to run while holding the lock.
 * @returns {Promise<T>} The result of fn, or its thrown error.
 */
function withLaneLock(id, fn) {
  const key = String(id);
  const prev = chains.get(key) || Promise.resolve();
  const run = prev.then(fn, fn); // run regardless of how the previous holder settled
  // Keep the chain alive but never let a rejection poison the next waiter.
  const settled = run.then(
    () => {},
    () => {}
  );
  chains.set(key, settled);
  // Clean up the chain entry when it settles to prevent unbounded map growth.
  settled.then(() => {
    if (chains.get(key) === settled) chains.delete(key);
  });
  return run;
}

module.exports = { withLaneLock };
