import 'dart:convert'; import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_functions/cloud_functions.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/firebase/firebase_providers.dart'; import '../../household/data/household_providers.dart'; /// Ergebnis der KI-Krankheits-Diagnose (Cloud Function diagnosePlant). class DiagnosisResult { const DiagnosisResult({ required this.healthy, required this.matchesSpecies, required this.summary, required this.details, required this.treatment, required this.prevention, }); final bool healthy; /// false, wenn das Foto laut KI eindeutig eine andere Pflanzenart zeigt /// als die im Profil hinterlegte (z. B. versehentlich falsches Foto). final bool matchesSpecies; final String summary; final String details; final String treatment; final String prevention; } /// Ein gespeicherter Eintrag der Untersuchungshistorie /// (households/{id}/plants/{id}/diagnoses). class PlantDiagnosisEntry { const PlantDiagnosisEntry({ required this.id, required this.createdAt, required this.result, }); final String id; /// null, solange der Server-Zeitstempel noch nicht bestätigt ist. final DateTime? createdAt; final DiagnosisResult result; } class PlantDiagnosisService { PlantDiagnosisService( this._functionsGetter, this._firestore, this._householdIdGetter, ); // Lazy, damit Widget-Tests ohne initialisiertes Firebase auskommen — // FirebaseFunctions.instanceFor wirft sonst schon beim Erzeugen. final FirebaseFunctions Function() _functionsGetter; final FirebaseFirestore _firestore; final String? Function() _householdIdGetter; CollectionReference> _diagnosesRef( String householdId, String plantId, ) { return _firestore .collection('households') .doc(householdId) .collection('plants') .doc(plantId) .collection('diagnoses'); } /// Schickt das Foto an die Cloud Function (Claude) – dauert einige Sekunden. /// [speciesHint] ist die bekannte Art der Pflanze und hilft der Diagnose. Future diagnose( Uint8List imageBytes, { String? speciesHint, }) async { final callable = _functionsGetter().httpsCallable('diagnosePlant'); final response = await callable.call>({ 'imageBase64': base64Encode(imageBytes), if (speciesHint != null && speciesHint.trim().isNotEmpty) 'speciesHint': speciesHint.trim(), }); final data = response.data; return DiagnosisResult( healthy: data['healthy'] as bool? ?? false, matchesSpecies: data['matchesSpecies'] as bool? ?? true, summary: data['summary'] as String? ?? '', details: data['details'] as String? ?? '', treatment: data['treatment'] as String? ?? '', prevention: data['prevention'] as String? ?? '', ); } /// Legt einen Eintrag in der Untersuchungshistorie der Pflanze an. Future saveDiagnosis(String plantId, DiagnosisResult result) async { final householdId = _householdIdGetter(); if (householdId == null) return; await _diagnosesRef(householdId, plantId).add({ 'healthy': result.healthy, 'matchesSpecies': result.matchesSpecies, 'summary': result.summary, 'details': result.details, 'treatment': result.treatment, 'prevention': result.prevention, 'createdAt': FieldValue.serverTimestamp(), }); } /// Entfernt einen Eintrag aus der Historie (nur volle Mitglieder). Future deleteDiagnosis(String plantId, String diagnosisId) async { final householdId = _householdIdGetter(); if (householdId == null) return; await _diagnosesRef(householdId, plantId).doc(diagnosisId).delete(); } } final plantDiagnosisServiceProvider = Provider((ref) { return PlantDiagnosisService( () => FirebaseFunctions.instanceFor(region: 'europe-west3'), ref.watch(firestoreProvider), () => ref.read(householdIdProvider).value, ); }); /// Untersuchungshistorie einer Pflanze, neueste zuerst, live aus Firestore. final plantDiagnosesProvider = StreamProvider.family, String>((ref, plantId) { final householdId = ref.watch(householdIdProvider).value; if (householdId == null) return Stream.value(const []); return ref .watch(firestoreProvider) .collection('households') .doc(householdId) .collection('plants') .doc(plantId) .collection('diagnoses') .orderBy('createdAt', descending: true) .snapshots() .map((snapshot) => [ for (final doc in snapshot.docs) PlantDiagnosisEntry( id: doc.id, createdAt: (doc.data()['createdAt'] as Timestamp?)?.toDate(), result: DiagnosisResult( healthy: doc.data()['healthy'] as bool? ?? false, matchesSpecies: doc.data()['matchesSpecies'] as bool? ?? true, summary: doc.data()['summary'] as String? ?? '', details: doc.data()['details'] as String? ?? '', treatment: doc.data()['treatment'] as String? ?? '', prevention: doc.data()['prevention'] as String? ?? '', ), ), ]); });