import 'package:em2rp/models/event_model.dart'; import 'package:intl/intl.dart'; import 'package:em2rp/utils/debug_log.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class IcsExportService { /// Génère un fichier ICS à partir d'un événement static Future generateIcsContent(EventModel event) async { final now = DateTime.now().toUtc(); final timestamp = DateFormat('yyyyMMddTHHmmss').format(now) + 'Z'; // Récupérer les informations supplémentaires final eventTypeName = await _getEventTypeName(event.eventTypeId); final workforce = await _getWorkforceDetails(event.workforce); final optionsWithNames = await _getOptionsDetails(event.options); // Formater les dates au format ICS (UTC) final startDate = _formatDateForIcs(event.startDateTime); final endDate = _formatDateForIcs(event.endDateTime); // Construire la description détaillée final description = _buildDescription(event, eventTypeName, workforce, optionsWithNames); // Générer un UID unique basé sur l'ID de l'événement final uid = 'em2rp-${event.id}@em2rp.app'; // Construire le contenu ICS final icsContent = '''BEGIN:VCALENDAR VERSION:2.0 PRODID:-//EM2RP//Event Manager//FR CALSCALE:GREGORIAN METHOD:PUBLISH BEGIN:VEVENT UID:$uid DTSTAMP:$timestamp DTSTART:$startDate DTEND:$endDate SUMMARY:${_escapeIcsText(event.name)} DESCRIPTION:${_escapeIcsText(description)} LOCATION:${_escapeIcsText(event.address)} STATUS:${_getEventStatus(event.status)} CATEGORIES:${_escapeIcsText(eventTypeName)} END:VEVENT END:VCALENDAR'''; return icsContent; } /// Récupère le nom du type d'événement depuis EventModel (déjà chargé) /// Note: Les eventTypes sont maintenant chargés via Cloud Function dans l'EventModel static Future _getEventTypeName(String eventTypeId) async { if (eventTypeId.isEmpty) return 'Non spécifié'; // Les eventTypes sont publics et déjà chargés dans l'app via Cloud Function // On retourne simplement l'ID, le nom sera résolu par l'app return eventTypeId; } /// Récupère les détails de la main d'œuvre /// Note: Les données users devraient être passées directement depuis l'app /// qui les a déjà récupérées via Cloud Function static Future> _getWorkforceDetails(List workforce) async { final List workforceNames = []; for (final ref in workforce) { try { // Si c'est déjà une Map avec les données, l'utiliser directement if (ref is Map) { final firstName = ref['firstName'] ?? ''; final lastName = ref['lastName'] ?? ''; if (firstName.isNotEmpty || lastName.isNotEmpty) { workforceNames.add('$firstName $lastName'.trim()); } continue; } // Si c'est un String (UID), on ne peut pas récupérer les données ici // Les données devraient être passées directement if (ref is String) { workforceNames.add('Utilisateur $ref'); continue; } // Si c'est une DocumentReference, extraire l'ID seulement if (ref is DocumentReference) { workforceNames.add('Utilisateur ${ref.id}'); } } catch (e) { print('Erreur lors du traitement des détails utilisateur: $e'); } } return workforceNames; } /// Récupère les détails des options /// Note: Les options sont publiques et déjà chargées via Cloud Function static Future>> _getOptionsDetails(List> options) async { final List> optionsWithNames = []; for (final option in options) { try { // Les options devraient déjà contenir le nom optionsWithNames.add({ 'name': option['name'] ?? option['optionId'] ?? 'Option inconnue', 'quantity': option['quantity'], }); } catch (e) { print('Erreur lors du traitement des options: $e'); } } return optionsWithNames; } /// Construit la description détaillée de l'événement static String _buildDescription( EventModel event, String eventTypeName, List workforce, List> optionsWithNames, ) { final buffer = StringBuffer(); // Type d'événement buffer.writeln('TYPE: $eventTypeName'); buffer.writeln(''); // Description if (event.description.isNotEmpty) { buffer.writeln('DESCRIPTION:'); buffer.writeln(event.description); buffer.writeln(''); } // Jauge if (event.jauge != null) { buffer.writeln('JAUGE: ${event.jauge} personnes'); } // Contact email if (event.contactEmail != null && event.contactEmail!.isNotEmpty) { buffer.writeln('EMAIL DE CONTACT: ${event.contactEmail}'); } // Contact téléphone if (event.contactPhone != null && event.contactPhone!.isNotEmpty) { buffer.writeln('TÉLÉPHONE DE CONTACT: ${event.contactPhone}'); } // Adresse if (event.address.isNotEmpty) { buffer.writeln(''); buffer.writeln('ADRESSE: ${event.address}'); } // Temps d'installation et démontage if (event.installationTime > 0 || event.disassemblyTime > 0) { buffer.writeln(''); if (event.installationTime > 0) { buffer.writeln('INSTALLATION: ${event.installationTime}h'); } if (event.disassemblyTime > 0) { buffer.writeln('DÉMONTAGE: ${event.disassemblyTime}h'); } } // Main d'œuvre if (workforce.isNotEmpty) { buffer.writeln(''); buffer.writeln('MAIN D\'ŒUVRE:'); for (final name in workforce) { buffer.writeln(' - $name'); } } // Options if (optionsWithNames.isNotEmpty) { buffer.writeln(''); buffer.writeln('OPTIONS:'); for (final option in optionsWithNames) { final optionName = option['name'] ?? 'Option inconnue'; final quantity = option['quantity']; if (quantity != null && quantity > 1) { buffer.writeln(' - $optionName (x$quantity)'); } else { buffer.writeln(' - $optionName'); } } } // Prix if (event.basePrice > 0) { buffer.writeln(''); buffer.writeln('PRIX DE BASE: ${event.basePrice.toStringAsFixed(2)}€'); } // Lien vers l'application buffer.writeln(''); buffer.writeln('---'); buffer.writeln('Géré par EM2RP Event Manager'); return buffer.toString(); } /// Formate une date au format ICS (yyyyMMddTHHmmssZ) static String _formatDateForIcs(DateTime dateTime) { final utcDate = dateTime.toUtc(); return DateFormat('yyyyMMddTHHmmss').format(utcDate) + 'Z'; } /// Échappe les caractères spéciaux pour le format ICS static String _escapeIcsText(String text) { return text .replaceAll('\\', '\\\\') .replaceAll(',', '\\,') .replaceAll(';', '\\;') .replaceAll('\n', '\\n') .replaceAll('\r', ''); } /// Convertit le statut de l'événement en statut ICS static String _getEventStatus(EventStatus status) { switch (status) { case EventStatus.confirmed: return 'CONFIRMED'; case EventStatus.canceled: return 'CANCELLED'; case EventStatus.waitingForApproval: return 'TENTATIVE'; } } /// Génère le nom du fichier ICS static String generateFileName(EventModel event) { final safeName = event.name .replaceAll(RegExp(r'[^\w\s-]'), '') .replaceAll(RegExp(r'\s+'), '_'); final dateStr = DateFormat('yyyyMMdd').format(event.startDateTime); return 'event_${safeName}_$dateStr.ics'; } }