import { EventEmitter } from "node:events";
/**
 * Stable categorical contract for the controller's current operating condition. Internal reducer transitions can change without breaking consumers because the
 * external surface stays a small ordered set: `healthy` < `degraded` < `stressed`.
 *
 *   - `healthy`: nominal. The controller is responsive; no recent symptom rate concerns.
 *   - `degraded`: elevated symptom rate. Reconnect-aggressive behavior should soften (e.g., defer non-urgent reconnects); critical paths still proceed.
 *   - `stressed`: sustained or library-acknowledged stress. Non-critical work should back off; only urgent paths (in-flight HKSV recordings, active live streams)
 *      should drive new requests at the controller.
 */
export type NvrHealthState = "degraded" | "healthy" | "stressed";
/**
 * Symptoms feed the reducer. Each variant carries the timestamp when the symptom occurred (stamped by the caller, so the reducer remains pure with respect to
 * wall time), and a discriminating `kind`. A `cameraId` is included on livestream variants so future policies can distinguish per-camera vs. correlated stress
 * without changing the symptom shape.
 *
 *   - `apiError`: an API request returned a failure (timeout, 4xx/5xx, network error, etc.).
 *   - `apiSuccess`: an API request returned a successful response. Counted as evidence of recovery; reduces the apparent symptom rate.
 *   - `livestreamStall`: a camera's livestream session detected a stall (the stall timer fired during streaming).
 *   - `livestreamRecovery`: a camera's livestream session resumed segment delivery after a stall.
 *   - `libraryThrottleEntered`: the unifi-protect library entered its internal throttle. A strong stress signal: the library has unilaterally paused
 *      communication with the controller for the duration of its cooldown.
 *   - `libraryThrottleReleased`: the unifi-protect library released its internal throttle. Evidence of recovery, but does not by itself reset the state...we wait
 *      for organic improvement (success events accumulating in the window) rather than auto-resetting on release.
 */
export type HealthSymptom = {
    at: number;
    kind: "apiError";
} | {
    at: number;
    kind: "apiSuccess";
} | {
    at: number;
    kind: "libraryThrottleEntered";
} | {
    at: number;
    kind: "libraryThrottleReleased";
} | {
    at: number;
    cameraId: string;
    kind: "livestreamRecovery";
} | {
    at: number;
    cameraId: string;
    kind: "livestreamStall";
};
/**
 * The reducer's persistent state. Exposed (via `NvrHealth.snapshot()`) for diagnostics and tests; not directly mutable.
 *
 *   - `state`: the current derived state, the externally-meaningful output.
 *   - `recentSymptoms`: the sliding-window buffer of symptoms within HEALTH_WINDOW_MS of the most recent observation. Old symptoms are evicted on each reduce.
 *   - `libraryThrottled`: latched flag for the library's throttle. While true, the state is forced to `stressed` regardless of symptom counts; releases on
 *      `libraryThrottleReleased`. Modeled as latched state rather than derived from buffer contents because the library throttle is a longer-lived condition than
 *      the symptom window.
 */
export interface HealthState {
    readonly libraryThrottled: boolean;
    readonly recentSymptoms: readonly HealthSymptom[];
    readonly state: NvrHealthState;
}
/**
 * Initial state. Exported so callers can construct a fresh state for tests or for replay scenarios without depending on the class.
 */
export declare function createInitialHealthState(): HealthState;
/**
 * Pure reducer. Given the previous state, a symptom, and the current wall time, returns the new state. Total: every symptom variant is handled, every transition
 * is explicit, and the function does not read external state. Callers that need to drive eviction without a new symptom can pass a synthetic `apiSuccess` (the
 * reducer evicts old entries on every call regardless of symptom kind) - in practice this is unnecessary because the consumer-facing `state` getter is correct
 * the moment the reducer has been called with any symptom.
 *
 * Stress evaluation prioritises the latched library-throttle flag: as long as the library has paused communication, we are stressed regardless of what the
 * symptom window says. Otherwise we count weighted symptoms and apply hysteresis against the previous state.
 */
