/**
 * SystemMirror — system-wide reactive view of every device, like
 * Scrypted's `systemManager.getDeviceById`. One warm-boot fetches the
 * full bindings + runtime-state snapshot + device metadata; live
 * `device.state-changed`, `capability.binding-changed`,
 * `device.registered`, `device.unregistered`, and `device.updated`
 * events keep the in-memory mirrors fresh. Subsequent
 * `getDeviceById(id)` calls are sync.
 *
 * Renamed from `SystemManager` (and its file `system-manager.ts`) when
 * the SDK introduced a top-level `System` facade. The cluster-wide
 * mirror is one of `System`'s collaborators — calling it a "mirror"
 * disambiguates from the new public class. The old export name is
 * still available as a deprecated alias from this file.
 *
 * Cap-agnostic by construction: the state mirror is `Map<deviceId,
 * Map<capName, slice>>` and the proxies are produced by the
 * codegen'd `createDeviceProxy` we already use everywhere — so a
 * brand-new cap with `runtimeState:` lights up here without a single
 * line of additional code.
 *
 * Two transports today, same shape:
 *   - tRPC client (browser, Electron, SDK consumers).
 *   - In-process AddonApi (server REPL, addons that want a global view).
 *
 * Boot:
 *
 *   const sm = new SystemMirror(api)
 *   await sm.init()
 *
 *   const dev = sm.getDeviceById(8)            // sync
 *   dev?.state.battery.value?.sleeping         // sync read from mirror
 *   await dev?.snapshot?.getSnapshot({...})    // method dispatch via cap-router
 *
 *   sm.query({ addonId: 'reolink', online: true })
 *   sm.whereState('battery', s => s.percentage < 20)
 *   sm.listenCap('motion', (id, slice) => …)
 *   await sm.waitForState(8, 'battery', s => !s.sleeping, 30_000)
 *
 * Lifecycle: caller is responsible for `dispose()` when done — closes
 * the live event subscriptions.
 */
import type { DeviceBinding } from './device-binding.js';
import { type DeviceProxy } from '../generated/device-proxy.js';
import type { DeviceInfo } from '../capabilities/device-manager.cap.js';
import { DeviceType } from './device-type.js';
import { type SliceHandleApi } from './device-state-handle.js';
/**
 * Minimal subset of `AddonApi` the SystemMirror needs.
 */
export interface SystemMirrorApi extends SliceHandleApi {
    readonly deviceManager: {
        readonly getAllBindings: {
            query(input: Record<string, never>): Promise<ReadonlyArray<DeviceBinding>>;
        };
        readonly listAll: {
            query(input: {
                addonId?: string;
            }): Promise<ReadonlyArray<DeviceInfo>>;
        };
    };
    readonly deviceState: SliceHandleApi['deviceState'] & {
        readonly getAllSnapshots: {
            query(input: Record<string, never>): Promise<Record<string, Record<string, Record<string, unknown>>>>;
        };
    };
}
export type SystemMirrorListener = (deviceId: number, capName: string, slice: Record<string, unknown> | undefined) => void;
export type DeviceCapListener = (deviceId: number, slice: Record<string, unknown> | undefined) => void;
export type DeviceLifecycleListener = (deviceId: number, info: DeviceInfo | null) => void;
/**
 * Match shape — `string` is exact match, `RegExp` is regex match,
 * `{ contains }` is case-insensitive substring, `{ exact }` is the
 * verbose form of plain string. Useful for `query({ name: ... })`
 * where the same field accepts multiple match modes.
 */
export type StringMatch = string | RegExp | {
    readonly exact: string;
} | {
    readonly contains: string;
};
/**
 * Filter set for `query()`. All fields are optional and ANDed
 * together. Array values are ORed within a single field
 * (`addonId: ['a', 'b']` matches devices owned by either). The
 * `where` escape hatch runs LAST so the structured filters do the
 * cheap rejection first.
 */
