/**
 * Frontend WebSocket client — portable, Svelte-reactive, implements `WebsocketConnection`.
 *
 * Plain class with `$state` runes (no Cell inheritance, no app coupling).
 * Drop into any SvelteKit frontend as the underlying connection for
 * `FrontendWebsocketTransport`. Handles auto-reconnect with exponential
 * backoff, respects `WS_CLOSE_SESSION_REVOKED` (no reconnect loop after the
 * server revokes auth), exposes reactive status for UI indicators, and ships
 * three correctness primitives default-on:
 *
 * - `FrontendWebsocketClient.request` — promise-based JSON-RPC with
 *   auto-assigned ids and a pending-id map. Intercepts responses on the
 *   message path so request/response correlation is transport-level rather
 *   than re-invented per consumer.
 * - **Durable queue** — `request()` calls made while disconnected buffer up
 *   to `DEFAULT_QUEUE_MAX_SIZE` requests and flush on reopen. Overflow
 *   rejects with `queue_overflow`. Raw `FrontendWebsocketClient.send`
 *   is drop-on-disconnect (fire-and-forget notifications want that).
 * - **Activity-aware heartbeat** — idles fire a shared `heartbeat` request;
 *   receive-silence past `DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT` closes
 *   with `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT` and lets auto-reconnect
 *   pick back up.
 *
 * @module
 */
import type { Logger } from '@fuzdev/fuz_util/log.js';
import type { AsyncStatus } from '@fuzdev/fuz_util/async.js';
import { type JsonrpcRequestId } from '../http/jsonrpc.js';
import type { WebsocketConnection } from './transports_ws.js';
/** Default WebSocket close code (normal closure). */
export declare const DEFAULT_CLOSE_CODE = 1000;
/** Base reconnect delay in ms. */
export declare const DEFAULT_RECONNECT_DELAY = 1000;
/** Max reconnect delay in ms (cap on exponential backoff). */
export declare const DEFAULT_RECONNECT_DELAY_MAX = 10000;
/** Exponential backoff factor: delay = base * factor^(attempt-1). */
export declare const DEFAULT_BACKOFF_FACTOR = 1.5;
/** Idle interval before sending a heartbeat (ms). */
export declare const DEFAULT_HEARTBEAT_INTERVAL = 30000;
/** Max receive silence before closing with `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT` (ms). */
export declare const DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT = 60000;
/** Default bound on buffered requests while disconnected. Overflow rejects. */
export declare const DEFAULT_QUEUE_MAX_SIZE = 100;
/**
 * Client-side WebSocket status.
 *
 * - `initial` — never connected; `connect()` has not been called.
 * - `connecting` — WebSocket `readyState === CONNECTING`.
 * - `connected` — WebSocket `readyState === OPEN`.
 * - `reconnecting` — close fired; waiting out backoff before next attempt.
 * - `closed` — socket is not open. Terminal only when `revoked` is `true`
 *   or auto-reconnect is disabled; otherwise `connect()` reopens.
 */
export type SocketStatus = 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
export type SocketMessageHandler = (event: MessageEvent) => void;
export type SocketErrorHandler = (event: Event) => void;
export interface FrontendWebsocketReconnectOptions {
    /** Base reconnect delay in ms. Defaults to 1000. */
    delay?: number;
    /** Max reconnect delay in ms (cap on exponential backoff). Defaults to 10000. */
    delay_max?: number;
    /** Exponential backoff factor. Defaults to 1.5. */
    factor?: number;
}
export interface FrontendWebsocketHeartbeatOptions {
    /**
     * Idle duration (ms) after which a heartbeat is sent. Reset by any send or
     * receive — chatty clients never emit extras. Defaults to
     * `DEFAULT_HEARTBEAT_INTERVAL`.
     */
    interval?: number;
    /**
     * Receive-silence (ms) after which the client closes the socket with
     * `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT`, letting auto-reconnect kick
     * in. Should be a comfortable multiple of `interval`. Defaults to
     * `DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT`.
     */
    receive_timeout?: number;
}
export interface FrontendWebsocketQueueOptions {
    /**
     * Maximum number of requests held while the socket is disconnected.
     * Enqueue past this rejects the new call with a `queue_overflow` error.
     * Defaults to `DEFAULT_QUEUE_MAX_SIZE`.
     */
    max_size?: number;
}
export interface FrontendWebsocketClientOptions {
    /**
     * Auto-reconnect policy. `false` disables reconnect entirely; `true` or
     * omit for default timing; pass an object to customize.
     */
    reconnect?: boolean | FrontendWebsocketReconnectOptions | null;
    /**
     * Activity-aware heartbeat. `true`/`null`/omit for defaults; `false` disables
     * the timer entirely (only do this if the server side is also running
     * without heartbeat); pass an object to tune `interval` / `receive_timeout`.
     */
    heartbeat?: boolean | FrontendWebsocketHeartbeatOptions | null;
    /**
     * Durable queue for `FrontendWebsocketClient.request`. `true` or omit
     * for defaults; `false` disables buffering (requests while disconnected
     * reject immediately). Raw `FrontendWebsocketClient.send` is never
     * queued — use `request()` for RPC semantics.
     */
    queue?: boolean | FrontendWebsocketQueueOptions;
    /** Optional logger for diagnostic messages. */
    log?: Logger | null;
}
/**
 * Reactive WebSocket client implementing `WebsocketConnection`.
 *
 * Construct with a URL and optional config; call `connect()` to open the
 * socket and begin auto-reconnect. Register message/error handlers via
 * `add_message_handler` / `add_error_handler` — both return unsubscribe
 * functions. `FrontendWebsocketTransport` consumes this as its connection.
 *
 * Session-revocation close codes (`WS_CLOSE_SESSION_REVOKED`) put the client
 * in a permanently-closed state; reconnecting would just loop on 401.
 */