export declare function reduceHealth(prev: HealthState, symptom: HealthSymptom, now: number): HealthState;
/**
 * Injectable clock for testability. Production uses {@link systemClock}; tests can substitute a deterministic clock to drive eviction without real time.
 */
export interface Clock {
    now(): number;
}
export declare const systemClock: Clock;
/**
 * Event surface for {@link NvrHealth}. Listeners receive the new state on every transition; the previous state is provided for context (so listeners can
 * render directional log lines like "healthy -> degraded" without tracking it themselves).
 */
export interface NvrHealthEvents {
    stateChange: [next: NvrHealthState, previous: NvrHealthState];
}
/**
 * Thin event-sink wrapper around the reducer. Consumers call `observe()` with a symptom; the class drives the reducer with the injected clock, replaces its
 * internal state, and emits `stateChange` on transitions. The class exists so consumers do not have to thread the previous state through reduce calls themselves;
 * the reducer remains pure and independently testable.
 *
 * Lifecycle integration notes for future maintainers:
 *
 *   - `suspend()` / `resume()` are normally driven from `ProtectNvr.transition()` rather than called directly. The NVR's phase is the single source of truth for
 *      "induced vs organic disruption"; this class follows along. Calling `suspend()` directly is fine for tests but in production code should go through phase
 *      transitions so all derived effects (logApiErrors, future signals) stay aligned.
 *   - `reset()` clears the symptom buffer and forces the state back to `healthy` without emitting `stateChange`. Called after a successful reconnect, whether
 *      following a disconnect (`ProtectNvr.connect()`) or an induced reboot (`ProtectNvr.resumeFromInducedReboot()`); pre-disruption symptoms are no longer
 *      relevant. We deliberately do not emit on reset because that would surface a "responsive again" message at every reset boundary, which is
 *      misleading...the reset is internal bookkeeping, not a recovery event.
 *   - There is no "force healthy" method. Use `reset()` if you need to clear state. A force-healthy that emits a recovery event would be a footgun.
 *   - Initial state is `healthy`; the plugin assumes normalcy until proven otherwise. The NVR suspends observation in `connecting` phase so initial-connect
 *      errors do not bias the baseline.
 */
export declare class NvrHealth extends EventEmitter<NvrHealthEvents> {
    private current;
    private suspended;
    private readonly clock;
    constructor(clock?: Clock);
    /**
     * Record a symptom. Drives the reducer with the current clock and emits a `stateChange` event if the derived state crossed a transition.
     *
     * The caller stamps the symptom's `at` field at the call site (typically `Date.now()`, but a different value is acceptable for replayed or queued symptoms).
     * The clock injected into NvrHealth is used as the eviction-window reference time on each call; it is not a fallback for `symptom.at`.
     *
     * No-op while suspended: symptoms observed during operations the plugin is intentionally driving are not signal we want to surface to consumers.
     */
    observe(symptom: HealthSymptom): void;
    /**
     * Stop observing symptoms until {@link resume} is called. Used to bracket operations the plugin is intentionally driving (controlled disconnects, scheduled
     * reboots) so the induced API errors and stalls do not get surfaced as if they were organic controller stress. Idempotent.
     */
    suspend(): void;
    /**
     * Resume observing symptoms. Idempotent. Does not change the current state...if the parent wants a clean slate after the suspend window, it should call
     * {@link reset} explicitly.
     */
    resume(): void;
    /**
     * Clear the symptom buffer and force the state back to `healthy`. Does NOT emit `stateChange`...this is bookkeeping for "forget what came before this point,"
     * not a recovery event the user should see surfaced. Typical use: after a successful reconnect following a controller reboot or disconnect, where the
     * pre-disruption history is no longer relevant.
     */
    reset(): void;
    /**
     * The current state. O(1) read; the reducer maintains it on every observation.
     */
    get state(): NvrHealthState;
    /**
     * Diagnostic snapshot of the full reducer state. Intended for debug feature flags and for tests; not part of the steady-state read path.
     */
    snapshot(): HealthState;
}
//# sourceMappingURL=nvr-health.d.ts.map