import { formatPhone } from '~/utils/formatters/phone'; export interface ConstructeurSummary { id: string; name?: string | null; email?: string | null; phone?: string | null; } const isObject = (value: unknown): value is Record => Boolean(value) && typeof value === 'object' && !Array.isArray(value); const toStringId = (value: unknown): string | null => { if (typeof value !== 'string') { return null; } const trimmed = value.trim(); if (!trimmed) { return null; } if (trimmed.includes('/')) { const parts = trimmed.split('/').filter(Boolean); return parts.length ? parts[parts.length - 1] : null; } return trimmed; }; export const uniqueConstructeurIds = (...sources: unknown[]): string[] => { const ids = new Set(); const pushId = (value: unknown) => { const id = toStringId(value); if (id) { ids.add(id); } }; const explore = (value: unknown): void => { if (!value) { return; } if (Array.isArray(value)) { value.forEach(explore); return; } if (typeof value === 'string') { pushId(value); return; } if (isObject(value)) { if (Array.isArray(value.constructeurIds)) { value.constructeurIds.forEach(pushId); } if (value.constructeurId) { pushId(value.constructeurId); } if (Array.isArray(value.constructeurs)) { value.constructeurs.forEach(explore); } if (value.constructeur) { explore(value.constructeur); } // Only extract ID if this looks like a constructeur object (has @type or recognizable fields) // Don't extract ID from component/piece/product objects that happen to be passed in if (typeof value.id === 'string' && !value.name && !value.typeComposant && !value.typePiece && !value.typeProduct) { pushId(value.id); } return; } }; sources.forEach(explore); return Array.from(ids); }; export const resolveConstructeurs = ( ids: string[], ...candidatePools: Array ): ConstructeurSummary[] => { if (!Array.isArray(ids) || ids.length === 0) { return []; } const index = new Map(); const register = (pool?: ConstructeurSummary[] | null) => { if (!Array.isArray(pool)) { return; } pool.forEach((entry) => { if (entry && typeof entry === 'object' && typeof entry.id === 'string') { index.set(entry.id, entry); } }); }; candidatePools.forEach(register); return ids .map((id) => index.get(id)) .filter((item): item is ConstructeurSummary => Boolean(item)) .map((item) => ({ ...item })); }; export const formatConstructeurContact = ( constructeur?: ConstructeurSummary | null, ): string => { if (!constructeur) { return ''; } const formattedPhone = formatPhone(constructeur.phone); const phone = formattedPhone || constructeur.phone || null; return [constructeur.email, phone].filter(Boolean).join(' • '); }; export const buildConstructeurRequestPayload = >( payload: T, ): T & { constructeurs?: string[] } => { const collected = new Set(uniqueConstructeurIds( payload?.constructeurIds, payload?.constructeurId, payload?.constructeur, payload?.constructeurs, )); if (!collected.size) { const fallbackLists = [ payload?.constructeurIds, payload?.constructeurs, ]; fallbackLists.forEach((list) => { if (!Array.isArray(list)) { return; } list.forEach((item) => { if (typeof item === 'string') { const id = toStringId(item); if (id) { collected.add(id); } return; } if (isObject(item) && typeof item.id === 'string') { collected.add(item.id); } }); }); } const ids = Array.from(collected); const next = { ...payload } as Record; delete next.constructeurId; delete next.constructeur; delete next.constructeurs; delete next.constructeurIds; if (ids.length) { next.constructeurs = ids.map((id) => `/api/constructeurs/${id}`); } return next as T & { constructeurs?: string[] }; };