import 'package:drift/drift.dart'; import '../../../core/database/database.dart'; part 'rooms_dao.g.dart'; /// Stats for a room including task counts and cleanliness ratio. class RoomWithStats { final Room room; final int totalTasks; final int dueTasks; final int overdueCount; final double cleanlinessRatio; const RoomWithStats({ required this.room, required this.totalTasks, required this.dueTasks, required this.overdueCount, required this.cleanlinessRatio, }); } @DriftAccessor(tables: [Rooms, Tasks, TaskCompletions]) class RoomsDao extends DatabaseAccessor with _$RoomsDaoMixin { RoomsDao(super.attachedDatabase); /// Watch all rooms ordered by sortOrder. Stream> watchAllRooms() { return (select(rooms)..orderBy([(r) => OrderingTerm.asc(r.sortOrder)])) .watch(); } /// Watch all rooms with computed task stats. /// /// Cleanliness ratio = (totalTasks - overdueCount) / totalTasks. /// 1.0 when no tasks are overdue, 0.0 when all are overdue. /// Rooms with no tasks have ratio 1.0. Stream> watchRoomWithStats({DateTime? today}) { final now = today ?? DateTime.now(); final todayDateOnly = DateTime(now.year, now.month, now.day); return watchAllRooms().asyncMap((roomList) async { final stats = []; for (final room in roomList) { final taskList = await (select(tasks) ..where( (t) => t.roomId.equals(room.id) & t.isActive.equals(true), )) .get(); final totalTasks = taskList.length; var overdueCount = 0; var dueTasks = 0; for (final task in taskList) { final dueDate = DateTime( task.nextDueDate.year, task.nextDueDate.month, task.nextDueDate.day, ); if (dueDate.isBefore(todayDateOnly) || dueDate.isAtSameMomentAs(todayDateOnly)) { dueTasks++; } if (dueDate.isBefore(todayDateOnly)) { overdueCount++; } } final ratio = totalTasks == 0 ? 1.0 : (totalTasks - overdueCount) / totalTasks; stats.add(RoomWithStats( room: room, totalTasks: totalTasks, dueTasks: dueTasks, overdueCount: overdueCount, cleanlinessRatio: ratio, )); } return stats; }); } /// Insert a new room. Returns the auto-generated id. Future insertRoom(RoomsCompanion room) => into(rooms).insert(room); /// Update an existing room. Returns true if a row was updated. Future updateRoom(Room room) => update(rooms).replace(room); /// Delete a room and cascade to its tasks and completions. Future deleteRoom(int roomId) { return transaction(() async { // Get all task IDs for this room final taskList = await (select(tasks) ..where((t) => t.roomId.equals(roomId))) .get(); // Delete completions for each task for (final task in taskList) { await (delete(taskCompletions) ..where((c) => c.taskId.equals(task.id))) .go(); } // Delete tasks await (delete(tasks)..where((t) => t.roomId.equals(roomId))).go(); // Delete room await (delete(rooms)..where((r) => r.id.equals(roomId))).go(); }); } /// Reorder rooms by updating sortOrder for each room in the list. Future reorderRooms(List roomIds) { return transaction(() async { for (var i = 0; i < roomIds.length; i++) { await (update(rooms)..where((r) => r.id.equals(roomIds[i]))) .write(RoomsCompanion(sortOrder: Value(i))); } }); } /// Get a single room by its id. Future getRoomById(int id) { return (select(rooms)..where((r) => r.id.equals(id))).getSingle(); } }