import asyncio from sqlalchemy.ext.asyncio import AsyncSession from app.chats.models import Chat from app.chats import repository as chats_repository from app.chats.schemas import ChatDiscoverRead from app.chats.service import serialize_chat_for_user from app.messages.service import search_messages from app.search.schemas import GlobalSearchRead from app.users.service import search_users_by_username async def global_search( db: AsyncSession, *, user_id: int, query: str, users_limit: int = 10, chats_limit: int = 10, messages_limit: int = 10, ) -> GlobalSearchRead: normalized = query.strip() if len(normalized.lstrip("@")) < 2: return GlobalSearchRead(users=[], chats=[], messages=[]) users_task = search_users_by_username( db, query=normalized, limit=max(1, min(users_limit, 50)), exclude_user_id=user_id, ) messages_task = search_messages( db, user_id=user_id, query=normalized, chat_id=None, limit=max(1, min(messages_limit, 50)), ) users, messages = await asyncio.gather(users_task, messages_task) # Combine own chats and discoverable public chats into one chat result list. own_chats = await chats_repository.list_user_chats( db, user_id=user_id, limit=max(1, min(chats_limit, 50)), query=normalized, ) own_chat_ids = {chat.id for chat in own_chats} discovered_rows = await chats_repository.discover_public_chats( db, user_id=user_id, query=normalized, limit=max(1, min(chats_limit, 50)), ) merged_chats: list[tuple[Chat, bool]] = [(chat, True) for chat in own_chats] for chat, is_member in discovered_rows: if chat.id in own_chat_ids: continue merged_chats.append((chat, is_member)) if len(merged_chats) >= max(1, min(chats_limit, 50)): break chats: list[ChatDiscoverRead] = [] for chat, is_member in merged_chats: serialized = await serialize_chat_for_user(db, user_id=user_id, chat=chat) chats.append( ChatDiscoverRead.model_validate( { **serialized.model_dump(), "is_member": bool(is_member), } ) ) return GlobalSearchRead( users=users, chats=chats, messages=messages, )