import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/repositories/unit_repository.dart'; import 'package:uto_manager/domain/services/unit_tree_service.dart'; import 'package:uto_manager/infrastructure/repositories/drift_position_slot_repository.dart'; // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- /// State for the unit tree cubit. sealed class UnitTreeState extends Equatable { const UnitTreeState(); } /// Initial state before any data is loaded. class UnitTreeInitial extends UnitTreeState { const UnitTreeInitial(); @override List get props => []; } /// State while the unit tree is loading. class UnitTreeLoading extends UnitTreeState { const UnitTreeLoading(); @override List get props => []; } /// State when the unit tree has been loaded successfully. class UnitTreeLoaded extends UnitTreeState { const UnitTreeLoaded({ required this.units, required this.slots, required this.roots, this.selectedUnitId, }); /// Flat list of all unit nodes. final List units; /// Flat list of all position slots. final List slots; /// Root-level [UnitTreeNode]s (built from units + slots). final List roots; /// The currently selected unit ID, or null if none selected. final String? selectedUnitId; /// Returns the selected [UnitNode], or null. UnitNode? get selectedUnit => selectedUnitId == null ? null : units.firstWhere((u) => u.unitId == selectedUnitId); /// Returns slots for the selected unit. List get selectedUnitSlots => selectedUnitId == null ? [] : slots.where((s) => s.unitId == selectedUnitId).toList(); UnitTreeLoaded copyWith({ List? units, List? slots, List? roots, String? selectedUnitId, bool clearSelection = false, }) { return UnitTreeLoaded( units: units ?? this.units, slots: slots ?? this.slots, roots: roots ?? this.roots, selectedUnitId: clearSelection ? null : (selectedUnitId ?? this.selectedUnitId), ); } @override List get props => [units, slots, roots, selectedUnitId]; } /// State when an error occurred loading the unit tree. class UnitTreeError extends UnitTreeState { const UnitTreeError(this.message); final String message; @override List get props => [message]; } // --------------------------------------------------------------------------- // Cubit // --------------------------------------------------------------------------- /// Manages the unit tree state, including loading, selection, and re-parenting. class UnitTreeCubit extends Cubit { UnitTreeCubit({ required this.unitRepository, required this.slotRepository, required this.treeService, }) : super(const UnitTreeInitial()); final UnitRepository unitRepository; final DriftPositionSlotRepository slotRepository; final UnitTreeService treeService; StreamSubscription>? _unitSubscription; StreamSubscription>? _slotSubscription; List _units = []; List _slots = []; // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- /// Starts watching the database for unit and slot changes. void startWatching() { emit(const UnitTreeLoading()); _unitSubscription = unitRepository.watchAll().listen( (units) { _units = units; _rebuild(); }, onError: (Object e) => emit(UnitTreeError(e.toString())), ); _slotSubscription = slotRepository.watchAll().listen( (slots) { _slots = slots; _rebuild(); }, onError: (Object e) => emit(UnitTreeError(e.toString())), ); } void _rebuild() { final roots = treeService.buildTree(_units, _slots); final currentSelection = state is UnitTreeLoaded ? (state as UnitTreeLoaded).selectedUnitId : null; emit(UnitTreeLoaded( units: _units, slots: _slots, roots: roots, selectedUnitId: currentSelection)); } // --------------------------------------------------------------------------- // Selection // --------------------------------------------------------------------------- /// Selects the unit with [unitId]. void selectUnit(String unitId) { if (state is UnitTreeLoaded) { emit((state as UnitTreeLoaded).copyWith(selectedUnitId: unitId)); } } /// Clears the current unit selection. void clearSelection() { if (state is UnitTreeLoaded) { emit((state as UnitTreeLoaded).copyWith(clearSelection: true)); } } // --------------------------------------------------------------------------- // Mutations // --------------------------------------------------------------------------- /// Adds a new [unit] to the tree. Future addUnit(UnitNode unit) async { try { await unitRepository.insert(unit); } catch (e) { emit(UnitTreeError(e.toString())); } } /// Re-parents [unitId] under [newParentId] after doctrinal validation. Future reparentUnit(String unitId, String newParentId) async { if (state is! UnitTreeLoaded) return false; final loaded = state as UnitTreeLoaded; final child = loaded.units.firstWhere((u) => u.unitId == unitId); final newParent = loaded.units.firstWhere((u) => u.unitId == newParentId); final validation = treeService.validateReparent(child, newParent, loaded.units); if (!validation.isValid) return false; try { await unitRepository.reparent(unitId, newParentId); return true; } catch (e) { emit(UnitTreeError(e.toString())); return false; } } /// Deletes [unitId] and all its descendants. Future deleteSubtree(String unitId) async { try { await unitRepository.deleteSubtree(unitId); } catch (e) { emit(UnitTreeError(e.toString())); } } /// Clears all units and slots (template reset). Future clearAll() async { try { await slotRepository.deleteAll(); await unitRepository.deleteAll(); } catch (e) { emit(UnitTreeError(e.toString())); } } @override Future close() { _unitSubscription?.cancel(); _slotSubscription?.cancel(); return super.close(); } }