import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:uto_manager/domain/entities/personnel_record.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/domain/repositories/personnel_repository.dart'; import 'package:uto_manager/domain/services/assignment_service.dart'; import 'package:uto_manager/infrastructure/repositories/drift_position_slot_repository.dart'; // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- /// State for the personnel cubit. sealed class PersonnelState extends Equatable { const PersonnelState(); } class PersonnelInitial extends PersonnelState { const PersonnelInitial(); @override List get props => []; } class PersonnelLoading extends PersonnelState { const PersonnelLoading(); @override List get props => []; } class PersonnelLoaded extends PersonnelState { const PersonnelLoaded({ required this.allPersonnel, required this.unassigned, this.filterQuery = '', }); /// All personnel records. final List allPersonnel; /// Unassigned soldiers (available for assignment). final List unassigned; /// Current search/filter query string. final String filterQuery; /// Returns [unassigned] filtered by [filterQuery]. List get filteredUnassigned { if (filterQuery.isEmpty) return unassigned; final q = filterQuery.toLowerCase(); return unassigned.where((p) { return p.name.toLowerCase().contains(q) || p.primaryMos.code.toLowerCase().contains(q) || p.payGrade.payGrade.toLowerCase().contains(q); }).toList(); } PersonnelLoaded copyWith({ List? allPersonnel, List? unassigned, String? filterQuery, }) { return PersonnelLoaded( allPersonnel: allPersonnel ?? this.allPersonnel, unassigned: unassigned ?? this.unassigned, filterQuery: filterQuery ?? this.filterQuery, ); } @override List get props => [allPersonnel, unassigned, filterQuery]; } class PersonnelError extends PersonnelState { const PersonnelError(this.message); final String message; @override List get props => [message]; } // --------------------------------------------------------------------------- // Cubit // --------------------------------------------------------------------------- /// Manages personnel state, including loading, filtering, and assignment. class PersonnelCubit extends Cubit { PersonnelCubit({ required this.personnelRepository, required this.slotRepository, required this.assignmentService, }) : super(const PersonnelInitial()); final PersonnelRepository personnelRepository; final DriftPositionSlotRepository slotRepository; final AssignmentService assignmentService; StreamSubscription>? _allSubscription; StreamSubscription>? _unassignedSubscription; List _all = []; List _unassigned = []; // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- /// Starts watching the database for personnel changes. void startWatching() { emit(const PersonnelLoading()); _allSubscription = personnelRepository.watchAll().listen( (all) { _all = all; _rebuild(); }, onError: (Object e) => emit(PersonnelError(e.toString())), ); _unassignedSubscription = personnelRepository.watchUnassigned().listen( (unassigned) { _unassigned = unassigned; _rebuild(); }, onError: (Object e) => emit(PersonnelError(e.toString())), ); } void _rebuild() { final currentFilter = state is PersonnelLoaded ? (state as PersonnelLoaded).filterQuery : ''; emit(PersonnelLoaded(allPersonnel: _all, unassigned: _unassigned, filterQuery: currentFilter)); } // --------------------------------------------------------------------------- // Filtering // --------------------------------------------------------------------------- /// Updates the search filter query. void setFilter(String query) { if (state is PersonnelLoaded) { emit((state as PersonnelLoaded).copyWith(filterQuery: query)); } } // --------------------------------------------------------------------------- // Assignment // --------------------------------------------------------------------------- /// Assigns [soldier] to [slot] after validation. /// /// Returns true on success, false if validation failed. Future assignSoldierToSlot(PersonnelRecord soldier, PositionSlot slot) async { final outcome = assignmentService.assign(slot, soldier); if (!outcome.isSuccess) return false; try { await personnelRepository.assignToSlot(soldier.soldierId, slot.slotId); await slotRepository.assignPersonnel(slot.slotId, soldier.soldierId); return true; } catch (e) { emit(PersonnelError(e.toString())); return false; } } /// Unassigns the soldier from [slot]. Future unassignFromSlot(PositionSlot slot) async { if (slot.isVacant) return; try { await personnelRepository.unassignFromSlot(slot.assignedPersonnelId!); await slotRepository.clearAssignment(slot.slotId); } catch (e) { emit(PersonnelError(e.toString())); } } /// Adds a new [record] to the personnel roster. Future addPersonnel(PersonnelRecord record) async { try { await personnelRepository.insert(record); } catch (e) { emit(PersonnelError(e.toString())); } } @override Future close() { _allSubscription?.cancel(); _unassignedSubscription?.cancel(); return super.close(); } }