// 350 ms per-list add-to-list debouncer for the List Organizer (Surface E).
//
// The organizer's whip-drag flow can drop five accounts onto one list inside a
// few hundred ms. We want the optimistic UI to react on each drop (handled by
// the caller's mutation), but we want a *single* network POST carrying
// `account_ids[5]` rather than five separate POSTs. This registry coalesces
// drops per list: each drop resets a 350 ms timer; when it finally fires, one
// `fire(ids)` call goes out with every account accumulated in the window.
//
// Mirrors FediverseLists' `pendingAdditionsByListId` + `addToListBatchDelay`
// (350_000_000 ns). The optimistic cache update is *not* debounced — it runs
// synchronously at the drop site so the row goes green instantly; only the
// authoritative network write is coalesced here.

type FireFn = (accountIds: string[]) => Promise<unknown>

type Pending = {
  accountIds: Set<string>
  timer: ReturnType<typeof setTimeout>
  fire: FireFn
}

const FLUSH_MS = 350
const pending = new Map<string, Pending>()

function flush(listId: string): void {
  const entry = pending.get(listId)
  if (!entry) return
  pending.delete(listId)
  const ids = Array.from(entry.accountIds)
  if (ids.length === 0) return
  // Fire-and-forget: the optimistic cache already reflects the drop, and the
  // mutation's own onError rolls back per-id if the POST fails.
  void entry.fire(ids)
}

/**
 * Queue `accountId` to be added to `listId`. Repeated calls within the flush
 * window accumulate into one batch and reset the timer. `fire` is captured
 * fresh on every call (so the latest mutation closure wins) — it's invoked
 * once per batch with the merged id list.
 */
export function enqueueAddToList(listId: string, accountId: string, fire: FireFn): void {
  const entry = pending.get(listId)
  if (entry) {
    entry.accountIds.add(accountId)
    entry.fire = fire
    clearTimeout(entry.timer)
    entry.timer = setTimeout(() => flush(listId), FLUSH_MS)
    return
  }
  pending.set(listId, {
    accountIds: new Set([accountId]),
    fire,
    timer: setTimeout(() => flush(listId), FLUSH_MS),
  })
}

/**
 * Flush every pending batch immediately. The organizer calls this on unmount
 * so a drop made moments before leaving the screen is never silently dropped.
 */
export function flushPendingAddToList(): void {
  for (const [listId, entry] of pending) {
    clearTimeout(entry.timer)
    const ids = Array.from(entry.accountIds)
    pending.delete(listId)
    if (ids.length > 0) void entry.fire(ids)
  }
}
