'use client'; import { create } from 'zustand'; import type { XiaobaoVersionRisk } from '@/lib/xiaobao-risk'; import { markXiaobaoWarningRead, type XiaobaoWarningReadState, } from '@/lib/xiaobao-warning-view'; interface XiaobaoWarningReadStoreState { readStates: XiaobaoWarningReadState[]; readStateLoaded: boolean; error?: string; fetchReadStates: () => Promise; markRiskRead: (userId: string, risk: XiaobaoVersionRisk, readAt?: string) => Promise; } const READ_STATE_CACHE_MS = 30_000; const READ_STATE_STORAGE_KEY = 'ftb_xiaobao_warning_views'; let lastReadStatesFetchAt = 0; function loadReadStates(): XiaobaoWarningReadState[] { try { if (typeof window === 'undefined') return []; return normalizeReadStates(JSON.parse(window.localStorage.getItem(READ_STATE_STORAGE_KEY) || '[]')); } catch {} return []; } function saveReadStates(rows: XiaobaoWarningReadState[]) { try { if (typeof window === 'undefined') return; window.localStorage.setItem(READ_STATE_STORAGE_KEY, JSON.stringify(rows)); } catch {} } function normalizeReadStates(rows: unknown): XiaobaoWarningReadState[] { if (!Array.isArray(rows)) return []; return rows.filter(isReadState); } function isReadState(row: unknown): row is XiaobaoWarningReadState { if (!row || typeof row !== 'object') return false; const item = row as Partial; return ( typeof item.userId === 'string' && typeof item.versionId === 'string' && typeof item.signature === 'string' && typeof item.readAt === 'string' ); } function mergeReadStates( remoteRows: XiaobaoWarningReadState[] = [], localRows: XiaobaoWarningReadState[] = [], ): XiaobaoWarningReadState[] { const byKey = new Map(); for (const row of [...remoteRows, ...localRows]) { const key = `${row.userId}::${row.versionId}`; const existing = byKey.get(key); if (!existing || row.readAt.localeCompare(existing.readAt) >= 0) { byKey.set(key, row); } } return Array.from(byKey.values()).sort((a, b) => b.readAt.localeCompare(a.readAt)); } export const useXiaobaoWarningReadStore = create((set, get) => ({ readStates: [], readStateLoaded: false, error: undefined, fetchReadStates: async () => { if (get().readStateLoaded && Date.now() - lastReadStatesFetchAt < READ_STATE_CACHE_MS) return; const rows = loadReadStates(); if (get().readStateLoaded && Date.now() - lastReadStatesFetchAt < READ_STATE_CACHE_MS) return; lastReadStatesFetchAt = Date.now(); set({ readStates: mergeReadStates(rows, get().readStates), readStateLoaded: true, error: undefined, }); }, markRiskRead: async (userId, risk, readAt = new Date().toISOString()) => { if (!userId) return; const optimistic = markXiaobaoWarningRead(get().readStates, userId, risk, readAt); set({ readStates: optimistic, error: undefined }); saveReadStates(optimistic); }, }));