import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; import 'package:flux/features/tasks/data/task_repository.dart'; import 'package:flux/features/tasks/models/task_model.dart'; import 'package:flux/features/tasks/models/task_status.dart'; import 'package:get_it/get_it.dart'; part 'task_form_state.dart'; class TaskFormCubit extends Cubit { final TaskRepository _taskRepository = GetIt.I.get(); final List _globalStaff; final String currentCompanyId = GetIt.I().state.company!.id!; final String? currentStoreId = GetIt.I().state.currentStore?.id; TaskFormCubit({ required List globalStaff, TaskModel? initialTask, // Arriva dalla navigazione interna (extra) String? initialTaskId, // Arriva dal Deep Link (parametro URL) }) : _globalStaff = globalStaff, super(const TaskFormState()) { _initForm(initialTask, initialTaskId); } Future _initForm(TaskModel? task, String? taskId) async { // 1. Mettiamo subito il form in caricamento emit(state.copyWith(status: TaskFormStatus.loading)); TaskModel? taskToLoad = task; // 2. SCENARIO DEEP LINK: Non abbiamo l'oggetto, ma abbiamo un ID valido if (taskToLoad == null && taskId != null && taskId != 'new') { try { taskToLoad = await _taskRepository.getTaskById(taskId); } catch (e) { emit( state.copyWith( status: TaskFormStatus.failure, errorMessage: 'Impossibile caricare il task dal link: $e', ), ); return; // Ci fermiamo qui } } // 3. Popoliamo lo stato con i dati (sia che arrivino dall'extra, sia dal DB, sia nulli) final isGlobalMode = taskToLoad != null ? taskToLoad.storeId == null : currentStoreId == null; final existingStaffIds = taskToLoad?.assignedToStaff.map((s) => s.id!).toList() ?? taskToLoad?.assignedToIds ?? []; emit( state.copyWith( id: taskToLoad?.id, title: taskToLoad?.title ?? '', description: taskToLoad?.description ?? '', dueDate: taskToLoad?.dueDate, taskStatus: taskToLoad?.status ?? TaskStatus.open, isGlobal: isGlobalMode, selectedStaffIds: existingStaffIds, status: TaskFormStatus.initial, // Caricamento finito, form pronto! ), ); _updateStaffScope(isGlobalMode); } // --- 2. SWITCH SCOPE E RAGGRUPPAMENTO --- void toggleGlobalScope(bool isGlobal) { emit( state.copyWith( isGlobal: isGlobal, selectedStaffIds: [], // Resettiamo la selezione se si cambia scope ), ); _updateStaffScope(isGlobal); } void _updateStaffScope(bool isGlobal) { // 1. Filtriamo in memoria: cerchiamo nell'array degli ID! final filteredStaff = isGlobal ? _globalStaff : _globalStaff .where((s) => s.assignedStoreIds.contains(currentStoreId)) .toList(); // 2. Raggruppamento M2M (Ciclo manuale) final Map> groupedStaff = {}; for (final staff in filteredStaff) { // Se non ha nessun negozio assegnato, finisce in Direzione if (staff.assignedStores.isEmpty) { groupedStaff.putIfAbsent('Direzione / HQ', () => []).add(staff); } else { // Se ha più negozi, clona la sua presenza in ogni gruppo! for (final store in staff.assignedStores) { final storeName = store.name; groupedStaff.putIfAbsent(storeName, () => []).add(staff); } } } // 3. Emettiamo il nuovo stato all'istante emit( state.copyWith( status: TaskFormStatus.initial, groupedAvailableStaff: groupedStaff, ), ); } // --- 3. SELEZIONE AVANZATA (SINGOLA E PER NEGOZIO) --- void toggleStaffSelection(String staffId) { final currentList = List.from(state.selectedStaffIds); if (currentList.contains(staffId)) { currentList.remove(staffId); } else { currentList.add(staffId); } emit(state.copyWith(selectedStaffIds: currentList)); } void toggleStoreSelection(String storeName, bool selectAll) { // Recupera tutti i membri di quel gruppo final staffInStore = state.groupedAvailableStaff[storeName] ?? []; final idsInStore = staffInStore.map((s) => s.id!).toList(); final currentSelection = Set.from(state.selectedStaffIds); if (selectAll) { currentSelection.addAll(idsInStore); } else { currentSelection.removeAll(idsInStore); } emit(state.copyWith(selectedStaffIds: currentSelection.toList())); } // --- 4. AGGIORNAMENTO CAMPI STANDARD --- void updateTitle(String title) => emit(state.copyWith(title: title)); void updateDescription(String desc) => emit(state.copyWith(description: desc)); void updateDueDate(DateTime? date) => emit(state.copyWith(dueDate: date, clearDueDate: date == null)); void updateLinkedTicket(String? ticketId) => emit(state.copyWith(linkedTicketId: ticketId)); // --- 5. LOGICA DEI REMINDER FINTI --- void addMockReminder(String type, int minutes) { final currentReminders = List.from(state.reminders); currentReminders.add(TaskReminder(type: type, minutesBefore: minutes)); emit(state.copyWith(reminders: currentReminders)); } void removeMockReminder(int index) { final currentReminders = List.from(state.reminders); currentReminders.removeAt(index); emit(state.copyWith(reminders: currentReminders)); } // --- 6. SALVATAGGIO FINALE --- Future saveTask({required String currentUserId}) async { if (!state.isFormValid) return; emit(state.copyWith(status: TaskFormStatus.submitting)); try { final taskToSave = TaskModel( id: state.id, companyId: currentCompanyId, storeId: state.isGlobal ? null : currentStoreId, // La vera discriminante Globale/Store! createdById: state.id == null ? currentUserId : null, // Lo settiamo solo alla creazione title: state.title.trim(), description: state.description.trim().isEmpty ? null : state.description.trim(), dueDate: state.dueDate, status: state.taskStatus, assignedToIds: state .selectedStaffIds, // L'array che andrà a popolare la tabella di giunzione ); if (state.id == null) { await _taskRepository.createTask(taskToSave); } else { await _taskRepository.updateTask(taskToSave); } emit(state.copyWith(status: TaskFormStatus.success)); } catch (e) { emit( state.copyWith( status: TaskFormStatus.failure, errorMessage: 'Errore durante il salvataggio: $e', ), ); } } }