/**
 * Per-(list, actor) coalescing debounce for list-change push notifications (D-01).
 *
 * A grocery burst — many rapid saves — collapses into a single push dispatch
 * instead of one push per change. The window is sliding: each new call within
 * the window resets the timer, so the dispatch fires once the member pauses.
 *
 * Exports: coalesceListPush
 */

type DispatchFn = (listId: number, actorId: number, count: number) => Promise<void>;

type PendingEntry = {
  count: number;
  timer: ReturnType<typeof setTimeout>;
};

// Module-level singleton — keyed by `${listId}:${actorId}`.
// Entries are self-deleting: deleted when the timer fires.
const pending = new Map<string, PendingEntry>();

/**
 * Coalesce list-change push notifications for a single (list, actor) pair.
 *
 * @param listId    - The list that changed.
 * @param actorId   - The member who made the change (userId). Used as the
 *                    excludeUserId argument to dispatch so the actor is never
 *                    notified of their own changes (D-03).
 * @param dispatch  - Called once per coalesce window with (listId, actorId, count).
 *                    The caller fans out to all subscribers except actorId.
 * @param windowMs  - Sliding debounce window in milliseconds (default 45 s).
 */
export function coalesceListPush(
  listId: number,
  actorId: number,
  dispatch: DispatchFn,
  windowMs = 45_000,
): void {
  const key = `${listId}:${actorId}`;
  const existing = pending.get(key);

  if (existing) {
    // Extend the window on every new call within the burst (sliding debounce).
    clearTimeout(existing.timer);
    existing.count++;
    existing.timer = setTimeout(() => fire(key, listId, actorId, dispatch), windowMs);
  } else {
    // First call in a new burst — start a fresh entry.
    const timer = setTimeout(() => fire(key, listId, actorId, dispatch), windowMs);
    pending.set(key, { count: 1, timer });
  }
}

function fire(key: string, listId: number, actorId: number, dispatch: DispatchFn): void {
  const entry = pending.get(key);
  if (!entry) return;
  const count = entry.count;
  pending.delete(key);
  dispatch(listId, actorId, count).catch((err: unknown) => {
    console.error(
      `[pushCoalescer] dispatch failed for list ${listId}:`,
      err instanceof Error ? err.message : String(err),
    );
  });
}
