import type { ApiDiscoveredProfile, ApiDiscoveredProfileGroup, ApiDiscoveryOtherPage, } from './discovery-types' import { profileDisplayName } from './discovery-utils' export function profileToDiscoveryGroup( profile: ApiDiscoveredProfile, ): ApiDiscoveredProfileGroup { return { group_key: profile.id, profile_ids: [profile.id], display_name: profileDisplayName(profile), primary_email: profile.primary_email, message_count: profile.message_count, profile, profiles: [profile], } } function isDiscoveredProfile(value: unknown): value is ApiDiscoveredProfile { if (!value || typeof value !== 'object') return false const row = value as Record return typeof row.id === 'string' && typeof row.primary_email === 'string' && !('profile' in row) } export function normalizeDiscoveryGroup( group: ApiDiscoveredProfileGroup, ): ApiDiscoveredProfileGroup | null { const profile = group.profile ?? group.profiles?.[0] if (!profile?.id) return null const profileIds = group.profile_ids?.length > 0 ? group.profile_ids : [profile.id] return { ...group, group_key: group.group_key || profileIds[0] || profile.id, profile_ids: profileIds, profile, profiles: group.profiles?.length ? group.profiles : [profile], display_name: group.display_name || profileDisplayName(profile), primary_email: group.primary_email || profile.primary_email, message_count: group.message_count ?? profile.message_count, } } export interface DiscoveryOtherPageResult { groups: ApiDiscoveredProfileGroup[] total: number hasMore: boolean } export function parseDiscoveryOtherPage( res: ApiDiscoveryOtherPage | { groups?: ApiDiscoveredProfileGroup[] profiles?: ApiDiscoveredProfile[] total?: number has_more?: boolean limit?: number offset?: number }, options?: { offset?: number; pageSize?: number }, ): DiscoveryOtherPageResult { const offset = options?.offset ?? 0 const pageSize = options?.pageSize ?? 20 const allGroups = parseDiscoveryOtherResponse(res) const hasServerHasMore = 'has_more' in res && res.has_more != null const hasServerTotal = typeof res.total === 'number' const serverLimit = typeof res.limit === 'number' ? res.limit : undefined // Réponse legacy : tous les groupes en une fois sans pagination const looksUnpaginated = !hasServerHasMore && !hasServerTotal && allGroups.length > pageSize && (serverLimit == null || serverLimit >= allGroups.length) if (looksUnpaginated) { const groups = allGroups.slice(offset, offset + pageSize) return { groups, total: allGroups.length, hasMore: offset + pageSize < allGroups.length, } } const groups = allGroups const total = hasServerTotal ? res.total! : groups.length + offset const hasMore = hasServerHasMore ? Boolean(res.has_more) : hasServerTotal ? offset + groups.length < res.total! : groups.length >= pageSize return { groups, total, hasMore } } export function parseDiscoveryOtherResponse(res: { groups?: ApiDiscoveredProfileGroup[] profiles?: ApiDiscoveredProfile[] }): ApiDiscoveredProfileGroup[] { if (res.groups?.length) { const normalized: ApiDiscoveredProfileGroup[] = [] for (const group of res.groups) { const row = normalizeDiscoveryGroup(group) if (row) normalized.push(row) } return normalized } if (res.profiles?.length) { return res.profiles.map(profileToDiscoveryGroup) } return [] } /** Stable React key — prefer person group id when several profiles are merged. */ export function discoveryGroupReactKey(group: ApiDiscoveredProfileGroup): string { const profileIds = (group.profile_ids ?? []).filter(Boolean) const profileId = group.profile?.id ?? profileIds[0] const groupKey = group.group_key?.trim() if (groupKey && profileIds.length > 1) { return groupKey } if (groupKey && profileId && groupKey !== profileId) { return groupKey } return profileId ?? groupKey ?? 'unknown' } /** Remove duplicate groups (infinite scroll / refetch overlap). */ export function dedupeDiscoveryGroups( groups: ApiDiscoveredProfileGroup[], ): ApiDiscoveredProfileGroup[] { const seen = new Set() const out: ApiDiscoveredProfileGroup[] = [] for (const group of groups) { const normalized = normalizeDiscoveryGroup(group) if (!normalized) continue const key = discoveryGroupReactKey(normalized) if (seen.has(key)) continue seen.add(key) out.push(normalized) } return out } /** Coerce cached query rows that may still be flat profiles. */ export function coerceDiscoveryGroups(data: unknown): ApiDiscoveredProfileGroup[] { if (!Array.isArray(data) || data.length === 0) return [] if (isDiscoveredProfile(data[0])) { return (data as ApiDiscoveredProfile[]).map(profileToDiscoveryGroup) } const out: ApiDiscoveredProfileGroup[] = [] for (const row of data as ApiDiscoveredProfileGroup[]) { const normalized = normalizeDiscoveryGroup(row) if (normalized) out.push(normalized) } return out }