import { httpExternal } from '~/utils/httpExternal' // Autocomplétion d'adresse branchée sur la Base Adresse Nationale (BAN), // `api-adresse.data.gouv.fr` — service public français, gratuit, CORS ouvert. // // Appel HTTP DIRECT depuis le front (pas de proxy back) : la BAN est un domaine // externe, sans cookie de session ni enveloppe Hydra → on passe par // `httpExternal` et NON `useApi()`. // // Contrat : // searchCity(postalCode) -> liste { city, postalCode } // searchAddress(query, cp?) -> liste { label, street, postalCode, city } // En cas d'erreur/timeout, la méthode THROW une // AddressAutocompleteUnavailableError. Le composant consommateur catch, // avertit l'utilisateur et bascule en saisie libre. /** URL de l'endpoint de recherche BAN. */ const BAN_SEARCH_URL = 'https://api-adresse.data.gouv.fr/search/' /** Une suggestion de ville renvoyée à partir d'un code postal. */ export interface CitySuggestion { city: string postalCode: string } /** Une suggestion d'adresse complète (saisie assistée du champ « Rue »). */ export interface AddressSuggestion { label: string street: string postalCode: string city: string } export interface AddressAutocomplete { searchCity(postalCode: string): Promise searchAddress(query: string, postalCode?: string): Promise } /** Erreur signalant que le service d'autocomplétion BAN n'est pas disponible. */ export class AddressAutocompleteUnavailableError extends Error { constructor() { super('Address autocomplete (BAN) is not available.') this.name = 'AddressAutocompleteUnavailableError' } } /** Propriétés d'une « feature » GeoJSON renvoyée par la BAN (champs utilisés). */ interface BanFeatureProperties { label?: string name?: string street?: string postcode?: string city?: string } /** Réponse GeoJSON FeatureCollection de la BAN. */ interface BanResponse { features?: { properties?: BanFeatureProperties }[] } export function useAddressAutocomplete(): AddressAutocomplete { return { async searchCity(postalCode: string): Promise { let res: BanResponse try { res = await httpExternal(BAN_SEARCH_URL, { query: { q: postalCode, type: 'municipality' }, }) } catch { throw new AddressAutocompleteUnavailableError() } return (res.features ?? []).map((feature) => { const props = feature.properties ?? {} return { city: props.city ?? props.name ?? '', postalCode: props.postcode ?? '', } }) }, async searchAddress(query: string, postalCode?: string): Promise { // Pas de `type=housenumber` ici : sans filtre, la BAN classe rues + // numéros par pertinence (comportement d'autocomplétion attendu). // On n'ajoute `postcode` que s'il est fourni (sinon recherche large). const banQuery: Record = { q: query } if (postalCode) { banQuery.postcode = postalCode } let res: BanResponse try { res = await httpExternal(BAN_SEARCH_URL, { query: banQuery }) } catch { throw new AddressAutocompleteUnavailableError() } return (res.features ?? []).map((feature) => { const props = feature.properties ?? {} return { label: props.label ?? '', // `name` porte la ligne d'adresse complète (numéro + voie) ; // `street` ne contient que la voie. On privilégie `name`. street: props.name ?? props.street ?? '', postalCode: props.postcode ?? '', city: props.city ?? '', } }) }, } }