/**
 * Device Auto-Discovery Service
 *
 * Polls the Cloud device registry to discover other Crewly instances
 * belonging to the same user account. Enables automatic relay pairing
 * without manual configuration.
 *
 * Flow:
 * 1. On cloud login, register this device with the Cloud device API
 * 2. Poll GET /v1/devices to discover other online devices
 * 3. Auto-initiate relay connection to discovered peers
 * 4. Maintain heartbeat so other devices can discover us
 *
 * Implements exponential backoff on API failures to prevent runaway
 * request storms. After MAX_CONSECUTIVE_FAILURES, polling stops and
 * the service enters an error state.
 *
 * @see https://github.com/stevehuang0115/crewly/issues/relay-auto-discovery
 * @module services/cloud/device-auto-discovery.service
 */
import { EventEmitter } from 'events';
/** A remote device discovered via the cloud registry */
export interface DiscoveredDevice {
    /** Unique device identifier (UUID) */
    deviceId: string;
    /** Human-readable device name (hostname) */
    deviceName: string;
    /** User ID who owns this device */
    userId: string;
    /** Whether the device is currently online */
    online: boolean;
    /** Device role (orchestrator or agent) */
    role: 'orchestrator' | 'agent';
    /** ISO timestamp of last heartbeat */
    lastHeartbeatAt: string;
    /** Relay pairing code for auto-connect */
    pairingCode?: string;
    /** Cloud relay server URL */
    relayUrl?: string;
}
/** Discovery service configuration */
export interface DiscoveryConfig {
    /** Cloud API base URL */
    cloudUrl: string;
    /** Auth token for cloud API calls */
    token: string;
    /** This device's ID */
    deviceId: string;
    /** This device's name */
    deviceName: string;
    /** This device's role */
    role: 'orchestrator' | 'agent';
    /** Polling interval in ms (default: 30s) */
    pollIntervalMs?: number;
    /** Heartbeat interval in ms (default: 30s) */
    heartbeatIntervalMs?: number;
}
/** Auto-discovery service state */
export type DiscoveryState = 'stopped' | 'registering' | 'polling' | 'error';
/** Discovery constants */
export declare const DISCOVERY_CONSTANTS: {
    /** Default polling interval for device discovery (30 seconds) */
    readonly POLL_INTERVAL_MS: 30000;
    /** Default heartbeat interval (30 seconds) */
    readonly HEARTBEAT_INTERVAL_MS: 30000;
    /** Device considered offline after this threshold (5 minutes) */
    readonly OFFLINE_THRESHOLD_MS: 300000;
    /** Maximum devices per user account */
    readonly MAX_DEVICES_PER_USER: 10;
    /** Cloud device API path */
    readonly DEVICES_PATH: "/v1/devices";
    /** Base delay for exponential backoff (1 second) */
    readonly BACKOFF_BASE_MS: 1000;
    /** Maximum backoff delay (5 minutes) */
    readonly BACKOFF_MAX_MS: 300000;
    /** Exponential backoff multiplier */
    readonly BACKOFF_MULTIPLIER: 2;
    /** Stop polling after this many consecutive failures */
    readonly MAX_CONSECUTIVE_POLL_FAILURES: 20;
    /** Stop sending heartbeats after this many consecutive failures */
    readonly MAX_CONSECUTIVE_HEARTBEAT_FAILURES: 10;
    /** HTTP request timeout for device API calls (ms) */
    readonly REQUEST_TIMEOUT_MS: 15000;
};
/**
 * DeviceAutoDiscoveryService discovers other Crewly instances via Cloud.
 *
 * Uses self-scheduling setTimeout loops (instead of fixed setInterval) so
 * that polling and heartbeat intervals can be dynamically adjusted when
 * exponential backoff is active.
 *
 * Emits events:
 * - 'deviceFound' — new device discovered
 * - 'deviceLost' — previously online device went offline
 * - 'devicesUpdated' — full device list refreshed
 * - 'backoffActive' — backoff engaged after a failure (payload: { delay, failures })
 * - 'maxRetriesExceeded' — polling stopped due to too many failures
 */
