import '../strings/consultorios_strings.dart'; /// Um consultório dentário devolvido pela Overpass API. Os campos de texto /// nunca são nulos — qualquer tag em falta no OpenStreetMap já é resolvida /// para um texto de substituição aqui — mas [hasPhone]/[hasOpeningHours] /// dizem se esse texto é um valor real ou só o substituto, para os widgets /// decidirem o que é "acionável" (ex.: só ligar quando há telefone real). class Clinic { const Clinic({ required this.id, required this.name, required this.lat, required this.lon, required this.address, required this.phone, required this.hasPhone, required this.openingHours, required this.hasOpeningHours, this.distanceKm, }); final String id; final String name; final double lat; final double lon; final String address; final String phone; final bool hasPhone; final String openingHours; final bool hasOpeningHours; /// Distância (km) até à morada guardada — só preenchida depois de /// [OverpassService.fetchNearbyClinics] a calcular; `null` até lá. final double? distanceKm; Clinic copyWithDistance(double distanceKm) { return Clinic( id: id, name: name, lat: lat, lon: lon, address: address, phone: phone, hasPhone: hasPhone, openingHours: openingHours, hasOpeningHours: hasOpeningHours, distanceKm: distanceKm, ); } /// Constrói a partir de um elemento da resposta da Overpass API (`node` /// ou `way`, com `out center tags;` para que `way` também tenha /// coordenadas via `element['center']`). factory Clinic.fromOverpassElement(Map element) { final tags = (element['tags'] as Map?)?.cast() ?? const {}; final double lat; final double lon; if (element['type'] == 'node') { lat = (element['lat'] as num).toDouble(); lon = (element['lon'] as num).toDouble(); } else { final center = (element['center'] as Map?)?.cast() ?? const {}; lat = ((center['lat'] as num?) ?? 0).toDouble(); lon = ((center['lon'] as num?) ?? 0).toDouble(); } final name = (tags['name'] as String?)?.trim(); final addressParts = [ [ tags['addr:street'], tags['addr:housenumber'], ].whereType().join(', '), tags['addr:postcode'], (tags['addr:city'] ?? tags['addr:suburb']), ].whereType().where((s) => s.trim().isNotEmpty).toList(); final phone = (tags['phone'] as String?)?.trim() ?? (tags['contact:phone'] as String?)?.trim(); final openingHours = (tags['opening_hours'] as String?)?.trim(); final hasPhone = phone != null && phone.isNotEmpty; final hasOpeningHours = openingHours != null && openingHours.isNotEmpty; return Clinic( id: '${element['type']}/${element['id']}', name: (name != null && name.isNotEmpty) ? name : ConsultoriosStrings.unnamedClinic, lat: lat, lon: lon, address: addressParts.isNotEmpty ? addressParts.join(' • ') : ConsultoriosStrings.noAddress, phone: hasPhone ? phone : ConsultoriosStrings.noPhone, hasPhone: hasPhone, openingHours: hasOpeningHours ? openingHours : ConsultoriosStrings.noOpeningHours, hasOpeningHours: hasOpeningHours, ); } }