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.phoneNumbers,
    required this.openingHours,
    required this.hasOpeningHours,
    this.distanceKm,
  });

  final String id;
  final String name;
  final double lat;
  final double lon;
  final String address;

  /// Um consultório na OSM pode ter mais do que um contacto na mesma tag
  /// `phone` (separados por `;`/`,`) — já vêm separados aqui, para quem usa
  /// isto poder oferecer escolha em vez de tentar ligar para o texto todo
  /// de uma vez. Lista vazia quando não há telefone.
  final List<String> phoneNumbers;
  final String openingHours;
  final bool hasOpeningHours;

  bool get hasPhone => phoneNumbers.isNotEmpty;

  /// 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,
      phoneNumbers: phoneNumbers,
      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<String, dynamic> element) {
    final tags = (element['tags'] as Map?)?.cast<String, dynamic>() ?? 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<String, dynamic>() ?? 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<String>().join(', '),
      tags['addr:postcode'],
      (tags['addr:city'] ?? tags['addr:suburb']),
    ].whereType<String>().where((s) => s.trim().isNotEmpty).toList();

    final phoneRaw = (tags['phone'] as String?)?.trim() ??
        (tags['contact:phone'] as String?)?.trim();
    final phoneNumbers = (phoneRaw == null || phoneRaw.isEmpty)
        ? const <String>[]
        : phoneRaw
              .split(RegExp(r'[;,]'))
              .map((s) => s.trim())
              .where((s) => s.isNotEmpty)
              .toList();
    final openingHours = (tags['opening_hours'] as String?)?.trim();
    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,
      phoneNumbers: phoneNumbers,
      openingHours: hasOpeningHours
          ? openingHours
          : ConsultoriosStrings.noOpeningHours,
      hasOpeningHours: hasOpeningHours,
    );
  }
}
