/**
 * Interlock between the yt-dlp self-update (which REWRITES the managed yt-dlp.exe)
 * and the download / terminal spawns (which EXECUTE it). On Windows, spawning the
 * exe while `--update-to` is swapping it in place races into a sharing violation or
 * launches a half-written binary (L139). This serialises the two:
 *
 *  - each live yt-dlp process registers via acquireSpawn()/releaseSpawn();
 *  - the updater calls beginYtdlpUpdate(), which REFUSES (returns false) when any
 *    spawn is live — the update is best-effort and daily-throttled, so it simply
 *    skips this cycle rather than rewrite the exe out from under a running process;
 *  - a spawn that arrives while an update holds the lock is HELD (not failed): the
 *    IPC layer awaits whenYtdlpUpdateSettled() before spawning, so the download just
 *    waits out the brief rewrite instead of erroring. isYtdlpUpdating() is the
 *    synchronous backstop for any caller that doesn't await.
 *
 * The main process is single-threaded, so these plain counters/flags need no real
 * locking: a check-then-act sequence (e.g. await whenYtdlpUpdateSettled() → spawn →
 * acquireSpawn) runs with no await between the settle and the spawn, so no update can
 * interleave. Pure (no electron/node chain) so it's unit-testable in isolation.
 */
let updating = false
let liveSpawns = 0
// Resolved by endYtdlpUpdate(); recreated per update so a held spawn wakes exactly
// when the current rewrite finishes. Only meaningful while `updating` is true.
let settle: (() => void) | null = null
let settledPromise: Promise<void> = Promise.resolve()

/** True while an update is rewriting yt-dlp.exe — synchronous spawn backstop. */
export function isYtdlpUpdating(): boolean {
  return updating
}

/** Register a live yt-dlp process — call immediately after a successful spawn. */
export function acquireSpawn(): void {
  liveSpawns++
}

/** Deregister a yt-dlp process on settle (close/error). Never drops below zero. */
export function releaseSpawn(): void {
  if (liveSpawns > 0) liveSpawns--
}

/** How many yt-dlp processes are currently live (test seam / diagnostics). */
export function liveSpawnCount(): number {
  return liveSpawns
}

/**
 * Resolves once no update is in progress — used to HOLD a spawn until the exe swap
 * finishes, rather than refuse it. Returns an already-resolved promise when idle, so
 * the common (non-updating) path adds no delay. Bounded by the caller's update
 * timeout: endYtdlpUpdate() always runs (finally), so this can't hang forever.
 */
export function whenYtdlpUpdateSettled(): Promise<void> {
  return updating ? settledPromise : Promise.resolve()
}

/**
 * Enter the update critical section. Returns false — and does NOT take the lock — if
 * any spawn is live OR another update already holds the lock, in which case the
 * caller must not update. The updating check matters: a second concurrent begin
 * (startup auto-update racing a manual "Update now") would otherwise replace the
 * settle promise, letting the FIRST update's end() release spawns held by the
 * second one mid-rewrite. On true the caller owns the lock until endYtdlpUpdate().
 */
export function beginYtdlpUpdate(): boolean {
  if (updating || liveSpawns > 0) return false
  updating = true
  settledPromise = new Promise((resolve) => {
    settle = resolve
  })
  return true
}

/** Leave the update critical section, waking any spawn held on whenYtdlpUpdateSettled(). */
export function endYtdlpUpdate(): void {
  updating = false
  settle?.()
  settle = null
}
