/**
 * IndexedDB storage for room messages.
 * Provides persistent message storage that survives page reloads and works offline.
 */
import type { MessageWithMeta } from '@/contexts/room-context';
import type { ReactionGroup } from '@/contexts';

const DB_NAME = 'room-messages';
const DB_VERSION = 1;
const STORE_MESSAGES = 'messages';
const STORE_REACTIONS = 'reactions';

let dbPromise: Promise<IDBDatabase> | null = null;

function openDB(): Promise<IDBDatabase> {
  if (dbPromise) return dbPromise;

  dbPromise = new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onerror = () => reject(request.error);

    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = (event) => {
      const db = (event.target as IDBOpenDBRequest).result;

      // Messages store: keyed by message_id, indexed by room_id and seq
      if (!db.objectStoreNames.contains(STORE_MESSAGES)) {
        const msgStore = db.createObjectStore(STORE_MESSAGES, { keyPath: 'id' });
        msgStore.createIndex('by_room', 'room', { unique: false });
        msgStore.createIndex('by_room_seq', ['room', 'seq'], { unique: false });
      }

      // Reactions store: keyed by message_id
      if (!db.objectStoreNames.contains(STORE_REACTIONS)) {
        db.createObjectStore(STORE_REACTIONS, { keyPath: 'message_id' });
      }
    };
  });

  return dbPromise;
}

export interface StoredMessage {
  id: string;
  room: string;
  seq: number;
  sender_type: string;
  sender_id?: string;
  display_name?: string;
  thread_id?: string;
  content: string;
  content_type: string;
  edited_at?: string;
  send_at: string;
  revoked?: string;
  revoked_by?: string;
  reactions?: ReactionGroup[];
}

function msgToStored(msg: MessageWithMeta): StoredMessage {
  return {
    id: msg.id,
    room: msg.room,
    seq: msg.seq,
    sender_type: msg.sender_type,
    sender_id: msg.sender_id ?? undefined,
    display_name: msg.display_name ?? undefined,
    thread_id: msg.thread_id,
    content: msg.content,
    content_type: msg.content_type,
    edited_at: msg.edited_at ?? undefined,
    send_at: msg.send_at,
    revoked: msg.revoked ?? undefined,
    revoked_by: msg.revoked_by ?? undefined,
    reactions: msg.reactions,
  };
}

function storedToMsg(stored: StoredMessage): MessageWithMeta {
  return {
    ...stored,
    thread: stored.thread_id,
    display_content: stored.content,
    is_streaming: false,
  };
}

/** Save a single message to IndexedDB */
export async function saveMessage(msg: MessageWithMeta): Promise<void> {
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readwrite');
    tx.objectStore(STORE_MESSAGES).put(msgToStored(msg));
    await new Promise<void>((resolve, reject) => {
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch (err) {
    console.warn('[IDB] saveMessage failed:', err);
  }
}

/** Save multiple messages in a single transaction */
export async function saveMessages(_roomId: string, msgs: MessageWithMeta[]): Promise<void> {
  if (msgs.length === 0) return;
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readwrite');
    const store = tx.objectStore(STORE_MESSAGES);
    for (const msg of msgs) {
      store.put(msgToStored(msg));
    }
    await new Promise<void>((resolve, reject) => {
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch (err) {
    console.warn('[IDB] saveMessages failed:', err);
  }
}

/** Load all messages for a room from IndexedDB, sorted by seq ascending */
export async function loadMessages(roomId: string): Promise<MessageWithMeta[]> {
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readonly');
    const index = tx.objectStore(STORE_MESSAGES).index('by_room');
    const request = index.getAll(IDBKeyRange.only(roomId));

    return new Promise((resolve, reject) => {
      request.onsuccess = () => {
        const results: StoredMessage[] = request.result;
        const msgs = results.map(storedToMsg);
        msgs.sort((a, b) => a.seq - b.seq);
        resolve(msgs);
      };
      request.onerror = () => reject(request.error);
    });
  } catch (err) {
    console.warn('[IDB] loadMessages failed:', err);
    return [];
  }
}

/** Delete a message by id */
export async function deleteMessage(messageId: string): Promise<void> {
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readwrite');
    tx.objectStore(STORE_MESSAGES).delete(messageId);
    await new Promise<void>((resolve, reject) => {
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch (err) {
    console.warn('[IDB] deleteMessage failed:', err);
  }
}

/** Clear all messages for a room */
export async function clearRoomMessages(roomId: string): Promise<void> {
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readwrite');
    const index = tx.objectStore(STORE_MESSAGES).index('by_room');
    const request = index.openCursor(IDBKeyRange.only(roomId));

    await new Promise<void>((resolve, reject) => {
      request.onsuccess = () => {
        const cursor = request.result;
        if (cursor) {
          cursor.delete();
          cursor.continue();
        }
      };
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
    });
  } catch (err) {
    console.warn('[IDB] clearRoomMessages failed:', err);
  }
}

/** Load older messages from IDB (seq < beforeSeq), sorted ascending, up to `limit` */
export async function loadOlderMessagesFromIdb(
  roomId: string,
  beforeSeq: number,
  limit = 50,
): Promise<MessageWithMeta[]> {
  try {
    const db = await openDB();
    const tx = db.transaction(STORE_MESSAGES, 'readonly');
    const index = tx.objectStore(STORE_MESSAGES).index('by_room_seq');

    // Compound key range: roomId + any seq less than beforeSeq
    const range = IDBKeyRange.bound([roomId, 0], [roomId, beforeSeq - 1]);
    const request = index.openCursor(range, 'prev'); // 'prev' = descending seq (newest first)
    // We want oldest before `beforeSeq`, so after getting `limit` items in 'prev' order,
    // reverse to ascending seq.
    const collected: StoredMessage[] = [];

    return new Promise((resolve, reject) => {
      request.onsuccess = () => {
        const cursor = request.result;
        if (cursor && collected.length < limit) {
          collected.push(cursor.value);
          cursor.continue();
        } else {
          // Reverse back to ascending order (oldest first)
          const msgs = collected.reverse().map(storedToMsg);
          resolve(msgs);
        }
      };
      request.onerror = () => reject(request.error);
    });
  } catch (err) {
    console.warn('[IDB] loadOlderMessagesFromIdb failed:', err);
    return [];
  }
}

/** Get the highest seq number for a room (for dedup) */
export async function getMaxSeq(roomId: string): Promise<number> {
  try {
    const msgs = await loadMessages(roomId);
    return msgs.length > 0 ? msgs[msgs.length - 1].seq : -1;
  } catch {
    return -1;
  }
}
