export type NotificationType = "success" | "error" | "info" | "warning"; export interface AppNotification { id: string; type: NotificationType; title: string; message?: string; href?: string; read: boolean; createdAt: string; } const STORAGE_KEY_PREFIX = "haixun-notifications"; const MAX_ITEMS = 80; type Listener = () => void; const listeners = new Set(); let scopedUserId: string | null = null; let cachedNotifications: AppNotification[] = []; let cachedUnreadCount = 0; let initialized = false; function storageKey() { return scopedUserId ? `${STORAGE_KEY_PREFIX}-${scopedUserId}` : `${STORAGE_KEY_PREFIX}-anonymous`; } function emit() { listeners.forEach((fn) => fn()); } /** 切換登入使用者時重載通知,避免看到別人的 local 紀錄。 */ export function setNotificationScope(userId: string) { if (scopedUserId === userId) return; scopedUserId = userId; initialized = false; cachedNotifications = []; cachedUnreadCount = 0; ensureInitialized(); } function load(): AppNotification[] { if (typeof window === "undefined") return []; try { const raw = localStorage.getItem(storageKey()); if (!raw) return []; return JSON.parse(raw) as AppNotification[]; } catch { return []; } } function refreshUnreadCount() { cachedUnreadCount = cachedNotifications.filter((n) => !n.read).length; } function ensureInitialized() { if (initialized || typeof window === "undefined") return; cachedNotifications = load(); refreshUnreadCount(); initialized = true; } function createNotificationId(): string { const c = typeof globalThis !== "undefined" ? globalThis.crypto : undefined; if (c && typeof c.randomUUID === "function") { return c.randomUUID(); } return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`; } function save(items: AppNotification[]) { cachedNotifications = items.slice(0, MAX_ITEMS); refreshUnreadCount(); initialized = true; if (typeof window !== "undefined") { try { localStorage.setItem(storageKey(), JSON.stringify(cachedNotifications)); } catch { // localStorage 已滿或不可寫入(例如 Safari 隱私模式),略過持久化 } } emit(); } export function subscribe(listener: Listener): () => void { listeners.add(listener); return () => listeners.delete(listener); } export function getNotifications(): AppNotification[] { ensureInitialized(); return cachedNotifications; } export function getUnreadCount(): number { ensureInitialized(); return cachedUnreadCount; } /** * 寫入通知中心 — 僅用於 AI 產出、背景任務完成/失敗等可稍後查看的訊息。 * 儲存、刪除、表單驗證等直接操作請用頁面內 InlineAlert(useActionFeedback)。 */ export function notify(params: { type: NotificationType; title: string; message?: string; href?: string; }) { ensureInitialized(); const item: AppNotification = { id: createNotificationId(), type: params.type, title: params.title, message: params.message, href: params.href, read: false, createdAt: new Date().toISOString(), }; save([item, ...cachedNotifications]); return item.id; } export function markRead(id: string) { ensureInitialized(); save(cachedNotifications.map((n) => (n.id === id ? { ...n, read: true } : n))); } export function markAllRead() { ensureInitialized(); save(cachedNotifications.map((n) => ({ ...n, read: true }))); } export function clearAll() { save([]); } export function removeNotification(id: string) { ensureInitialized(); save(cachedNotifications.filter((n) => n.id !== id)); }