/* ============================================================================
 * async-guard — tiny concurrency helper shared by the frontend.
 *
 * Keeps long-running native operations (offline-cache download/delete, playlist
 * mutations) from being triggered twice at once for the same target, so the UI
 * stays responsive and on-device state can't be corrupted by rapid clicks.
 *
 * Framework-free and dependency-free on purpose:
 *   • Loads as a plain <script> under CSP `script-src 'self'` (browser global
 *     `window.AsyncGuard`).
 *   • `require`-able by `node --test` (CommonJS `module.exports`).
 * ========================================================================== */
(function (root) {
  'use strict';

  /**
   * Run `fn` exclusively for `key`: if an operation for `key` is already
   * in-flight (tracked in `set`), this call is a no-op and resolves to
   * `undefined`. Otherwise `key` is added to `set`, `fn` runs, and `key` is
   * removed once it settles — on success AND on failure — so a failed
   * operation can be retried.
   *
   * @param {Set<string>} set  in-flight key registry (caller owns it)
   * @param {string}      key  identifies the target being operated on
   * @param {() => (any|Promise<any>)} fn  the work to run exclusively
   * @returns {Promise<any>} fn's resolved value, or undefined if skipped
   */
  async function runExclusive(set, key, fn) {
    if (set.has(key)) return undefined;
    set.add(key);
    try {
      return await fn();
    } finally {
      set.delete(key);
    }
  }

  const AsyncGuard = { runExclusive };

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = AsyncGuard;
  } else {
    root.AsyncGuard = AsyncGuard;
  }
})(typeof globalThis !== 'undefined' ? globalThis : this);
