import type { Call, CalendarEvent, Message, Task } from "@slackwsh/contracts";
/**
 * "Should this interrupt someone, and what should it say?"
 *
 * One pure function for every notifiable realtime event. It lives here rather
 * than inline in the client's socket handler for two reasons: the rules are
 * the kind that rot silently (a duplicate toast for one edit, a notification
 * for your own action, ringing someone who is already looking at the call),
 * and a pure function is the only version of them that can be unit-tested
 * without a browser or a desktop build.
 *
 * Nothing in here touches `window`, `document` or the clock — everything
 * situational (who I am, what I'm looking at, what I just did, what I last
 * saw of this row) arrives in the context.
 */
export type MessageNotifyMode = "all" | "mentions" | "off";
export interface NotifyPrefs {
    /** "mentions" covers DMs too — a DM is addressed to you by definition. */
    messages: MessageNotifyMode;
    calls: boolean;
    tasks: boolean;
    calendar: boolean;
    sound: boolean;
    /** Right-side in-app flash toasts (2–3s). OS desktop toasts are separate. */
    inAppFlash: boolean;
}
/** Every message notified before preferences existed, so `all` is the
 * non-regressing default. */
export declare const DEFAULT_NOTIFY_PREFS: NotifyPrefs;
export type NotifiableEvent = {
    type: "message:created";
    message: Message;
    workspaceId?: number | string | null;
    channelId?: number | string | null;
    /** Needed to tell a DM from a room; the wire message doesn't carry it. */
    channelType?: string | null;
    channelName?: string | null;
} | {
    type: "call:started" | "call:updated" | "call:ended";
    call: Call;
} | {
    type: "task:created" | "task:updated";
    task: Task;
} | {
    type: "task:deleted";
    workspaceId: number | string;
    taskId: number | string;
    title: string;
    createdBy: number | string;
    deletedByUserId: number | string;
} | {
    type: "event:created" | "event:updated";
    event: CalendarEvent;
};
export interface NotifyContext {
    myUserId: number | null;
    /** Handle used to resolve @mentions; without it, only @here/@channel match. */
    myUsername?: string | null;
    prefs: NotifyPrefs;
    /** Current in-app path including query, e.g. "/channel?workspaceId=1&channelId=2". */
    currentRoute?: string | null;
    /** document.hidden — a backgrounded window is never "already looking at it". */
    hidden?: boolean;
    /** Display name for a user id, for notification copy. */
    nameFor?: (userId: number) => string | undefined;
    /**
     * The last version of this row this client saw. Realtime collapses every
     * mutation kind into one `*:updated` event, so "what changed" can only be
     * answered by diffing against what was already known.
     */
    previousTask?: Task | null;
    previousEvent?: CalendarEvent | null;
    previousCall?: Call | null;
    /**
     * True when this client itself caused the change. The server's `*:updated`
     * payload carries no actor, so an echo of your own edit is indistinguishable
     * from someone else's without this.
     */
    isSelfEcho?: (kind: "task" | "event" | "call" | "message", id: number) => boolean;
}
export type NotifySoundKind = "default" | "cheerful" | "short";
export interface NotificationDescription {
    title: string;
    body: string;
    /** In-app path to open on click. */
    route: string;
    /** Replaces an earlier notification about the same subject. */
    tag: string;
    group: string;
    /** Ignores the "I'm already looking at it" suppression. */
    urgent?: boolean;
    /** Post without a sound — something else is already making noise. */
    silent?: boolean;
    /** In-app chime. Omitted plays the default message ping. */
    sound?: NotifySoundKind;
    /** Play the assignment chime even if the Tasks page is already open. */
    chimeWhileLooking?: boolean;
    /** Skip the OS toast — used when the window is already on the target route. */
    skipToast?: boolean;
}
/**
 * Returns what to show for an event, or null to stay silent.
 *
 * The final gate applies to every family: if the window is focused and already
 * showing the exact route the notification would open, saying it out loud is
 * noise. `urgent` (an incoming call) opts out.
 */
export declare function describeNotification(event: NotifiableEvent, ctx: NotifyContext): NotificationDescription | null;
//# sourceMappingURL=notify-router.d.ts.map