import type { ThreadListEntry, ThreadUnread, TimelineEvent, TimelineItem } from './types';

export interface ThreadEntry {
  room_id: string;
  root_event_id: string;
  preview: string;
  sender: string;
  reply_count: number;
  latest_ms: number;
  pinned: boolean;
  unread: number;
  mentions: number;
}

function previewOf(event: TimelineEvent): string {
  const content = event.content;
  if (content.kind === 'Message') return content.message.media?.caption ?? content.message.body;
  if (content.kind === 'Sticker') return content.body || 'Sticker';
  if (content.kind === 'Poll') return content.question;
  return '';
}

function entryFor(roomId: string, event: TimelineEvent, pinned: boolean): ThreadEntry | null {
  if (!event.event_id || event.thread_root) return null;
  return {
    room_id: roomId,
    root_event_id: event.event_id,
    preview: previewOf(event),
    sender: event.sender.display_name ?? event.sender.user_id,
    reply_count: event.thread_summary?.reply_count ?? 0,
    latest_ms: event.thread_summary?.latest_event_timestamp_ms ?? event.timestamp_ms,
    pinned,
    unread: 0,
    mentions: 0
  };
}

/**
 * Pinned threads first, then by latest activity. The server's thread list wins over roots seen in the
 * loaded timeline, and pins or unread roots that neither has loaded still get a row.
 */
export function roomThreads(
  roomId: string,
  items: TimelineItem[],
  pinnedRoots: string[],
  listed: ThreadListEntry[] = [],
  unreads: Record<string, ThreadUnread> = {}
): ThreadEntry[] {
  const pinned = new Set(pinnedRoots);
  const found = new Map<string, ThreadEntry>();
  for (const item of items) {
    if (item.kind !== 'event') continue;
    const id = item.event.event_id;
    if (!id || (!pinned.has(id) && (item.event.thread_summary?.reply_count ?? 0) === 0)) continue;
    const entry = entryFor(roomId, item.event, pinned.has(id));
    if (entry) found.set(id, entry);
  }
  for (const listedEntry of listed) {
    const entry = entryFor(roomId, listedEntry.root, pinned.has(listedEntry.root_event_id));
    if (!entry) continue;
    found.set(listedEntry.root_event_id, { ...entry, reply_count: listedEntry.reply_count, latest_ms: listedEntry.latest_activity_ms });
  }
  for (const root of new Set([...pinnedRoots, ...Object.keys(unreads)])) {
    if (!found.has(root)) {
      found.set(root, { room_id: roomId, root_event_id: root, preview: '', sender: '', reply_count: 0, latest_ms: 0, pinned: pinned.has(root), unread: 0, mentions: 0 });
    }
  }
  for (const [root, unread] of Object.entries(unreads)) {
    const entry = found.get(root);
    if (entry) found.set(root, { ...entry, unread: unread.messages, mentions: unread.mentions });
  }
  return [...found.values()].sort((a, b) => Number(b.pinned) - Number(a.pinned) || b.latest_ms - a.latest_ms);
}

export function threadMatches(entry: ThreadEntry, query: string): boolean {
  if (!query) return true;
  return entry.preview.toLowerCase().includes(query) || entry.sender.toLowerCase().includes(query);
}
