import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import type { User, DmChannel } from '@backspace/shared';
import { Avatar } from '../ui/Avatar';
import { useSpaceStore } from '../../stores/spaceStore';
import { useAuthStore } from '../../stores/authStore';
import { useUIStore } from '../../stores/uiStore';
import { api } from '../../api/client';
import { isSelf, parseFederatedUsername } from '../../utils/identity';
import { useCanonicalUserView } from '../../utils/userViewLookup';
import { useFloatingPosition } from '../../hooks/useFloatingPosition';
/**
* Single slot in the group-avatar stack for the DM search bar dropdown.
* Extracted as a component so useCanonicalUserView is called per-slot (hooks
* must not be called inside a variable-length .map()).
*/
function DmSearchGroupAvatarSlot({ member, index }: { member: User; index: number }) {
const canonical = useCanonicalUserView(member);
const displayName = canonical.displayName ?? parseFederatedUsername(canonical.username).baseName;
return (
);
}
/**
* Avatar + display-name row for a user result in the DM search bar.
* Extracted as a component so useCanonicalUserView can be called at the top
* of a stable component rather than inside a variable-length .map().
*/
function DmSearchUserRow({ user, isSelected, selectedRef, onClick }: {
user: User;
isSelected: boolean;
selectedRef?: React.Ref;
onClick: () => void;
}) {
const canonical = useCanonicalUserView(user);
const displayName = canonical.displayName ?? canonical.username;
return (
);
}
/**
* Full row for a DM conversation item in the DM search bar dropdown.
* For 1-on-1 DMs, routes the partner through useCanonicalUserView.
* For group DMs, delegates per-slot to DmSearchGroupAvatarSlot.
*/
function DmSearchDmRow({ item, isSelected, selectedRef, onClick }: {
item: DmItem;
isSelected: boolean;
selectedRef?: React.Ref;
onClick: () => void;
}) {
// For 1-on-1 DMs: canonicalize the single partner. For groups: pass through
// unchanged (DmSearchGroupAvatarSlot handles per-slot canonicalization).
const rawPartner = !item.isGroup ? (item.otherMembers[0] ?? null) : null;
// Call the hook unconditionally — pass a stable fallback (empty User shape)
// for group DMs so hooks are always called the same number of times.
const FALLBACK_USER = { id: '', username: '', createdAt: 0, isAdmin: false, replicatedInstances: [] } as unknown as User;
const canonicalPartner = useCanonicalUserView(rawPartner ?? FALLBACK_USER);
const partner = rawPartner ? canonicalPartner : null;
return (