/**
 * Wait for a provider's `synced` event with a timeout.
 *
 * Mirrors `@abraca/dabra`'s `DocUtils.waitForSync`: the `isSynced` short-circuit
 * is load-bearing. `synced` is emitted once per sync transition (and deferred a
 * microtask), so a provider that has ALREADY synced — e.g. the cached child
 * provider that `loadChild` returns on a repeat read/write of the same doc —
 * will never re-emit. Without this guard those callers block for the full
 * `timeoutMs` ("works once, then every later op on that doc times out at 15s").
 */
export function waitForSync(
  provider: {
    isSynced?: boolean
    on(event: string, cb: () => void): void
    off(event: string, cb: () => void): void
  },
  timeoutMs = 15000
): Promise<void> {
  // Already synced — resolve immediately (no event will fire again).
  if (provider.isSynced) return Promise.resolve()

  return new Promise<void>((resolve, reject) => {
    const timer = setTimeout(() => {
      provider.off('synced', handler)
      reject(new Error(`Sync timed out after ${timeoutMs}ms`))
    }, timeoutMs)

    function handler() {
      clearTimeout(timer)
      provider.off('synced', handler)
      resolve()
    }

    provider.on('synced', handler)
  })
}

/**
 * Wraps a promise with a timeout.
 */
export function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const timer = setTimeout(
      () => reject(new Error(message ?? `Operation timed out after ${timeoutMs}ms`)),
      timeoutMs
    )
    promise.then(
      (val) => { clearTimeout(timer); resolve(val) },
      (err) => { clearTimeout(timer); reject(err) }
    )
  })
}
