/**
 * Debounce an async task with coalescing.
 *
 * - Multiple `schedule()` calls within the delay window collapse into one run.
 * - If the task is already running, the next run is queued and executed once after it finishes.
 */
export function createDebouncedAsyncTask(
  task: () => Promise<void>,
  delayMs: number,
): Readonly<{ schedule: () => void; cancel: () => void }> {
  let timer: ReturnType<typeof setTimeout> | null = null
  let running = false
  let queued = false

  async function run(): Promise<void> {
    if (running) {
      queued = true
      return
    }
    running = true
    queued = false
    try {
      await task()
    } finally {
      running = false
      if (queued) schedule()
    }
  }

  function schedule(): void {
    queued = true
    if (timer) clearTimeout(timer)
    timer = setTimeout(run, Math.max(0, delayMs))
  }

  function cancel(): void {
    queued = false
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
  }

  return { schedule, cancel } as const
}
