import type { AppSupabaseClient } from '../supabase/client'; // `@anna_b` style — letters, digits, underscore, dot, dash, 2-32 chars. // Conservative on purpose: false negatives (a real username we don't match) // are recoverable (no notification fires); false positives (matching a // non-username) just become an INSERT that the FK check rejects. const MENTION_RE = /(?:^|[\s,;:!?(])@([a-zA-Z0-9_.-]{2,32})/g; export function parseMentionUsernames(plaintext: string): string[] { const out = new Set(); for (const m of plaintext.matchAll(MENTION_RE)) { if (m[1]) out.add(m[1].toLowerCase()); } return [...out]; } export interface MentionResolver { // Resolves an array of @usernames in this conversation to user-ids. // Returns only memberships that exist + are accepted. resolveUsernames(conversationId: string, usernames: string[]): Promise>; } export function makeMentionResolver(client: AppSupabaseClient): MentionResolver { return { async resolveUsernames(conversationId, usernames) { if (usernames.length === 0) return new Map(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const { data, error } = await (client as any) .from('conversation_members') .select('user_id, accepted, profiles!inner(username)') .eq('conversation_id', conversationId) .eq('accepted', true) .in('profiles.username', usernames); if (error) throw error; const out = new Map(); for (const row of (data ?? []) as Array<{ user_id: string; profiles: { username: string } }>) { out.set(row.profiles.username.toLowerCase(), row.user_id); } return out; }, }; } export async function insertMentions( client: AppSupabaseClient, messageId: string, conversationId: string, mentionedUserIds: string[], ): Promise { if (mentionedUserIds.length === 0) return; // eslint-disable-next-line @typescript-eslint/no-explicit-any const { error } = await (client as any).from('message_mentions').insert( mentionedUserIds.map((uid) => ({ message_id: messageId, mentioned_user_id: uid, conversation_id: conversationId, })), ); if (error) throw error; }