/**
 * Reactive read handle for one cap-keyed slice of a device's
 * runtime state. Returned by `createDeviceProxy(...).state[capName]`.
 *
 * Three pieces:
 *   - `value`: last-known slice. `undefined` until something has
 *     populated it (an active subscription, an explicit `refresh()`,
 *     or — in the case of the SystemManager mirror source — a
 *     warm-boot `getAllSnapshots` payload). Read sync.
 *   - `refresh()`: one-shot pull from the underlying source. For
 *     the lazy tRPC source this is `deviceState.getCapSlice`; for
 *     the SystemManager mirror it's a no-op (the mirror is push-only).
 *   - `subscribe(cb)`: hooks into the source's notification channel,
 *     filtered by `(deviceId, capName)`. Refcounted — the underlying
 *     subscription closes when the last caller unsubscribes.
 *
 * Cross-environment: the same API works in browser (admin-ui via the
 * tRPC client) and on the server (in-process AddonApi). The transport
 * is whatever the supplied source exposes.
 */
export interface SliceHandle<T> {
    /** Latest cached slice. Reflects the most recent `refresh()` or
     *  push event. `undefined` if neither has happened. */
    readonly value: T | undefined;
    /** Force a re-fetch from the hub mirror. Updates `value` + notifies
     *  every active subscriber. No-op for the SystemManager mirror
     *  source (it's already push-driven). */
    refresh(): Promise<void>;
    /** Subscribe to slice changes. Returns the unsubscribe fn.
     *  Auto-fires the callback once with the current value (or
     *  `undefined`) so the caller sees a snapshot immediately. */
    subscribe(cb: (slice: T | undefined) => void): () => void;
    /**
     * Replace the slice on the hub. Routes through
     * `device-state.setCapSlice` — the canonical cross-layer write
     * entrypoint. The local mirror updates via the `onChanged` event
     * (round-trip), so callers should treat the write as eventually
     * consistent and read the new value via `subscribe` rather than
     * synchronously after `await`.
     */
    set(slice: T): Promise<void>;
    /**
     * Shallow-merge `partial` into the current slice on the hub.
     * Implemented as read-modify-write client-side: pulls `value`
     * (or refreshes), merges, calls `set()`. Single-writer
     * conventions on each cap make atomic-merge semantics
     * unnecessary — concurrent patches on the same slice from
     * different processes are undefined and should be avoided.
     */
    patch(partial: Partial<T>): Promise<void>;
}
/**
 * Minimal tRPC API surface used by the lazy source. Both `AddonApi`
 * (server) and the tRPC client proxy (browser) satisfy it structurally
 * — we don't import either to avoid pulling them in from a leaf type
 * module.
 */
export interface SliceHandleApi {
    readonly deviceState: {
        readonly getCapSlice: {
            query(input: {
                deviceId: number;
                capName: string;
            }): Promise<Record<string, unknown> | null>;
        };
        readonly setCapSlice: {
            mutate(input: {
                deviceId: number;
                capName: string;
                slice: Record<string, unknown>;
            }): Promise<void>;
        };
    };
    readonly live?: {
        readonly onEvent: {
            subscribe(input: {
                category: string;
            }, opts: {
                onData: (evt: {
                    data: unknown;
                }) => void;
                onError?: (err: unknown) => void;
            }): {
                unsubscribe: () => void;
            };
        };
    };
}
/**
 * Pluggable state-source contract. `createSliceHandle` is now a thin
 * adapter over this interface — every implementation produces an
 * identical `SliceHandle<T>` shape. Two impls today:
 *
 *   - `createLazyTrpcSource(api)`: per-handle local cache, refresh
 *     via `deviceState.getCapSlice`, subscribe via `live.onEvent`.
 *     Default behavior, used by `createDeviceProxy(api, binding)`
 *     when no source is passed.
 *   - `createMirrorSource(mirror, listeners)`: reads from a shared
 *     `Map<deviceId, Map<capName, slice>>` populated by a
 *     `SystemManager` warm-boot. Refresh is a no-op; subscribe
 *     registers in a shared listener set that the SystemManager
 *     fans out to on every push event.
 *
 * Both produce the same `SliceHandle<T>` API; consumers don't see
 * which source is behind the handle.
 */
export interface SliceHandleSource {
    /** Sync read of the last-known slice for `(deviceId, capName)`. */
    read(deviceId: number, capName: string): unknown | undefined;
    /** Force a re-fetch (where applicable). No-op when the source is
     *  push-only (e.g. the SystemManager mirror). */
    refresh(deviceId: number, capName: string): Promise<void>;
    /** Register a listener for slice changes. Returns the unsubscribe
     *  fn. Implementations should fan out the latest cached slice to
     *  the callback synchronously when seeding is desired (the handle
     *  always seeds via a separate `cb(read(...))` call). */
    watch(deviceId: number, capName: string, cb: (slice: unknown | undefined) => void): () => void;
    /** Write the slice for `(deviceId, capName)` to the hub via the
     *  canonical `device-state.setCapSlice` entrypoint. Throws if the
     *  source has no write transport (e.g. a mirror source built
     *  without an api reference). */
    write(deviceId: number, capName: string, slice: Record<string, unknown>): Promise<void>;
}
/**
 * Lazy tRPC source — the default behavior. Maintains a per-key local
 * cache (`{deviceId}:{capName}` → last slice), one shared
 * `live.onEvent` bridge that fans out into the listener map, and
 * `refresh` round-trips through `deviceState.getCapSlice`.
 *
 * The bridge is opened on first `watch()` and closed when the last
 * watcher unsubscribes — keeps idle handles cheap.
 */
export declare function createLazyTrpcSource(api: SliceHandleApi): SliceHandleSource;
/**
 * Mirror source — reads from a shared map populated by a
 * `SystemManager` warm-boot + push event handler. Refresh is a no-op
 * (the SystemManager owns the update loop). `watch()` registers in a
 * shared listener set — the SystemManager calls these from its single
 * `device.state-changed` subscription.
 *
 * The mirror map and listener map are passed in by reference so the
 * SystemManager can mutate both as events arrive.
 */
export declare function createMirrorSource(mirror: ReadonlyMap<number, ReadonlyMap<string, unknown>>, listeners: Map<string, Set<(slice: unknown | undefined) => void>>, api?: SliceHandleApi): SliceHandleSource;
/**
 * Build a `SliceHandle<T>` bound to `(deviceId, capName)`. Caller
 * provides the type parameter — codegen passes
 * `InferRuntimeState<typeof <cap>>` so consumers see the cap's typed
 * shape:
 *
 *   const dev = createDeviceProxy(api, binding)
 *   dev.state.battery.value           // BatteryStatus | undefined
 *   dev.state.battery.value?.percentage
 *
 * Two-arg form (deprecated path, kept for spec compatibility):
 * passes the api directly, builds a per-handle lazy source. New
 * code should pre-build a single source and reuse it across handles
 * via the explicit `source` form.
 */
export declare function createSliceHandle<T>(source: SliceHandleSource | SliceHandleApi, deviceId: number, capName: string): SliceHandle<T>;
//# sourceMappingURL=device-state-handle.d.ts.map