/**
 * Per-user notifications driven by collaboration events.
 *
 * Owns the pure notification logic: kind discriminator, payload validation,
 * permission gates, and a storage-agnostic CRUD surface. The HTTP layer
 * (in `api/handlers.ts`) is a thin wrapper that handles auth + JSON
 * envelope; the comments hook layer (slice 6, `notification-triggers.ts`)
 * calls these helpers when a comment lifecycle event fires.
 *
 * Storage shape — see `packages/cms-core/prisma/cms-schema.prisma`, model
 * `DocumentNotification`. We require only a minimal Prisma surface so the
 * helpers can be tested against an in-memory fake.
 *
 * ## Discriminated payloads
 *
 * The `payload` Json column varies by `kind`. The TypeScript types here
 * are the source of truth for each variant; the service helpers narrow
 * by `kind` so callers can't accidentally insert a malformed payload.
 *
 * ## Mentions
 *
 * Slice 6 ships a deliberately simple mention parser: `@<userId>` tokens
 * in a comment body trigger a `comment_mention` notification. A richer
 * `@username` parser that resolves against the user table is a Phase 4
 * follow-up — the storage shape already supports it.
 */
/** Hard cap on returned notifications per `listForUser` call. */
export declare const MAX_NOTIFICATION_LIST_PAGE_SIZE = 200;
/** Default page size when the caller does not request one. */
export declare const DEFAULT_NOTIFICATION_LIST_PAGE_SIZE = 50;
/**
 * Notification kind discriminator. New variants extend the `Payload`
 * union — never replace existing kinds, because old rows live forever
 * in the table.
 */
export type NotificationKind = 'comment_reply' | 'comment_resolved' | 'comment_mention';
export interface CommentReplyPayload {
    /** User who replied. */
    actorId: string | null;
    actorName: string | null;
    /** First 120 chars of the reply body, for the bell preview. */
    excerpt: string;
}
export interface CommentResolvedPayload {
    actorId: string | null;
    actorName: string | null;
    /** Excerpt of the resolved root comment, so the bell can show context. */
    rootExcerpt: string;
}
export interface CommentMentionPayload {
    actorId: string | null;
    actorName: string | null;
    /** Excerpt of the body that mentioned the recipient. */
    excerpt: string;
}
export type NotificationPayload = ({
    kind: 'comment_reply';
} & CommentReplyPayload) | ({
    kind: 'comment_resolved';
} & CommentResolvedPayload) | ({
    kind: 'comment_mention';
} & CommentMentionPayload);
/** Public REST shape. Mirrors the row plus the discriminated payload. */
export interface NotificationDTO {
    id: string;
    userId: string;
    kind: NotificationKind;
    documentId: string | null;
    commentId: string | null;
    payload: NotificationPayload;
    readAt: string | null;
    createdAt: string;
}
export interface CreateNotificationInput {
    userId: string;
    documentId?: string | null;
    commentId?: string | null;
    payload: NotificationPayload;
}
export interface ListNotificationsFilter {
    userId: string;
    /** Default: false. When true, returns read + unread notifications. */
    includeRead?: boolean;
    /** Default: DEFAULT_NOTIFICATION_LIST_PAGE_SIZE. Hard upper bound applied. */
    pageSize?: number;
}
export type NotificationsError = {
    code: 'validation';
    message: string;
} | {
    code: 'forbidden';
    message: string;
} | {
    code: 'not_found';
    message: string;
};
export type NotificationsResult<T> = {
    ok: true;
    value: T;
} | {
    ok: false;
    error: NotificationsError;
};
/**
 * Minimal subset of Prisma's notification delegate we depend on. Keeping
 * this narrow lets the unit tests use a fake implementation that holds
 * rows in an array — no schema generation required.
 */
export interface DocumentNotificationRow {
    id: string;
    userId: string;
    kind: string;
    documentId: string | null;
    commentId: string | null;
    payload: unknown;
    readAt: Date | null;
    createdAt: Date;
}
export interface NotificationsDB {
    documentNotification: {
        create(args: {
            data: Omit<DocumentNotificationRow, 'id' | 'createdAt' | 'readAt'> & {
                readAt?: Date | null;
            };
        }): Promise<DocumentNotificationRow>;
        findUnique(args: {
            where: {
                id: string;
            };
        }): Promise<DocumentNotificationRow | null>;
        findMany(args: {
            where: {
                userId: string;
                readAt?: null | {
                    not: null;
                };
            };
            orderBy?: {
                createdAt: 'desc' | 'asc';
            };
            take?: number;
        }): Promise<DocumentNotificationRow[]>;
        update(args: {
            where: {
                id: string;
            };
            data: {
                readAt?: Date | null;
            };
        }): Promise<DocumentNotificationRow>;
        updateMany(args: {
            where: {
                userId: string;
                readAt?: null;
            };
            data: {
                readAt: Date;
            };
        }): Promise<{
            count: number;
        }>;
        count(args: {
            where: {
                userId: string;
                readAt: null;
            };
        }): Promise<number>;
    };
}
export declare function toNotificationDTO(row: DocumentNotificationRow): NotificationDTO;
export declare function isNotificationKind(value: unknown): value is NotificationKind;
/**
 * Insert a new notification. The payload's `kind` field is the source
 * of truth; `kind` on the DB row is derived from it so callers can't
 * drift them apart.
 */
export declare function createNotification(db: NotificationsDB, input: CreateNotificationInput): Promise<NotificationsResult<NotificationDTO>>;
/**
 * List notifications for a user, newest first. By default returns only
 * unread; pass `includeRead: true` for the full feed (the bell dropdown
 * uses both depending on the tab the user is on).
 */
export declare function listForUser(db: NotificationsDB, filter: ListNotificationsFilter): Promise<NotificationsResult<NotificationDTO[]>>;
/** Count unread notifications for the bell badge. */
export declare function unreadCount(db: NotificationsDB, userId: string): Promise<NotificationsResult<number>>;
/** Mark a single notification read (idempotent). */
export declare function markRead(db: NotificationsDB, id: string, actor: {
    userId: string;
}): Promise<NotificationsResult<NotificationDTO>>;
/** Mark every unread notification for the user as read. */
export declare function markAllRead(db: NotificationsDB, userId: string): Promise<NotificationsResult<{
    count: number;
}>>;
/**
 * Cheap excerpt builder for notification payloads. Markdown is kept as-is;
 * we just trim whitespace and clip to 120 chars so the bell can render
 * a single-line preview.
 */
export declare function excerptForNotification(body: string, max?: number): string;
export declare function extractMentionedUserIds(body: string): string[];
//# sourceMappingURL=notifications.d.ts.map