import { MqttClient } from "homebridge-plugin-utils";
import type { Camera, ProtectDeviceConfig, ProtectNvrConfig } from "unifi-protect";
import { ProtectClient } from "unifi-protect";
import type { HomebridgePluginLogging, Nullable } from "homebridge-plugin-utils";
import type { ProtectAccessory, ProtectDevices } from "../types.ts";
import { DoorbellCapability } from "../devices/cameras/doorbell.ts";
import { NvrHealth } from "./nvr-health.ts";
import { ProtectCamera } from "../devices/cameras/camera.ts";
import { ProtectCameraPackage } from "../devices/cameras/camera-package.ts";
import { ProtectEventDispatch } from "./event-dispatch.ts";
import type { ProtectNvrOptions } from "../options.ts";
import { ProtectNvrSystemInfo } from "./nvr-systeminfo.ts";
import type { ProtectPlatform } from "../platform.ts";
/**
 * The NVR's lifecycle phase. The single source of truth for "what is this NVR doing right now?", consulted by every component that needs to distinguish induced
 * disruptions (where the plugin is intentionally driving an outage and downstream noise should be suppressed) from organic disruptions (where something genuinely
 * unexpected happened and the noise IS the signal).
 *
 *   - `connecting`: initial setup, or post-disruption reconnection attempt. Real connection errors should still surface to the user (e.g., "invalid credentials"),
 *      but health-symptom observation is suspended because we have not yet established a baseline of "running" against which to weigh stress.
 *   - `running`: connected, normal steady-state operation. Errors are organic; health observation is active; consumers log unexpected events normally.
 *   - `rebooting`: the plugin initiated a controller reboot. Errors are induced (we know the controller is going away); health observation is suspended; consumers
 *      suppress noise about the disruption.
 *   - `shuttingDown`: terminal. The plugin or the host process is going away. Same suppression as `rebooting` but no recovery is expected.
 *
 * Transitions are owned by `ProtectNvr.transition()`. Components consume via `nvr.phase`. Derived predicates (currently `logApiErrors`) are getters that project
 * from phase, so phase is the lone mutable axis.
 */
