import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flux/features/attachments/models/attachment_model.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; class AttachmentsRepository { final _supabase = Supabase.instance.client; static const String _bucketName = 'attachments'; static const String _tableName = 'attachment'; // Cambia col vero nome della tua tabella se diverso! /// Scarica i byte di un file direttamente da Supabase Storage Future downloadAttachmentBytes(String storagePath) async { try { final Uint8List bytes = await _supabase.storage .from(_bucketName) .download(storagePath); return bytes; } catch (e) { throw Exception("Impossibile scaricare il documento dal cloud: $e"); } } /// RESTITUISCE IL NOME DELLA COLONNA DB IN BASE AL TIPO String _getColumnNameForParent(AttachmentParentType parentType) { switch (parentType) { case AttachmentParentType.operation: return 'operation_id'; case AttachmentParentType.ticket: return 'ticket_id'; case AttachmentParentType.customer: return 'customer_id'; } } /// RECUPERA I FILE IN TEMPO REALE Stream> getFilesStream( String parentId, AttachmentParentType parentType, ) { final columnName = _getColumnNameForParent(parentType); return _supabase .from(_tableName) .stream(primaryKey: ['id']) .eq(columnName, parentId) .map( (listOfMaps) => listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(), ); } /// CARICA IL FILE NELLO STORAGE E LO REGISTRA NEL DB Future uploadAndRegisterFile({ required String parentId, required AttachmentParentType parentType, required PlatformFile pickedFile, }) async { try { final companyId = GetIt.I.get().state.company!.id!; final extension = pickedFile.extension ?? pickedFile.name.split('.').last; final cleanName = pickedFile.name .replaceAll(RegExp(r'[^\w\s\.-]'), '') .replaceAll(' ', '_'); // Creiamo un path ordinato: idAzienda/tipoEntita/idEntita/timestamp_nomefile final timestamp = DateTime.now().millisecondsSinceEpoch; final storagePath = '$companyId/${parentType.name}/$parentId/${timestamp}_$cleanName'; // 1. Upload su Supabase Storage await _supabase.storage .from(_bucketName) .uploadBinary( storagePath, pickedFile.bytes!, fileOptions: FileOptions( upsert: true, contentType: _guessContentType(extension), ), ); // 2. Creiamo la mappa per il DB dinamicamente final Map insertData = { 'company_id': companyId, 'name': pickedFile.name.replaceAll('.$extension', ''), 'extension': extension, 'file_size': pickedFile.size, 'storage_path': storagePath, }; // Inseriamo l'ID nella colonna giusta! final columnName = _getColumnNameForParent(parentType); insertData[columnName] = parentId; // 3. Salviamo su Postgres await _supabase.from(_tableName).insert(insertData); } catch (e) { throw Exception("Errore nel caricamento del file: $e"); } } /// ELIMINA IL FILE (Scollegamento intelligente) Future deleteFiles({ required List files, required AttachmentParentType currentContextType, }) async { if (files.isEmpty) return; try { for (var file in files) { if (file.id == null) continue; // 1. Capiamo quali collegamenti ha questo file attualmente final currentLinks = { AttachmentParentType.operation: file.operationId, AttachmentParentType.ticket: file.ticketId, AttachmentParentType.customer: file.customerId, }; // 2. Simuliamo la rimozione del collegamento per il contesto attuale currentLinks[currentContextType] = null; // 3. Controlliamo se rimangono altri ID valorizzati final hasOtherActiveLinks = currentLinks.values.any( (id) => id != null && id.isNotEmpty, ); if (hasOtherActiveLinks) { // A. Ci sono ancora altre entità che usano questo file! // Scolleghiamolo SOLO dal contesto attuale mettendo a NULL la sua colonna await _supabase .from(_tableName) .update({currentContextType.dbColumn: null}) .eq('id', file.id!); } else { // B. Nessuno usa più questo file! ELIMINAZIONE FISICA TOTALE. await _supabase.from(_tableName).delete().eq('id', file.id!); if (file.storagePath != null) { await _supabase.storage.from(_bucketName).remove([ file.storagePath!, ]); } } } } catch (e) { throw Exception("Errore nell'eliminazione dei file: $e"); } } /// RINOMINA UN FILE (Solo nel DB, non cambiamo il file fisico) Future renameAttachment(String fileId, String newName) async { try { await _supabase .from(_tableName) .update({'name': newName}) .eq('id', fileId); } catch (e) { throw Exception("Errore nella rinomina del file: $e"); } } /// ASSOCIA UN FILE A UN'ALTRA ENTITÀ (Modifica il record esistente) Future linkFileToEntity({ required String fileId, required AttachmentParentType targetType, required String targetId, }) async { try { // Facciamo un semplice UPDATE aggiungendo l'ID nella colonna giusta await _supabase .from(_tableName) .update({targetType.dbColumn: targetId}) .eq('id', fileId); } catch (e) { throw Exception("Errore nel collegamento del file: $e"); } } // Helper per indovinare il content-type base String _guessContentType(String extension) { switch (extension.toLowerCase()) { case 'pdf': return 'application/pdf'; case 'png': return 'image/png'; case 'jpg': case 'jpeg': return 'image/jpeg'; default: return 'application/octet-stream'; } } }