import * as React from 'react';
import { type ChannelKey, type PeerId } from "./coordinatePreference.mjs";
/**
 * Options for {@link useCoordinated}. See `coordinatePreference` for
 * the underlying semantics; only React-specific behaviors are
 * documented here.
 */
export interface UseCoordinatedOptions<TValue, TPreload> {
  /**
   * Coordination scope. All peers (component instances) that share a
   * `channelKey` participate in the same layout-shift barrier. Pass
   * `null` to opt out of coordination entirely (the hook becomes a
   * plain pass-through of the underlying `[value, setValue]`).
   */
  channelKey: ChannelKey | null;
  /**
   * Stable identifier for *this* peer within the channel. Defaults to
   * a freshly generated id on mount. Override when stable cross-mount
   * identity matters (e.g. for analytics / debugging).
   */
  peerId?: PeerId;
  /**
   * Return `true` when applying this target would visibly shift
   * layout — the peer joins the channel-wide barrier so all such
   * peers commit together. Return `false` for non-disruptive changes
   * — the peer commits lazily on its own self-serial chain. See
   * `coordinatePreference` for the full semantics.
   */
  causesLayoutShift: (target: TValue) => boolean;
  /**
   * Optional async work to run before the barrier commits (for
   * `causesLayoutShift === true`) or before a lazy commit (for
   * `false`). The resolved value is handed to `onCommit`.
   */
  preload?: (target: TValue, signal: AbortSignal) => TPreload | Promise<TPreload>;
  /**
   * Hook fired inside the coordinated commit, before the visible
   * value flips. Useful for installing precomputed payloads into
   * neighboring state. The visible `value` returned from this hook
   * always lags `pendingValue` until coordination settles, so this
   * runs *with* the value flip, not before it.
   *
   * Also fires once on first mount, with the initial preloaded
   * payload, so consumers can install precomputed state on hydration
   * without a separate code path.
   *
   * Under normal conditions `preloaded` is whatever this peer's
   * `preload` resolved to. It may still be `undefined` when:
   *  - the barrier force-resolved at `ultimateTimeoutMs` (a sibling
   *    peer crashed / hung; accompanied by a console warning),
   *  - `preload` threw (logged via `console.error`, treated as a
   *    no-op so the rest of the channel still commits), or
   *  - `preload` explicitly returned `undefined`.
   *
   * Handlers should tolerate the undefined case and fall back to a
   * synchronous render path rather than throwing.
   */
  onCommit?: (target: TValue, preloaded: TPreload | undefined) => void;
  /**
   * See {@link AnnounceOptions.minWaitMs}.
   */
  minWaitMs?: number;
  /**
   * See {@link AnnounceOptions.multiPeerExtraMinWaitMs}.
   */
  multiPeerExtraMinWaitMs?: number;
  /**
   * See {@link AnnounceOptions.lazyMinWaitMs}.
   */
  lazyMinWaitMs?: number;
  /**
   * See {@link AnnounceOptions.gracePeriodMs}.
   */
  gracePeriodMs?: number;
  /**
   * See {@link AnnounceOptions.ultimateTimeoutMs}.
   */
  ultimateTimeoutMs?: number;
  /**
   * Controls whether `isCoordinating` flips *during* the preload
   * or *after* it. `pendingValue` (the user-facing "intent"
   * signal) always flips synchronously regardless of this flag —
   * toolbars and other affordances stay responsive on click.
   *
   * - `false` (default) — defer `isCoordinating` until the
   *   originator's `preload` settles. Use this when the preload
   *   is CPU-bound (parsing, syntax highlighting, layout
   *   measurement, etc.) and the consumer drives a visible
   *   animation off `isCoordinating`. Running the animation
   *   concurrently with the preload would steal main-thread time
   *   from the compositor and produce a janky transition; with
   *   the flip deferred the animation only starts once the heavy
   *   work is done.
   * - `true` — flip `isCoordinating` synchronously on the
   *   originating setter call, so the animation runs in parallel
   *   with the preload. Use this when the preload is I/O-bound
   *   (network fetches, `localStorage` reads, etc.) so the
   *   animation and the I/O roundtrip overlap.
   *
   * The coordinator always yields to the browser (via
   * `scheduler.yield()` when available, otherwise a `setTimeout`
   * macrotask) before invoking `preload`, so even synchronous
   * preloads settle one macrotask after the originating setter
   * call. This lets the intermediate loading state paint before
   * the (potentially CPU-bound) preload monopolizes the main
   * thread. This flag has no effect when `preload` is omitted;
   * the flip is synchronous either way.
   *
   * Only the originator's flip is affected. Sibling peers picked
   * up via `notifySiblings` still observe the receiver flow's
   * synchronous flip, because their `isCoordinating` is driven
   * by the originator's broadcast rather than a local click.
   */
  animateDuringPreload?: boolean;
  /**
   * Scheduling priority for lazy-path commits.
   *
   * - `'idle'` (default) — lazy-path commits are deferred via
   *   `requestIdleCallback` so the browser can yield to in-flight
   *   paints and input. Use when the lazy peer's commit itself is
   *   main-thread heavy (DOM reconciliation of a freshly
   *   transformed tree, etc.).
   * - `'normal'` — the commit lands as soon as the preload
   *   resolves. Use for I/O-bound `preload`s where the commit is
   *   cheap and you want each peer's swap to surface immediately;
   *   otherwise idle scheduling can cluster commits together near
   *   the slowest peer's settle.
   *
   * Has no effect when this peer takes the barrier path — barrier
   * commits are batched synchronously inside the barrier's resolve
   * microtask regardless.
   */
  lazyCommitPriority?: 'idle' | 'normal';
}
export interface UseCoordinatedExtras<TValue> {
  /**
   * The most recently announced target value. Equals the committed
   * `value` when no coordination is in flight. Useful for showing an
   * optimistic preview UI (toolbar selection, etc.) that should react
   * instantly to a click even when the visible content has a pending
   * barrier.
   */
  pendingValue: TValue;
  /**
   * `true` while a coordination is in flight that this peer can drive
   * an animation off of. For receivers and barrier joiners that lands
   * synchronously with the announce. For originators the flip is
   * controlled by `animateDuringPreload`: with the default
   * `animateDuringPreload: false`, `isCoordinating` stays `false`
   * until the originator's `preload` settles, then flips `true` for
   * the remainder of the barrier; with `animateDuringPreload: true`,
   * it flips synchronously on the originating setter call so the
   * animation overlaps with an I/O-bound preload. Use
   * {@link pendingValue} to drive intent-based affordances (toolbar
   * selection, etc.) that should react instantly to a click
   * regardless of this flag. Surfaces as `data-coordinating` on
   * consumers.
   */
  isCoordinating: boolean;
  /**
   * `true` once the grace period has elapsed with the barrier still
   * unresolved. Only set on the originating peer. Surface as a
   * "waiting" affordance to the user.
   */
  isWaitingForPeers: boolean;
}
/**
 * Coordinate a piece of state across sibling component instances on
 * the same channel, so that visually disruptive value changes commit
 * in a single layout pass rather than independently. Designed as a
 * thin wrapper around any `useState`-shaped primitive (e.g.
 * `useLocalStorageState`, `usePreference`, plain `useState`).
 *
 * **Originator flow** — calling the returned `setValue`:
 *   1. `pendingValue` updates synchronously to the requested target
 *   2. The coordinator runs `preload` (per phase rules) and waits for
 *      sibling peers (phase 1 only)
 *   3. When the barrier resolves, the underlying `setValue` is called
 *      and this hook's visible `value` flips, so the swap is
 *      consistent with the optional `onCommit` side-effect
 *
 * **Receiver flow** — when the underlying `value` changes from outside
 * (e.g. a storage event from another tab):
 *   1. `pendingValue` updates to match
 *   2. Coordination runs locally (this tab's peers run their own
 *      phase-1 barrier)
 *   3. The visible `value` returned by this hook is held at the
 *      previous value until the barrier resolves, then flips
 *
 * Pass `channelKey: null` to disable coordination — the hook becomes
 * a transparent pass-through of the underlying tuple.
 */
export declare function useCoordinated<TValue, TPreload = void>(underlying: [TValue, (next: TValue) => void], options: UseCoordinatedOptions<TValue, TPreload>): [TValue, React.Dispatch<React.SetStateAction<TValue>>, UseCoordinatedExtras<TValue>];