export declare class DeviceAutoDiscoveryService extends EventEmitter {
    private static instance;
    private readonly logger;
    private state;
    private config;
    private pollTimer;
    private heartbeatTimer;
    private knownDevices;
    /** Number of consecutive poll failures (reset on success) */
    private consecutivePollFailures;
    /** Number of consecutive heartbeat failures (reset on success) */
    private consecutiveHeartbeatFailures;
    private constructor();
    static getInstance(): DeviceAutoDiscoveryService;
    static resetInstance(): void;
    /**
     * Get the current discovery state.
     */
    getState(): DiscoveryState;
    /**
     * Get all currently known devices.
     */
    getDevices(): DiscoveredDevice[];
    /**
     * Get only online devices (excluding self).
     */
    getOnlineDevices(): DiscoveredDevice[];
    /**
     * Get the number of consecutive poll failures.
     */
    getConsecutivePollFailures(): number;
    /**
     * Get the number of consecutive heartbeat failures.
     */
    getConsecutiveHeartbeatFailures(): number;
    /**
     * Start auto-discovery: register this device, begin polling + heartbeat.
     *
     * @param config - Discovery configuration with cloud credentials
     */
    start(config: DiscoveryConfig): Promise<void>;
    /**
     * Stop auto-discovery: clear timers and deregister.
     */
    stop(): void;
    /**
     * Calculate exponential backoff delay for the given number of consecutive
     * failures. Uses base × 2^(failures-1), capped at BACKOFF_MAX_MS.
     *
     * @param failures - Number of consecutive failures (0 = no backoff)
     * @returns Delay in ms (0 if no failures)
     */
    static calculateBackoff(failures: number): number;
    /**
     * Schedule the next poll. Uses the normal interval on success, or
     * exponential backoff on consecutive failures.
     */
    private schedulePoll;
    /**
     * Schedule the next heartbeat. Uses the normal interval on success, or
     * exponential backoff on consecutive failures.
     */
    private scheduleHeartbeat;
    /**
     * Register this device with the cloud device registry.
     */
    /**
     * Register this device with the cloud device registry.
     *
     * Uses PUT semantics to ensure upsert behavior — if the device already
     * exists (same deviceId), the server should update rather than create
     * a duplicate entry. The deviceId is included both in the body and
     * as a hint to the server.
     */
    private registerDevice;
    /**
     * Poll the cloud for other devices belonging to the same user. On failure,
     * increments the consecutive failure counter and triggers exponential
     * backoff. After MAX_CONSECUTIVE_POLL_FAILURES, stops polling entirely.
     */
    private pollDevices;
    /**
     * Handle a poll failure: increment counter, log with backoff info, and
     * stop polling if the max retry limit is exceeded.
     *
     * @param reason - Human-readable failure reason
     */
    private handlePollFailure;
    /**
     * Send a heartbeat to the cloud to keep this device marked as online.
     * On failure, increments heartbeat failure counter and backs off.
     * After MAX_CONSECUTIVE_HEARTBEAT_FAILURES, escalates the log level
     * from `warn` to `error` (one-shot edge event) but keeps retrying at
     * the capped backoff so heartbeats resume automatically when
     * connectivity returns. See issue #296.
     */
    private sendHeartbeat;
    /**
     * Handle a heartbeat failure: increment counter and log a warning. The
     * timer is NOT stopped — heartbeats continue at the capped backoff
     * (`BACKOFF_MAX_MS`, currently 5 min) so that whenever the network
     * or Cloud server recovers, `lastHeartbeatAt` resumes updating
     * automatically.
     *
     * Issue #296 background: previously, hitting
     * `MAX_CONSECUTIVE_HEARTBEAT_FAILURES` (10) permanently cleared
     * `heartbeatTimer`. A transient network blip 24-48h ago would freeze
     * `lastHeartbeatAt` forever even after sync recovered — the Cloud
     * dashboard then showed the device as offline indefinitely. Removing
     * the hard stop trades a small (capped) ongoing log volume for
     * self-healing.
     *
     * After `MAX_CONSECUTIVE_HEARTBEAT_FAILURES` we still emit an `error`
     * log so persistent failures stay visible in observability, but the
     * timer keeps firing at the 5-min cap until something succeeds.
     *
     * @param reason - Human-readable failure reason
     */
    private handleHeartbeatFailure;
}
//# sourceMappingURL=device-auto-discovery.service.d.ts.map