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/entities/unit_node.dart'; import 'package:uto_manager/domain/services/mtoe_validation_engine.dart'; import 'package:uto_manager/domain/services/validation_isolate.dart'; // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- /// State for the validation cubit. sealed class ValidationState extends Equatable { const ValidationState(); } /// No validation has been run yet. class ValidationInitial extends ValidationState { const ValidationInitial(); @override List get props => []; } /// Validation is in progress (running in background Isolate). class ValidationRunning extends ValidationState { const ValidationRunning(); @override List get props => []; } /// Validation completed with a (possibly empty) list of findings. class ValidationComplete extends ValidationState { const ValidationComplete(this.findings); final List findings; /// All critical findings. List get critical => findings.where((f) => f.severity == FindingSeverity.critical).toList(); /// All warning findings. List get warnings => findings.where((f) => f.severity == FindingSeverity.warning).toList(); /// All advisory findings. List get advisories => findings.where((f) => f.severity == FindingSeverity.advisory).toList(); /// Returns findings for a specific [unitId]. List findingsForUnit(String unitId) => findings.where((f) => f.unitId == unitId).toList(); /// Returns the highest-severity finding for [unitId], or null if clean. FindingSeverity? worstSeverityForUnit(String unitId) { final unitFindings = findingsForUnit(unitId); if (unitFindings.isEmpty) return null; if (unitFindings.any((f) => f.severity == FindingSeverity.critical)) { return FindingSeverity.critical; } if (unitFindings.any((f) => f.severity == FindingSeverity.warning)) { return FindingSeverity.warning; } return FindingSeverity.advisory; } /// True if there are no findings. bool get isClean => findings.isEmpty; @override List get props => [findings]; } // --------------------------------------------------------------------------- // Cubit // --------------------------------------------------------------------------- /// Runs the [MtoeValidationEngine] against the current unit tree snapshot /// and exposes the resulting [ValidationFinding]s to the UI. /// /// Validation is offloaded to a background [IsolateValidationRunner] so the /// UI thread is never blocked. The isolate is started lazily on the first /// [validate] call and kept alive for the lifetime of the cubit. /// /// Call [validate] whenever the unit tree, slots, or personnel change. class ValidationCubit extends Cubit { ValidationCubit() : super(const ValidationInitial()); /// Synchronous fallback engine used when the isolate is not available /// (e.g., in unit tests or on platforms that do not support isolates). final _syncEngine = const MtoeValidationEngine(); /// Background isolate runner (lazy-initialized). IsolateValidationRunner? _runner; // --------------------------------------------------------------------------- // Validate // --------------------------------------------------------------------------- /// Runs all MTOE validation rules against the provided snapshot. /// /// Attempts to run in a background [IsolateValidationRunner]. Falls back to /// synchronous execution if the isolate cannot be started. /// /// Emits [ValidationRunning] while computing, then [ValidationComplete] /// with the results. Future validate({ required List units, required List slots, required List personnel, }) async { emit(const ValidationRunning()); try { // Attempt isolate-based validation. final runner = await _getRunner(); final response = await runner.validate( units: units, slots: slots, personnel: personnel, ); if (response.hasError) { // Isolate reported an error — fall back to synchronous. final findings = _syncEngine.validate( units: units, slots: slots, personnel: personnel, ); emit(ValidationComplete(findings)); } else { emit(ValidationComplete(response.findings)); } } catch (_) { // Isolate unavailable — run synchronously on the UI thread. final findings = _syncEngine.validate( units: units, slots: slots, personnel: personnel, ); emit(ValidationComplete(findings)); } } /// Clears all findings and resets to the initial state. void clear() => emit(const ValidationInitial()); // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- /// Returns the shared [IsolateValidationRunner], starting it if needed. Future _getRunner() async { if (_runner == null) { _runner = IsolateValidationRunner(); await _runner!.start(); } return _runner!; } @override Future close() async { await _runner?.dispose(); _runner = null; return super.close(); } }