export type NvrPhase = "connecting" | "rebooting" | "running" | "shuttingDown";
export declare class ProtectNvr {
    #private;
    private api;
    client: ProtectClient;
    readonly config: ProtectNvrOptions;
    readonly configuredDevices: Map<string, ProtectDevices>;
    private connectionSubscriptions;
    private controllerStableSince;
    private deviceRemovalTimers;
    readonly events: ProtectEventDispatch;
    private featureLog;
    private hap;
    private hasStabilizedOnce;
    readonly health: NvrHealth;
    private lastRebootObservedAt;
    private readonly livestreamEpisodes;
    private liveviews;
    readonly log: HomebridgePluginLogging;
    mqtt: Nullable<MqttClient>;
    private name;
    private nvrRebootTimer;
    private rebootConfirmTimer;
    private _phase;
    readonly platform: ProtectPlatform;
    private stabilityReachedTimer;
    systemInfo: Nullable<ProtectNvrSystemInfo>;
    private unsupportedDevices;
    private readonly clientLog;
    private readonly deviceDescriptors;
    constructor(platform: ProtectPlatform, nvrOptions: ProtectNvrOptions);
    /**
     * Wire the NVR-health and user-facing log feeds for livestream disruptions, sourced from the unifi-protect library's process-global livestream-recovery diagnostics
     * channels. The library owns the recovery protocol and publishes each episode's lifecycle on these channels; the plugin translates them into its own health model and
     * its own per-camera logs, scoped to this controller's cameras.
     *
     * We subscribe the two episode-boundary channels - `recovery:started` (a stream was disrupted; the episode begins) and `recovery:recovered` (it resumed). These are
     * the stall/recovery health feed: one stress symptom per disruption episode, one recovery symptom per recovery, so a recovered episode
     * nets zero and a failed one (started, never recovered) stays +1 - correct stress accounting with no double count. The other livestream channels are deliberately not
     * consumed here: `stall:detected` is a subset of `recovery:started` (every detected stall begins an episode) and consuming both would double-count;
     * `recovery:exhausted` is the consumer-side self-heal's concern; `session:closed`/`codec:changed` are neither health nor log signals.
     *
     * The channels are process-global - shared by every `ProtectNvr` in this process - so each handler filters to this controller's cameras via `getDeviceById(cameraId)`
     * and drops the rest (another controller's camera, or one we do not configure). A package-camera stream carries its PARENT camera's device id, so the lookup resolves
     * the parent `ProtectCamera` and the log names the parent - correct, since there is no separate package-camera device to name.
     */
    private wireLivestreamHealth;
    /**
     * Current lifecycle phase. Components that need to distinguish induced disruption (rebooting, shutting down) from organic operation (running) consult this
     * property. Pure read - mutation goes through {@link transition} only.
     */
    get phase(): NvrPhase;
    /**
     * The terminal plugin-shutdown abort signal. Aborted exactly once, in `transition("shuttingDown")`. Every NVR-level observe loop and every per-accessory abort
     * controller composes against it, so plugin shutdown tears the whole tree down as one cascade. Pure read - the controller is private and aborted only through the
     * transition chokepoint.
     */
    get signal(): AbortSignal;
    /**
     * Read-through NVR configuration. Replaces the held bootstrap snapshot with the live unifi-protect projection, so every `nvr.ufp.<field>` read across the plugin
     * reflects the current reduced state with no merge and no reassignment. A read before the first successful connect() throws (the getter dereferences
     * `this.client`, which is unset until connect() assigns it) - this is deliberate: a too-early read should fail loudly, not silently return a stale snapshot, which
     * is the held-state footgun this read-through design avoids. No code path reaches that throw: the constructor and the only other pre-connect path (`login()`'s
     * global enable gate) both avoid `ufp` - the gate consults feature options by global scope, not the controller mac, and `ProtectEventDispatch` construction is
     * structural-only and does not read `hasFeature`/`ufp`.
     */
    get ufp(): Readonly<ProtectNvrConfig>;
    /**
     * Whether API error logging is currently surfaced to the user. Derived from phase: errors are visible during `connecting` (so credential or address problems
     * reach the user) and `running` (organic errors are real signal), but suppressed during `rebooting` and `shuttingDown` where the errors are induced by our own
     * teardown. Read by the unifi-protect client logger callback in this NVR's constructor.
     */
    get logApiErrors(): boolean;
    private transition;
    private publishLifecycle;
    private connect;
    private wireConnectionHealth;
    private onControllerRebooted;
    private onControllerRecovered;
    private resumeFromInducedReboot;
    private disconnect;
    login(): Promise<void>;
    private configureNvr;
    private configureScheduledReboot;
    private executeScheduledReboot;
    private get deviceConfigs();
    private deviceConfig;
    private deviceConfigByMac;
    private reconcileMembership;
    private currentAdoptedMacs;
    private sweepOrphans;
    private startDeviceObservers;
    private refreshRemovalStability;
    private get removalStable();
    private sweepRemovableDevices;
    scheduleDeviceRemoval(options: {
        accessory: ProtectAccessory;
        reason?: string;
        remove?: () => void;
        stillGone: () => boolean;
    }): void;
    cancelDeviceRemovalFor(uuid: string): void;
    private cancelAllDeviceRemovals;
    private startConnectionObserver;
    private spawnLoop;
    createDoorbellCapability(camera: ProtectCamera, device: Camera, signal: AbortSignal): DoorbellCapability;
    createPackageCamera(accessory: ProtectAccessory, device: Camera): ProtectCameraPackage;
    removeStaleDoorbellServices(accessory: ProtectAccessory): void;
    addHomeKitDevice(device: ProtectDeviceConfig): boolean;
    removeHomeKitDevice(accessory: ProtectAccessory): void;
    removeAccessoryFromHomeKit(accessory: ProtectAccessory): void;
    private devices;
    private deviceEndpoints;
    private episodeCameraReachable;
    getDeviceById(deviceId: string): Nullable<ProtectDevices>;
    getFeatureFloat(option: string): Nullable<number | undefined>;
    getFeatureNumber(option: string): Nullable<number | undefined>;
    isNvrFeature(option: string, device?: ProtectDeviceConfig | ProtectNvrConfig): boolean;
    hasFeature(option: string, device?: ProtectDeviceConfig | ProtectNvrConfig): boolean;
    logFeature(option: string, message: string): void;
}
//# sourceMappingURL=nvr.d.ts.map