export interface DeviceQueryFilters {
    /** Match by exact deviceId. Single value or array (OR'd). */
    readonly id?: number | readonly number[];
    /** Match by exact stableId. Single value or array. */
    readonly stableId?: string | readonly string[];
    /** Match by owning addonId. Single value or array. */
    readonly addonId?: string | readonly string[];
    /** Match by `DeviceType` enum value. Single value or array. */
    readonly type?: DeviceType | readonly DeviceType[];
    /** Match devices that bind ALL listed caps. Single capName or array. */
    readonly caps?: string | readonly string[];
    /** Match devices that bind ANY of the listed caps. Mutually exclusive
     *  with `caps`. */
    readonly anyCap?: string | readonly string[];
    /** Match by device name. */
    readonly name?: StringMatch;
    /** Match the `online` flag. */
    readonly online?: boolean;
    /** Match by parent device id. Pass `null` for top-level devices. */
    readonly parentDeviceId?: number | null;
    /** Match devices that expose a feature (e.g. `'BatteryOperated'`). */
    readonly feature?: string | readonly string[];
    /** Match `isCamera` flag. */
    readonly isCamera?: boolean;
    /** Custom predicate. Runs last; receives both metadata and proxy. */
    readonly where?: (info: DeviceInfo, proxy: DeviceProxy) => boolean;
}
export declare class SystemMirror {
    private readonly api;
    /** deviceId → capName → slice (push-driven mirror). */
    private readonly stateMirror;
    /** deviceId → binding (warm-boot + binding-changed events). */
    private readonly bindings;
    /** deviceId → metadata (warm-boot + lifecycle events). */
    private readonly devices;
    /** Listener map for individual `(deviceId, capName)` watches —
     *  shared with every `MirrorSource` we hand out. */
    private readonly handleListeners;
    /** Global state-change listeners registered via `listen()`. */
    private readonly globalStateListeners;
    /** Per-cap state listeners registered via `listenCap(capName, …)`. */
    private readonly capListeners;
    /** Per-device state listeners registered via `listenDevice(id, …)`. */
    private readonly deviceListeners;
    /** Lifecycle listeners. */
    private readonly addedListeners;
    private readonly removedListeners;
    /** waitForState pending requests — keyed by an internal counter. */
    private readonly pendingWaits;
    /** Memoised state source — every proxy shares it. */
    private readonly stateSource;
    /** Live event bridges — closed in `dispose()`. */
    private bridges;
    private initialized;
    private initPromise;
    constructor(api: SystemMirrorApi);
    /**
     * Warm-boot: three round-trips (bindings + snapshots + listAll) and
     * then the live event subscriptions. Idempotent — concurrent callers
     * await the same in-flight Promise.
     *
     * Each warm-boot query is wrapped in a 15s timeout so a missing
     * endpoint or stuck broker poll surfaces as a clear error instead
     * of silently hanging the caller forever. The default is generous
     * enough for cluster-wide setups but short enough for ops to
     * notice and fix the underlying transport issue.
     */
    init(timeoutMs?: number): Promise<void>;
    /** True after `init()` resolves. */
    isReady(): boolean;
    /** Promise that resolves once `init()` has completed. */
    awaitReady(): Promise<void>;
    /**
     * Tear down the event bridges + clear listener registries. Existing
     * proxy references become inert (slice reads keep working off the
     * stale mirror, but no further updates arrive).
     */
    dispose(): void;
    /** Sync lookup by numeric id. `null` if unknown. */
    getDeviceById(deviceId: number): DeviceProxy | null;
    /** Sync lookup by display name (exact match). `null` if unknown. */
    getDeviceByName(name: string): DeviceProxy | null;
    /** Sync lookup by stableId (the addon-scoped external id). */
    getDeviceByStableId(stableId: string): DeviceProxy | null;
    /** All devices owned by an addon. */
    getDevicesByAddon(addonId: string): readonly DeviceProxy[];
    /** All devices of a given `DeviceType`. */
    getDevicesByType(type: DeviceType): readonly DeviceProxy[];
    /**
     * Live metadata snapshot for a device. Same shape as
     * `deviceManager.getDevice(id)`; sync from the mirror.
     */
    getDeviceInfo(deviceId: number): DeviceInfo | null;
    /** Snapshot of every known device as a typed proxy. */
    getAllDevices(): readonly DeviceProxy[];
    /** Filter devices by cap presence — shorthand for `query({caps: capName})`. */
    filterByCap(capName: string): readonly DeviceProxy[];
    /**
     * Filter the fleet against a structured filter set. All fields ANDed,
     * arrays ORed within a single field. Returns proxies — chain with
     * cap-method calls or state reads as needed.
     *
     * Examples:
     *
     *   sm.query({ addonId: 'reolink', online: true })
     *   sm.query({ type: DeviceType.Camera, caps: ['snapshot', 'motion'] })
     *   sm.query({ id: [1, 2, 3] })
     *   sm.query({ name: { contains: 'sala' } })
     *   sm.query({ name: /^cam-\d+$/ })
     *   sm.query({ parentDeviceId: 8 })  // accessories of cam 8
     *   sm.query({ where: (info, dev) => dev.state.battery.value?.sleeping })
     */
    query(filters?: DeviceQueryFilters): readonly DeviceProxy[];
    /**
     * Return every proxy whose runtime-state slice for `capName` matches
     * `predicate`. Devices without the cap or without a slice are
     * skipped. The predicate receives a typed slice reference — pass
     * the typed cap (e.g. `'battery'`) and TypeScript widens to
     * `Record<string, unknown>`; cast inside the predicate body if the
     * caller knows the cap shape.
     */
    whereState<T extends Record<string, unknown> = Record<string, unknown>>(capName: string, predicate: (slice: T, deviceId: number) => boolean): readonly DeviceProxy[];
    /** Map every device's slice for `capName` to a derived value. Devices
     *  without the cap or without a slice are skipped. */
    mapState<T extends Record<string, unknown> = Record<string, unknown>, R = unknown>(capName: string, mapper: (slice: T, deviceId: number) => R): readonly R[];
    /** First device whose slice matches `predicate`, or null. */
    findState<T extends Record<string, unknown> = Record<string, unknown>>(capName: string, predicate: (slice: T, deviceId: number) => boolean): DeviceProxy | null;
    /** Count devices that bind a cap. Faster than `filterByCap(...).length`. */
    countByCap(capName: string): number;
    /** Count devices whose slice for `capName` matches `predicate`. */
    countByState<T extends Record<string, unknown> = Record<string, unknown>>(capName: string, predicate: (slice: T, deviceId: number) => boolean): number;
    /**
     * Global listener — fires for every `device.state-changed` event the
     * mirror absorbs.
     */
    listen(cb: SystemMirrorListener): () => void;
    /**
     * Per-cap listener — fires only for state changes on `capName`,
     * across every device. The callback receives the deviceId so the
     * caller can route.
     */
    listenCap(capName: string, cb: DeviceCapListener): () => void;
    /**
     * Per-device listener — fires for every cap change on `deviceId`.
     */
    listenDevice(deviceId: number, cb: SystemMirrorListener): () => void;
    /** Fires when `device.registered` lands. Receives the new metadata. */
    onDeviceAdded(cb: DeviceLifecycleListener): () => void;
    /** Fires when `device.unregistered` lands. `info` is the LAST-known
     *  metadata (or null if the device was never seen). */
    onDeviceRemoved(cb: DeviceLifecycleListener): () => void;
    /**
     * Resolve when `predicate` over the runtime-state slice for
     * `(deviceId, capName)` becomes true. Resolves immediately if the
     * current slice already matches. Rejects with `Error('timeout')`
     * after `timeoutMs` (default 30s; pass `Infinity` to wait forever).
     *
     * Returns the matching slice — caller can read it directly without
     * a second mirror lookup.
     */
    waitForState<T extends Record<string, unknown> = Record<string, unknown>>(deviceId: number, capName: string, predicate: (slice: T) => boolean, timeoutMs?: number): Promise<T>;
    /**
     * Resolve when a device with `deviceId` becomes available (a
     * binding exists). Resolves immediately if already known. Rejects
     * with timeout.
     */
    waitForDevice(deviceId: number, timeoutMs?: number): Promise<DeviceProxy>;
    /**
     * Iterate every device that binds `capName`. Awaits each callback
     * sequentially — for parallel use `invokeCap` with explicit
     * parallelism.
     */
    forEachCap(capName: string, cb: (proxy: DeviceProxy) => void | Promise<void>): Promise<void>;
    /**
     * Invoke a cap method on every device that binds the cap. Returns
     * one result per device, success or failure isolated. Optional
     * parallelism cap — useful for "snapshot all cameras but only 4
     * at a time so battery cams don't all wake at once".
     *
     * Example:
     *
     *   const results = await sm.invokeCap('snapshot', 'getSnapshot', {}, { parallelism: 4 })
     *   const failed = results.filter(r => !r.ok)
     */
    invokeCap<R = unknown>(capName: string, methodName: string, args: Record<string, unknown>, opts?: {
        parallelism?: number;
    }): Promise<ReadonlyArray<{
        deviceId: number;
        ok: boolean;
        result?: R;
        error?: unknown;
    }>>;
    /**
     * One-shot summary — fleet size, breakdown by cap / addon / type.
     * Designed for REPL inspection (`sm.summary()`).
     */
    summary(): {
        totalDevices: number;
        online: number;
        offline: number;
        byCap: Record<string, number>;
        byAddon: Record<string, number>;
        byType: Record<string, number>;
        statedDevices: number;
    };
    /**
     * Debug-friendly dump — full state + binding + metadata for one
     * device or all devices. Cheap deep clone so caller mutations don't
     * leak into the mirror.
     */
    dump(deviceId?: number): unknown;
    /**
     * Direct read-only access to the underlying state mirror. Use
     * sparingly — `getSystemState` returns a deep copy that's safer for
     * exploratory work; this avoids the clone cost when iterating
     * thousands of slices.
     */
    getRawMirror(): ReadonlyMap<number, ReadonlyMap<string, Record<string, unknown>>>;
    /**
     * Snapshot of the full state mirror — same shape as the warm-boot
     * payload. Deep-cloned; safe to mutate.
     */
    getSystemState(): Map<number, Map<string, Record<string, unknown>>>;
    private subscribeBus;
    private applyStateUpdate;
    private applyDeviceRemoval;
    private refreshBinding;
    private refreshDeviceMetadata;
}
//# sourceMappingURL=system-mirror.d.ts.map