// collect effects keyed by a string, then run each once on flush. later
// registrations under the same key replace earlier ones (last wins), so a burst
// of callers asking for the same work — the outbox handlers each requesting a
// feed / planned / trail-detail refresh as they drain — collapses to a single
// run when the burst settles, instead of a refetch storm. pure and I/O-free, so
// the runner's coalescing is testable without touching the network
export const createCoalescer = () => {
  const effects = new Map<string, () => void>()
  return {
    schedule: (key: string, effect: () => void): void => {
      effects.set(key, effect)
    },
    // run every scheduled effect once, then clear — a second flush with no new
    // schedules does nothing
    flush: (): void => {
      const pending = [...effects.values()]
      effects.clear()
      for (const run of pending) run()
    },
  }
}
