import type { ApiDiscoveredProfile, ApiDiscoveredProfileGroup } from './discovery-types' import type { FullContact } from './types' import { FIELD_LABELS, discoveredProfileToFullContact } from './discovery-utils' import type { ContactAddress } from './types' export interface DraftField { id: string label: string fieldKey: string value: string removed: boolean } function fieldId(fieldKey: string, value: string) { return `${fieldKey}::${value}` } /** Clé de déduplication par valeur normalisée (indépendante du libellé / source). */ function normalizedValueKey(fieldKey: string, value: string): string { const v = value.trim() if (fieldKey === 'emails') { return `emails:${v.toLowerCase()}` } if (fieldKey === 'phones') { const digits = v.replace(/\D/g, '') return digits.length >= 6 ? `phones:${digits}` : `phones:${v.toLowerCase()}` } if (fieldKey === 'addresses') { return `addresses:${v.toLowerCase()}` } return `${fieldKey}:${v.toLowerCase()}` } function pushDraftField( out: DraftField[], seen: Set, fieldKey: string, value: string, label: string, ) { const v = value.trim() if (!v) return const dedupeKey = normalizedValueKey(fieldKey, v) if (seen.has(dedupeKey)) return seen.add(dedupeKey) out.push({ id: fieldId(fieldKey, fieldKey === 'emails' ? v.toLowerCase() : v), label, fieldKey, value: v, removed: false, }) } function pushScalar( out: DraftField[], fieldKey: string, value: string | undefined, seen: Set, ) { const v = value?.trim() if (!v) return pushDraftField(out, seen, fieldKey, v, FIELD_LABELS[fieldKey] ?? fieldKey) } function pushLabeled( out: DraftField[], fieldKey: string, items: { value: string; label?: string }[] | undefined, seen: Set, ) { for (const item of items ?? []) { const v = item.value?.trim() if (!v) continue const suffix = item.label ? ` (${item.label})` : '' pushDraftField( out, seen, fieldKey, v, `${FIELD_LABELS[fieldKey] ?? fieldKey}${suffix}`, ) } } export function buildDraftFields(group: ApiDiscoveredProfileGroup): DraftField[] { const profile = group.profile ?? group.profiles?.[0] if (!profile) return [] const data = profile.enriched_data const seen = new Set() const out: DraftField[] = [] pushScalar(out, 'first_name', data?.first_name, seen) pushScalar(out, 'last_name', data?.last_name, seen) pushScalar(out, 'company', data?.company, seen) pushScalar(out, 'department', data?.department, seen) pushScalar(out, 'job_title', data?.job_title, seen) pushScalar(out, 'website', data?.website, seen) pushLabeled(out, 'social_profiles', data?.social_profiles, seen) pushScalar(out, 'notes', data?.notes, seen) pushLabeled(out, 'emails', data?.emails, seen) pushLabeled(out, 'phones', data?.phones, seen) for (const addr of data?.addresses ?? []) { const parts = [addr.street, addr.city, addr.region, addr.postal_code, addr.country] .map((p) => p?.trim()) .filter(Boolean) if (parts.length === 0) continue const v = parts.join(', ') pushDraftField( out, seen, 'addresses', v, addr.label ? `${FIELD_LABELS.addresses} (${addr.label})` : FIELD_LABELS.addresses, ) } for (const p of group.profiles ?? [profile]) { for (const e of p.all_emails ?? []) { const v = e.email?.trim() if (!v) continue pushDraftField(out, seen, 'emails', v, FIELD_LABELS.emails) } const primary = p.primary_email?.trim() if (primary) { pushDraftField(out, seen, 'emails', primary, FIELD_LABELS.emails) } } return out } function draftFieldContactLabel(field: DraftField): string { const base = FIELD_LABELS[field.fieldKey] ?? field.fieldKey if (field.label.startsWith(base)) { const suffix = field.label.slice(base.length).trim() if (suffix.startsWith('(') && suffix.endsWith(')')) { return suffix.slice(1, -1) } } return 'Autre' } function parseAddressDraftValue(value: string, label: string): ContactAddress { const parts = value .split(',') .map((p) => p.trim()) .filter(Boolean) if (parts.length >= 5) { return { street: parts[0], city: parts[1], region: parts[2], postalCode: parts[3], country: parts.slice(4).join(', '), label, } } return { street: value, label } } export function applyDraftToContact( profile: ApiDiscoveredProfile | undefined, fields: DraftField[], ): FullContact { if (!profile) { throw new Error('profile is required to build contact') } const base = discoveredProfileToFullContact(profile) const active = fields.filter((f) => !f.removed) const scalar = (key: string) => active.find((f) => f.fieldKey === key)?.value const firstName = scalar('first_name') const lastName = scalar('last_name') if (firstName) base.firstName = firstName if (lastName) base.lastName = lastName if (scalar('company')) base.company = scalar('company') if (scalar('department')) base.department = scalar('department') if (scalar('job_title')) base.jobTitle = scalar('job_title') if (scalar('website')) base.website = scalar('website') if (scalar('notes')) base.notes = scalar('notes') const hasSocialFields = fields.some((f) => f.fieldKey === 'social_profiles') const socialProfiles = active .filter((f) => f.fieldKey === 'social_profiles') .map((f) => ({ value: f.value, label: draftFieldContactLabel(f), })) if (hasSocialFields) { base.socialProfiles = socialProfiles.length > 0 ? socialProfiles : undefined } const hasEmailFields = fields.some((f) => f.fieldKey === 'emails') const emails = active .filter((f) => f.fieldKey === 'emails') .map((f) => ({ value: f.value, label: draftFieldContactLabel(f), })) if (hasEmailFields) base.emails = emails const hasPhoneFields = fields.some((f) => f.fieldKey === 'phones') const phones = active .filter((f) => f.fieldKey === 'phones') .map((f) => ({ value: f.value, label: draftFieldContactLabel(f), })) if (hasPhoneFields) base.phones = phones const hasAddressFields = fields.some((f) => f.fieldKey === 'addresses') const addresses = active .filter((f) => f.fieldKey === 'addresses') .map((f) => parseAddressDraftValue(f.value, draftFieldContactLabel(f))) if (hasAddressFields) { base.addresses = addresses.length > 0 ? addresses : undefined } return base }