/**
 * Notifications REST client — typed wrapper around the slice-6 endpoints
 * (`/notifications`, `/notifications/unread-count`,
 * `/notifications/:id/read`, `/notifications/read-all`).
 *
 * Lives in `cms-admin` so the bell, the toast layer, and any future
 * background sync agent share one request path, error model, and DTO
 * shape. The wire format mirrors `NotificationDTO` in
 * `@actuate-media/cms-core/realtime/notifications.ts`; we duplicate the
 * shape here to avoid taking a hard dependency on the server module
 * (which carries Prisma types that don't belong in the admin bundle).
 */
import { cmsApi, getApiBase } from './api.js';
function failure(error, status) {
    return { ok: false, error, status };
}
function envelope(res) {
    if (res.error || res.data === undefined) {
        return failure(res.error ?? `Request failed (${res.status})`, res.status);
    }
    return { ok: true, result: res.data };
}
function buildQuery(opts = {}) {
    const params = new URLSearchParams();
    if (opts.includeRead)
        params.set('includeRead', 'true');
    if (opts.pageSize !== undefined)
        params.set('pageSize', String(opts.pageSize));
    const qs = params.toString();
    return qs ? `?${qs}` : '';
}
export async function listNotifications(opts = {}) {
    const res = await cmsApi(`/notifications${buildQuery(opts)}`, {
        method: 'GET',
    });
    return envelope(res);
}
export async function unreadCount() {
    const res = await cmsApi(`/notifications/unread-count`, {
        method: 'GET',
    });
    if (res.error || !res.data) {
        return failure(res.error ?? `Request failed (${res.status})`, res.status);
    }
    return { ok: true, result: res.data.count };
}
export async function markNotificationRead(id) {
    if (!id)
        return failure('notification id is required', 0);
    const res = await cmsApi(`/notifications/${encodeURIComponent(id)}/read`, {
        method: 'POST',
        body: '{}',
    });
    return envelope(res);
}
export async function markAllNotificationsRead() {
    const res = await cmsApi(`/notifications/read-all`, {
        method: 'POST',
        body: '{}',
    });
    if (res.error || !res.data) {
        return failure(res.error ?? `Request failed (${res.status})`, res.status);
    }
    return { ok: true, result: res.data.count };
}
export function subscribeNotificationStream(opts) {
    // The `cmsApi()` helper handles base-path / origin resolution for
    // fetch calls, but EventSource takes a raw URL. We mirror its
    // logic locally — `/api/cms/notifications/stream` is correct for
    // both the local dev server and any production deploy that mounts
    // the CMS API at the default prefix. Custom prefixes are handled by
    // the consumer override (factory).
    // Resolve against `setApiBase()` so a consumer mounting the CMS at
    // a non-default prefix (e.g. `/cms-api`) still hits the right SSE
    // endpoint — `EventSource` doesn't go through `cmsApi()`, so we
    // can't rely on its url-construction logic.
    const url = `${getApiBase()}/notifications/stream`;
    const make = opts.factory ?? defaultEventSourceFactory;
    const source = make(url);
    if (!source) {
        // No EventSource available (SSR? JSDOM without polyfill? Or the
        // injected factory deliberately returns null in tests). Return a
        // no-op handle so callers can compose without conditional logic.
        return { close: () => { } };
    }
    let closed = false;
    const onReady = (event) => {
        if (!opts.onReady)
            return;
        const data = parseEventData(event);
        if (data && typeof data.userId === 'string') {
            opts.onReady({ userId: data.userId });
        }
    };
    const onNotification = (event) => {
        const data = parseEventData(event);
        if (data && isNotification(data)) {
            opts.onNotification(data);
        }
    };
    const onError = (event) => {
        opts.onError?.(event);
    };
    source.addEventListener('ready', onReady);
    source.addEventListener('notification', onNotification);
    source.addEventListener('error', onError);
    return {
        close: () => {
            if (closed)
                return;
            closed = true;
            source.removeEventListener?.('ready', onReady);
            source.removeEventListener?.('notification', onNotification);
            source.removeEventListener?.('error', onError);
            source.close();
        },
    };
}
function defaultEventSourceFactory(url) {
    if (typeof EventSource === 'undefined')
        return null;
    // `withCredentials: true` so the admin session cookie travels with
    // the connection upgrade — every notification stream requires auth
    // and the same-origin cookie is the only credential we trust here.
    return new EventSource(url, { withCredentials: true });
}
function parseEventData(event) {
    // EventSource emits MessageEvents with `data: string`. Plain
    // `Event` (e.g. the `error` event) has no payload; ignore.
    const data = event.data;
    if (typeof data !== 'string')
        return null;
    try {
        return JSON.parse(data);
    }
    catch {
        return null;
    }
}
function isNotification(value) {
    if (!value || typeof value !== 'object')
        return false;
    const v = value;
    // Defensive: don't trust the wire blindly — verify the discriminant
    // properties our consumers will read. A drift in the server-side
    // wire format must surface as "missing fields" in the test suite,
    // not as runtime errors deep in the bell's render path.
    return (typeof v.id === 'string' &&
        typeof v.userId === 'string' &&
        typeof v.createdAt === 'string' &&
        !!v.payload &&
        typeof v.payload.kind === 'string');
}
/**
 * Cheap single-line summary for the bell list — the server gives us a
 * structured payload, but the dropdown wants one human-readable string
 * per row. Falls back to `Notification` when an unknown kind sneaks in
 * (defensive against future server-side additions).
 */
export function notificationSummary(notification) {
    const actor = notification.payload.actorName ?? 'Someone';
    switch (notification.payload.kind) {
        case 'comment_reply':
            return `${actor} replied: "${notification.payload.excerpt}"`;
        case 'comment_resolved':
            return `${actor} resolved your thread: "${notification.payload.rootExcerpt}"`;
        case 'comment_mention':
            return `${actor} mentioned you: "${notification.payload.excerpt}"`;
        default:
            return 'Notification';
    }
}
//# sourceMappingURL=notifications-client.js.map