import 'package:drift/drift.dart' show Value; import 'package:uto_manager/domain/entities/doctrinal_branch.dart'; import 'package:uto_manager/domain/entities/echelon.dart'; import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/repositories/unit_repository.dart'; import 'package:uto_manager/infrastructure/database/app_database.dart'; import 'package:uto_manager/infrastructure/database/daos/unit_dao.dart'; /// Drift-backed implementation of [UnitRepository]. /// /// Converts between Drift [Unit] data rows and domain [UnitNode] entities. /// All persistence is handled via [UnitDao]. class DriftUnitRepository implements UnitRepository { const DriftUnitRepository({required this.dao, required this.db}); final UnitDao dao; final AppDatabase db; // --------------------------------------------------------------------------- // Mapping helpers // --------------------------------------------------------------------------- UnitNode _toDomain(Unit row) { return UnitNode( unitId: row.unitId, echelon: Echelon.values.firstWhere((e) => e.name == row.echelon), branch: DoctrinalBranch.values.firstWhere((b) => b.name == row.branch), parentUnitId: row.parentUnitId, name: row.name, isKeyLeader: row.isKeyLeader, ); } UnitsCompanion _toCompanion(UnitNode unit) { return UnitsCompanion.insert( unitId: unit.unitId, echelon: unit.echelon.name, branch: unit.branch.name, parentUnitId: Value(unit.parentUnitId), name: unit.name, isKeyLeader: unit.isKeyLeader, ); } // --------------------------------------------------------------------------- // UnitRepository implementation // --------------------------------------------------------------------------- @override Future> getAll() async { final rows = await dao.getAllUnits(); return rows.map(_toDomain).toList(); } @override Future getById(String unitId) async { final row = await dao.getUnitById(unitId); return row == null ? null : _toDomain(row); } @override Future> getChildren(String parentUnitId) async { final rows = await dao.getChildren(parentUnitId); return rows.map(_toDomain).toList(); } @override Future> getRoots() async { final rows = await dao.getRoots(); return rows.map(_toDomain).toList(); } @override Future> getByEchelon(Echelon echelon) async { final rows = await dao.getByEchelon(echelon.name); return rows.map(_toDomain).toList(); } @override Future> getSubtree(String unitId) async { final all = await getAll(); return _collectSubtree(unitId, all); } @override Future insert(UnitNode unit) async { final existing = await dao.getUnitById(unit.unitId); if (existing != null) { throw StateError('Unit ${unit.unitId} already exists.'); } await dao.insertUnit(_toCompanion(unit)); } @override Future insertAll(List units) async { await dao.insertAllUnits(units.map(_toCompanion).toList()); } @override Future update(UnitNode unit) async { final existing = await dao.getUnitById(unit.unitId); if (existing == null) { throw StateError('Unit ${unit.unitId} not found.'); } await dao.updateUnit(_toCompanion(unit)); } @override Future delete(String unitId) async { await dao.deleteUnit(unitId); } @override Future deleteSubtree(String unitId) async { final all = await getAll(); final subtree = _collectSubtree(unitId, all); // Delete leaves first (bottom-up) to respect FK constraints. for (final unit in subtree.reversed) { await dao.deleteUnit(unit.unitId); } } @override Future reparent(String unitId, String newParentId) async { final unit = await getById(unitId); if (unit == null) throw StateError('Unit $unitId not found.'); final newParent = await getById(newParentId); if (newParent == null) throw StateError('Parent $newParentId not found.'); if (!unit.echelon.isValidParent(newParent.echelon)) { throw ArgumentError( '${unit.echelon.displayName} cannot be a child of ' '${newParent.echelon.displayName}.', ); } final updated = unit.copyWith(parentUnitId: newParentId); await dao.updateUnit(_toCompanion(updated)); } @override Stream> watchAll() { return dao.watchAllUnits().map((rows) => rows.map(_toDomain).toList()); } @override Stream> watchSubtree(String unitId) { return watchAll().map((all) => _collectSubtree(unitId, all)); } @override Future deleteAll() async { await dao.deleteAllUnits(); } // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- List _collectSubtree(String rootId, List all) { final result = []; final queue = [rootId]; while (queue.isNotEmpty) { final current = queue.removeAt(0); final node = all.firstWhere( (u) => u.unitId == current, orElse: () => throw StateError('Unit $current not found'), ); result.add(node); queue.addAll(all.where((u) => u.parentUnitId == current).map((u) => u.unitId)); } return result; } }