170 lines
5.6 KiB
Dart
170 lines
5.6 KiB
Dart
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<Object?> get props => [];
|
|
}
|
|
|
|
/// Validation is in progress (running in background Isolate).
|
|
class ValidationRunning extends ValidationState {
|
|
const ValidationRunning();
|
|
|
|
@override
|
|
List<Object?> get props => [];
|
|
}
|
|
|
|
/// Validation completed with a (possibly empty) list of findings.
|
|
class ValidationComplete extends ValidationState {
|
|
const ValidationComplete(this.findings);
|
|
|
|
final List<ValidationFinding> findings;
|
|
|
|
/// All critical findings.
|
|
List<ValidationFinding> get critical =>
|
|
findings.where((f) => f.severity == FindingSeverity.critical).toList();
|
|
|
|
/// All warning findings.
|
|
List<ValidationFinding> get warnings =>
|
|
findings.where((f) => f.severity == FindingSeverity.warning).toList();
|
|
|
|
/// All advisory findings.
|
|
List<ValidationFinding> get advisories =>
|
|
findings.where((f) => f.severity == FindingSeverity.advisory).toList();
|
|
|
|
/// Returns findings for a specific [unitId].
|
|
List<ValidationFinding> 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<Object?> 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<ValidationState> {
|
|
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<void> validate({
|
|
required List<UnitNode> units,
|
|
required List<PositionSlot> slots,
|
|
required List<PersonnelRecord> 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<IsolateValidationRunner> _getRunner() async {
|
|
if (_runner == null) {
|
|
_runner = IsolateValidationRunner();
|
|
await _runner!.start();
|
|
}
|
|
return _runner!;
|
|
}
|
|
|
|
@override
|
|
Future<void> close() async {
|
|
await _runner?.dispose();
|
|
_runner = null;
|
|
return super.close();
|
|
}
|
|
}
|