export declare class FrontendWebsocketClient implements WebsocketConnection, Disposable {
    #private;
    ws: WebSocket | null;
    status: SocketStatus;
    reconnect_count: number;
    current_reconnect_delay: number;
    /** Epoch ms of the most recent successful open. Never cleared on close. */
    last_connect_time: number | null;
    /** Epoch ms of the most recent close event or client-initiated close. */
    last_close_time: number | null;
    /** Close code from the most recent close. Initial `null` means "never closed." */
    last_close_code: number | null;
    /** Reason string from the most recent close event (may be empty). */
    last_close_reason: string | null;
    /**
     * The error thrown by the most recent attempted `send()`, or `null` if the
     * most recent attempt succeeded or none has been attempted yet. Populated
     * when the underlying `ws.send` throws (e.g., buffer full, serialization
     * error); reset to `null` on the next successful send. Not touched when
     * `send()` short-circuits because the socket is not connected — consult
     * `connected` for that case. Wrappers surfacing per-message failure
     * reasons can read this after a `false` return from `send()`.
     */
    last_send_error: Error | null;
    readonly connected: boolean;
    constructor(url: string, options?: FrontendWebsocketClientOptions);
    /**
     * Swap the auto-reconnect policy in place. Accepts the same shape as the
     * constructor's `reconnect` option: `false` disables reconnect, `true` or
     * `null`/omitted restores the defaults, or a config object customizes
     * specific fields (missing fields fall back to defaults, not "keep
     * current" — each call defines the whole policy atomically, same as the
     * constructor).
     *
     * In-flight reconnect schedules are **monotonically shortened**: the
     * effective total wait from arm-time never exceeds what the new policy
     * prescribes. If the new target is already past the time already
     * elapsed, the reconnect fires immediately (on the next tick). The wait
     * is never extended.
     *
     * Turning reconnect off while a reconnect timer is pending cancels that
     * timer and transitions status to `closed` (since the lie of
     * `'reconnecting'` would be visible to UI indicators). Turning it back on
     * does not synthesize a reconnect — wait for the next close.
     *
     * @mutates this - replaces the reconnect policy fields; may also reset
     *   `status`, `reconnect_count`, and `current_reconnect_delay` when an
     *   in-flight reconnect timer is cancelled or rescheduled
     */
    set_reconnect(reconnect?: boolean | FrontendWebsocketReconnectOptions | null): void;
    /**
     * Swap the heartbeat policy in place. Accepts the same shape as the
     * constructor's `heartbeat` option: `false` disables the timer, `true` or
     * `null`/omitted restores the defaults, or a config object customizes
     * specific fields (missing fields fall back to defaults, not "keep
     * current" — each call defines the whole policy atomically, same as the
     * constructor and `set_reconnect`).
     *
     * When connected, the live timer is restarted immediately so the new
     * `interval` / `receive_timeout` take effect without a reconnect; when
     * disconnected, just stashes the policy for the next open.
     *
     * @mutates this - replaces the heartbeat policy fields; restarts the
     *   live heartbeat timer when connected
     */
    set_heartbeat(heartbeat?: boolean | FrontendWebsocketHeartbeatOptions | null): void;
    /**
     * Cancel a scheduled reconnect without closing the client or disabling
     * auto-reconnect. Transitions status from `reconnecting` → `closed` and
     * resets the backoff counters — the next close still triggers a fresh
     * reconnect cycle under the current policy. No-op when no reconnect is
     * pending.
     *
     * Use this when UI state asks "stop trying for now" without the finality
     * of `disconnect` (which also rejects pending/queued requests and
     * clears heartbeat) or the policy change of `set_reconnect(false)`
     * (which disables future reconnects). The queue stays intact so that
     * calling `connect` later flushes buffered work.
     *
     * @mutates this - clears the pending reconnect timer, sets `status` to
     *   `closed`, resets `reconnect_count` and `current_reconnect_delay`
     */
    cancel_reconnect(): void;
    get url(): string;
    /**
     * Whether the server has permanently closed the session. Once `true`, all
     * `connect()` calls are no-ops. Distinct from `status:'closed'`, which
     * reflects any closed state (incl. user-initiated `disconnect()`).
     */
    get revoked(): boolean;
    /**
     * Open the WebSocket. No-op on SSR, or if the session has been revoked.
     * Cancels any pending reconnect and tears down any existing connection first;
     * an open prior socket is closed with a normal-closure code.
     *
     * @mutates this - replaces `ws`, sets `status` to `connecting` (or
     *   `closed` on construction failure), and on construction failure may
     *   schedule a reconnect (mutating `reconnect_count` /
     *   `current_reconnect_delay`)
     */
    connect(): void;
    /**
     * Close the WebSocket, cancel any pending reconnect, and reset the reconnect
     * backoff counters. Puts the client in `closed` status; call `connect()` to
     * reopen. Safe to call more than once.
     *
     * @mutates this - clears `ws`, heartbeat, and reconnect timers; sets
     *   `status` to `closed`; rejects every pending and queued request with
     *   `service_unavailable`
     */
    disconnect(code?: number): void;
    /** Explicit-resource-management hook — supports `using client = new FrontendWebsocketClient(url)`. */
    [Symbol.dispose](): void;
    send(data: object): boolean;
    /**
     * Promise-based JSON-RPC over the socket. Auto-assigns a monotonic request
     * id (or uses an explicit one supplied via `options.id` — used by
     * `FrontendWebsocketTransport` which delegates to this method and has its
     * own peer-minted UUID), tracks the pending promise, and resolves when the
     * server sends a matching response.
     *
     * Callers supplying an explicit `options.id` are responsible for
     * uniqueness — the pending map is keyed by id, and a duplicate silently
     * overwrites the prior entry. Auto-minted ids are monotonic and never
     * collide with themselves or with peer-minted UUIDs (the types differ:
     * integer vs string).
     *
     * While the socket is disconnected, the request is buffered in a bounded
     * queue (default-on, `DEFAULT_QUEUE_MAX_SIZE`) and flushed on reopen. Pass
     * `{queue: false}` to reject immediately when disconnected — used
     * internally by the heartbeat, which must not fight the queue for the
     * disconnect-detection slot.
     *
     * On `AbortSignal` fire: rejects the local promise *and* sends the shared
     * `cancel` notification (`cancel_action_spec.method`) so the server-side
     * dispatcher can abort the matching handler's `ctx.signal`. Suppressed
     * for queued-but-never-sent (server doesn't know about it) and
     * response-beat-cancel races.
     *
     * @mutates this - inserts the new pending entry into `#pending` (or
     *   buffers into `#queue` when disconnected) and may bump
     *   `#next_request_id`, `#last_send_time`, `last_send_error`
     * @throws ThrownJsonrpcError on the returned promise — never thrown
     *   synchronously. Rejection codes:
     *   - `unauthenticated` — session revoked (entry check or close code)
     *   - `request_cancelled` — caller's `AbortSignal` fired
     *   - `queue_overflow` — durable queue full
     *   - `service_unavailable` — socket not connected / closed / torn down
     *     mid-flight
     *   - `internal_error` — `ws.send` threw (serialization, buffer full)
     *   - server's wire code verbatim — JSON-RPC error frame from peer
     */
    request<R = unknown>(method: string, params?: unknown, options?: {
        signal?: AbortSignal;
        queue?: boolean;
        id?: JsonrpcRequestId;
    }): Promise<R>;
    add_message_handler(handler: SocketMessageHandler): () => void;
    add_error_handler(handler: SocketErrorHandler): () => void;
}
/**
 * Project `SocketStatus` onto fuz_util's `AsyncStatus` — the
 * 5-way → 4-way mapping every consumer re-derives to surface connection state
 * to UI (loading indicators, retry banners). Collapses `reconnecting` into
 * `failure` (UI shows "lost, retrying") and splits `closed` by `revoked` so
 * a terminal session-revocation read as `failure` while a clean client-
 * initiated close reads as `initial` (the "not connected, not trying" state).
 *
 * @param revoked - whether the session has been permanently revoked
 *   (typically `FrontendWebsocketClient.revoked`)
 */
export declare const socket_status_to_async_status: (status: SocketStatus, revoked: boolean) => AsyncStatus;
//# sourceMappingURL=socket.svelte.d.ts.map