import { db, type RoomSyncMeta, type CachedMessage } from './index';
import type { MessageNewService, ReactionGroup } from '@/socket';

function toCache(m: MessageNewService): CachedMessage {
  return {
    id: m.id,
    roomId: m.room?.id ?? '',
    seq: m.seq,
    sender_type: m.sender_type,
    thread: m.thread ?? null,
    in_reply_to: m.in_reply_to ?? null,
    content: m.content,
    content_type: m.content_type,
    pinned: m.pinned ?? false,
    system_type: m.system_type,
    metadata: m.metadata,
    thinking_content: m.thinking_content,
    thinking_is_chunked: m.thinking_is_chunked,
    send_at: m.send_at,
    edited_at: undefined,
    _localReactions: m.reactions ?? [],
    _isStreaming: false,
    _thinkingContent: m.thinking_content,
    senderId: m.sender?.id ?? '',
    senderName: m.sender?.display_name ?? m.sender?.username ?? 'Unknown',
    senderAvatar: m.sender?.avatar_url ?? '',
  };
}

function fromCache(m: CachedMessage): MessageNewService {
  return {
    id: m.id,
    seq: m.seq,
    room: { id: m.roomId, name: '' },
    sender_type: 'user',
    sender: {
      id: m.senderId,
      username: '',
      display_name: m.senderName,
      avatar_url: m.senderAvatar,
    },
    thread: m.thread,
    in_reply_to: m.in_reply_to,
    content: m.content,
    content_type: m.content_type,
    pinned: m.pinned,
    system_type: m.system_type,
    metadata: m.metadata,
    thinking_content: m._thinkingContent,
    thinking_is_chunked: null,
    send_at: m.send_at,
    reactions: m._localReactions,
  };
}

export const MessageRepository = {
  async getMessages(roomId: string, beforeSeq?: number, limit = 50): Promise<MessageNewService[]> {
    const query = db.messages
      .where('[roomId+seq]')
      .between([roomId, 0], [roomId, beforeSeq ?? Number.MAX_SAFE_INTEGER], true, false);
    const cached = await query.reverse().limit(limit).toArray();
    return cached.reverse().map(fromCache);
  },

  async saveMessages(
    roomId: string,
    msgs: MessageNewService[],
    options?: { serverRuntimeId?: string },
  ): Promise<void> {
    if (msgs.length === 0) return;

    await db.transaction('rw', [db.messages, db.roomSyncMeta], async () => {
      const existingIds = msgs.map(m => m.id);
      const existing = await db.messages.bulkGet(existingIds);
      const existingReactions = new Map<string, ReactionGroup[]>();
      existing.forEach((ex) => {
        if (ex?._localReactions?.length) {
          existingReactions.set(ex.id, ex._localReactions);
        }
      });

      const merged = msgs.map(m => {
        const cached = toCache(m);
        const preserved = existingReactions.get(m.id);
        if (preserved?.length && !cached._localReactions.length) {
          return { ...cached, _localReactions: preserved };
        }
        return cached;
      });

      await db.messages.bulkPut(merged);

      const realMsgs = msgs
        .filter(m => m.seq !== Number.MAX_SAFE_INTEGER)
        .sort((a, b) => a.seq - b.seq);
      if (realMsgs.length === 0) return;

      const batchMin = realMsgs[0].seq;
      const batchMax = realMsgs[realMsgs.length - 1].seq;

      const existingMeta = await db.roomSyncMeta.get(roomId);
      const gaps = [...(existingMeta?.gaps ?? [])];
      if (existingMeta && batchMin > existingMeta.maxSeq + 1) {
        gaps.push([existingMeta.maxSeq + 1, batchMin - 1]);
      }

      const newMeta: RoomSyncMeta = {
        roomId,
        maxSeq: Math.max(existingMeta?.maxSeq ?? 0, batchMax),
        minSeq: Math.min(existingMeta?.minSeq ?? Number.MAX_SAFE_INTEGER, batchMin),
        gaps: gaps.slice(-10),
        lastActive: Date.now(),
        serverRuntimeId: options?.serverRuntimeId ?? existingMeta?.serverRuntimeId,
      };
      await db.roomSyncMeta.put(newMeta);
    });
  },

  async getSyncMeta(roomId: string): Promise<RoomSyncMeta | undefined> {
    return db.roomSyncMeta.get(roomId);
  },

  async getLastSeq(roomId: string): Promise<number> {
    const meta = await db.roomSyncMeta.get(roomId);
    return meta?.maxSeq ?? 0;
  },

  async clearRoom(roomId: string): Promise<void> {
    await db.transaction('rw', [db.messages, db.roomSyncMeta], async () => {
      await db.messages.where('roomId').equals(roomId).delete();
      await db.roomSyncMeta.delete(roomId);
    });
  },

  /** Prune old rooms to keep storage under ~50MB. Keeps 10 most recent rooms. */
  async prune(maxRooms = 10): Promise<void> {
    const allMeta = await db.roomSyncMeta.orderBy('lastActive').reverse().toArray();
    const toDelete = allMeta.slice(maxRooms);
    for (const meta of toDelete) {
      await MessageRepository.clearRoom(meta.roomId);
    }
  },
};
