import { Database, Q } from '@nozbe/watermelondb' import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite' import { imDbSchema } from './schema' import { ConversationModel } from './ConversationModel' import { MessageModel } from './MessageModel' import type { ImMessage } from '../types' let _db: Database | null = null function getDb(): Database { if (!_db) throw new Error('[ImDatabase] Not initialized — call ImDatabase.init() first.') return _db } function conversationId(appId: string, userId: string, targetId: string, chatType: string): string { if (chatType === 'GROUP') return `${appId}:G:${targetId}` const [a, b] = [userId, targetId].sort() return `${appId}:S:${a}:${b}` } export const ImDatabase = { init(dbName = 'xuqm_im') { if (_db) return const adapter = new SQLiteAdapter({ schema: imDbSchema, dbName, jsi: true, onSetUpError: (err) => console.error('[ImDatabase] setup error', err), }) _db = new Database({ adapter, modelClasses: [ConversationModel, MessageModel], }) }, async saveMessage(msg: ImMessage, currentUserId: string): Promise { const db = getDb() const convId = conversationId(msg.appId, currentUserId, msg.toId, msg.chatType) const now = Date.now() await db.write(async () => { // upsert message const existing = await db .get('im_messages') .query(Q.where('server_id', msg.id)) .fetch() if (existing.length === 0) { await db.get('im_messages').create(m => { m.serverId = msg.id m.appId = msg.appId m.conversationId = convId m.fromUserId = msg.fromUserId m.toId = msg.toId m.chatType = msg.chatType m.msgType = msg.msgType m.content = msg.content m.status = msg.status m.mentionedUserIds = msg.mentionedUserIds ?? null m.serverCreatedAt = new Date(msg.createdAt).getTime() m.syncedAt = now }) } else { await existing[0].update(m => { m.status = msg.status m.content = msg.content m.syncedAt = now }) } // upsert conversation const convs = await db .get('im_conversations') .query(Q.where('app_id', msg.appId), Q.where('target_id', msg.toId)) .fetch() const msgTime = new Date(msg.createdAt).getTime() if (convs.length === 0) { await db.get('im_conversations').create(c => { c.appId = msg.appId c.targetId = msg.toId c.chatType = msg.chatType c.lastMsgId = msg.id c.lastMsgContent = msg.content c.lastMsgType = msg.msgType c.lastMsgTime = msgTime c.unreadCount = msg.fromUserId !== currentUserId ? 1 : 0 c.updatedAt = new Date() }) } else { await convs[0].update(c => { if (msgTime >= c.lastMsgTime) { c.lastMsgId = msg.id c.lastMsgContent = msg.content c.lastMsgType = msg.msgType c.lastMsgTime = msgTime } if (msg.fromUserId !== currentUserId) { c.unreadCount = c.unreadCount + 1 } c.updatedAt = new Date() }) } }) }, async getMessages(appId: string, targetId: string, chatType: string, currentUserId: string, limit = 50): Promise { const db = getDb() const convId = conversationId(appId, currentUserId, targetId, chatType) return db .get('im_messages') .query( Q.where('conversation_id', convId), Q.sortBy('server_created_at', Q.desc), Q.take(limit), ) .fetch() }, async getConversations(appId: string): Promise { const db = getDb() return db .get('im_conversations') .query( Q.where('app_id', appId), Q.sortBy('last_msg_time', Q.desc), ) .fetch() }, async markRead(appId: string, targetId: string): Promise { const db = getDb() const convs = await db .get('im_conversations') .query(Q.where('app_id', appId), Q.where('target_id', targetId)) .fetch() if (convs.length > 0) { await db.write(async () => { await convs[0].update(c => { c.unreadCount = 0 }) }) } }, async bulkSave(messages: ImMessage[], currentUserId: string): Promise { for (const msg of messages) { await this.saveMessage(msg, currentUserId) } }, isInitialized(): boolean { return _db !== null }, }