import type { ConnectionState } from "unifi-protect";
import type { Nullable } from "homebridge-plugin-utils";
import type { NvrPhase } from "./nvr.ts";
/**
 * The startup-resilient connect retry policy, as a pure stateful predicate factory. Authentication faults get a small consecutive budget so a controller still sorting
 * out its own auth state recovers, but genuinely-wrong credentials fail fast rather than looping forever. Any non-auth
 * fault (network, transient) resets the budget, so a slow-to-appear controller is retried without bound. A pure factory so the budget logic is testable without standing
 * up a client - the separation of the retry *decision* from the connect *effect*.
 *
 * @param limit - The consecutive-auth-failure ceiling. Defaults to {@link PROTECT_AUTH_FAILURE_LIMIT}.
 *
 * @returns A `shouldRetry` predicate suitable for the retry() options; it closes over the running consecutive-auth count.
 */
export declare function createConnectRetryPolicy(limit?: number): {
    shouldRetry: (error: unknown) => boolean;
};
/**
 * Whether a lifecycle phase change is legal. Two changes are refused: a same-phase change (the long-standing no-op), and any change OUT of "shuttingDown".
 * "shuttingDown" is one-way because entering it aborts the terminal shutdown signal and tears down the whole observe/firehose tree - nothing may resurrect a
 * torn-down controller's lifecycle, so a stale reboot timer, a late-resolving connect, or any other deferred wake that tries to move the phase onward is
 * rejected here rather than at each call site. Entering "shuttingDown" from any other phase stays legal. An options object because both arguments share the
 * NvrPhase type and a positional swap would silently invert the predicate - the same reason {@link shouldResumeFromInducedReboot} takes a named shape.
 */
export declare function canTransition(options: {
    from: NvrPhase;
    to: NvrPhase;
}): boolean;
/**
 * Whether a livestream disruption is INDUCED - the controller is rebooting or shutting down because we asked it to - rather than ORGANIC. An induced disruption is
 * expected and already narrated at the controller level; an organic one is a single camera unexpectedly in trouble. It deliberately excludes "connecting", which is an
 * organic startup/reconnection window where a disruption should still surface to the user. The recovery policy's induced-disruption guard consults this directly. The
 * per-camera disruption logs consult a SUPERSET - induced OR {@link isWithinRebootRecency} - because a controller reboot's post-return re-establishment blip fires
 * after the plugin has already concluded the reboot and left the induced phase, so the phase alone no longer recognizes that blip as the reboot's tail.
 */
export declare function isInducedDisruption(phase: NvrPhase): boolean;
export declare function isWithinRebootRecency(options: {
    lastRebootMs: Nullable<number>;
    nowMs: number;
    windowMs: number;
}): boolean;
export declare function shouldResumeFromInducedReboot(options: {
    from: ConnectionState;
    phase: NvrPhase;
    to: ConnectionState;
}): boolean;
/**
 * Create a per-episode livestream-recovery quiet-classification latch. A livestream recovery episode opens (interruption) and later closes (recovery) on two independent
 * unifi-protect library events; the recovery edge cannot read the controller phase reliably (the controller has already returned), so it must consult what the
 * interruption edge recorded. What it records is `quiet` - whether the episode should be logged quietly (the reboot-tail-or-induced case) rather than at warn (a
 * genuine single-camera drop). Keyed by the library's livestream pool `key` (one slot per concurrent session, not per camera). The owner records at interruption and
 * consumes at recovery; `forgetCamera` reclaims any entries a removed camera left behind (a started-without-recovery episode would otherwise never be consumed). The Map
 * is owned here, never exposed - mirroring createConnectRetryPolicy's encapsulated state.
 */
export declare function createLivestreamEpisodeLatch(): {
    consume: (key: string) => boolean;
    forgetCamera: (cameraId: string) => void;
    record: (key: string, cameraId: string, quiet: boolean) => void;
};
/**
 * The HomeKit-membership delta between the controller's adopted-id set and the ids we have already configured, as a pure set diff. `toAdd` is the adopted ids we have
 * not configured; `toRemove` is the configured ids no longer adopted. This single set diff drives the whole of device sync - one engine for both adoption and
 * unadoption; isolating it makes the reconcile read as decision (this pure diff) then effect (add/remove). Order-preserving relative to the inputs.
 *
 * @param adoptedIds    - The controller's current adopted ids for one device category.
 * @param configuredIds - The ids we have already configured in that category.
 *
 * @returns The ids to add and the ids to remove.
 */
export declare function membershipDelta(adoptedIds: readonly string[], configuredIds: readonly string[]): {
    toAdd: string[];
    toRemove: string[];
};
/**
 * Whether a completed HTTP request belongs to this controller, from the unifi-protect library's process-global `http:request:end` diagnostic payload. The channel
 * carries every request from every client in the process, so each NVR filters by host to keep its health scoped to its own controller. The payload's `host` is the exact
 * address the client was built against, and this NVR passes its configured `address` to `connect()`, so an exact string comparison against that same address
 * attributes a request precisely - both strings descend from one configuration value. Exact identity keeps the port significant: a ported address ("1.2.3.4:8443")
 * attributes to itself, two controllers on one host at different ports stay distinct, and a mixed-case address matches itself verbatim.
 *
 * @param options - The configured controller `address` and the payload's reported `host`.
 *
 * @returns `true` when the request's host is exactly this controller's configured address.
 */
export declare function isRequestForController(options: {
    address: string;
    host: string;
}): boolean;
/**
 * Whether a completed HTTP request was successful, from the unifi-protect library's `http:request:end` diagnostic payload: no transport-level error, and a 2xx status.
 * Everything else - a transport error, an absent status, or a non-2xx response - is a failure. Isolated so the request-outcome-to-health-symptom mapping the NVR feeds
 * into NvrHealth is testable apart from the diagnostics-channel subscription that drives it.
 *
 * @param payload - The request-end payload (only `error` and `statusCode` are read).
 *
 * @returns `true` when the request succeeded.
 */
export declare function isSuccessfulRequest(payload: {
    error?: string;
    statusCode?: number;
}): boolean;
export declare function computeStableSince(options: {
    hasStabilizedBefore: boolean;
    nowMs: number;
    uptimeMs: number;
    windowMs: number;
}): number;
export declare function isStabilityWindowElapsed(options: {
    nowMs: number;
    stableSinceMs: Nullable<number>;
    windowMs: number;
}): boolean;
//# sourceMappingURL=nvr-policy.d.ts.map