import { OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import Redis from "ioredis";
import { type EntityIdInput } from "@slackwsh/messaging";
export type PresenceStatus = "active" | "away" | "offline";
export interface PresenceRecord {
    status: PresenceStatus;
    lastSeen: string;
}
/**
 * Presence per ARCHITECTURE §4.4 — "the one piece Elixir gave for free."
 * Hand-built on Redis, deliberately best-effort and self-healing rather than
 * strongly consistent (never used for authorisation).
 *
 * Redis keys:
 *   presence:{workspaceId}     HASH   userId -> JSON {status, lastSeen}
 *   presence:sock:{socketId}   STRING userId, TTL 60s (liveness token)
 *   presence:user:{userId}     SET    socketIds currently held by this user
 *
 * Device count is derived via SCARD on `presence:user:{userId}` rather than
 * stored and incremented separately — self-correcting, can't drift out of
 * sync with the SET it's supposed to describe.
 *
 * Single-node caveat: the sweeper below runs as a plain `setInterval` in
 * this gateway process. That's correct for one node; a multi-node
 * deployment needs this to run as a single elected job (or in `apps/worker`)
 * so N gateway nodes don't all sweep the same keys redundantly — not built,
 * since this dev environment only ever runs one gateway node.
 */
export declare class PresenceService implements OnModuleInit, OnModuleDestroy {
    private readonly redis;
    private readonly logger;
    private sweepTimer?;
    constructor(redis: Redis);
    onModuleInit(): void;
    onModuleDestroy(): void;
    /** Called on `channel:join` for a workspace not yet registered on this
     * socket. Returns true the first time this user comes online (deviceCount
     * 0 -> 1) — the caller broadcasts `presence:changed` only in that case, so
     * opening a second tab doesn't spam a redundant "still online" event. */
    connect(workspaceId: EntityIdInput, userId: EntityIdInput, socketId: string): Promise<{
        becameOnline: boolean;
    }>;
    heartbeat(socketId: string, userId: EntityIdInput, workspaceIds: Iterable<EntityIdInput>, active: boolean): Promise<void>;
    /** Only publish `offline` when deviceCount reaches zero — closing one tab
     * must not mark a user offline while they're still active elsewhere. This
     * is the specific defect §4.4 exists to prevent. */
    disconnect(workspaceId: EntityIdInput, userId: EntityIdInput, socketId: string): Promise<{
        becameOffline: boolean;
    }>;
    getWorkspacePresence(workspaceId: EntityIdInput): Promise<Record<string, PresenceRecord>>;
    private writeStatus;
    private requestedStatus;
    /** Reconciles `presence:user:{userId}` sets against surviving TTL tokens —
     * covers a gateway process dying ungracefully (no disconnect event ever
     * fires) so I9's "shown offline within 60s" holds even then. Iterates via
     * SCAN rather than KEYS to avoid blocking Redis on a large keyspace. */
    private sweep;
    private anyActiveSocket;
}
//# sourceMappingURL=presence.service.d.ts.map