import 'package:drift/drift.dart'; import '../../../core/database/database.dart'; import '../domain/daily_plan_models.dart'; part 'calendar_dao.g.dart'; /// DAO for calendar-based task queries. /// /// Provides date-parameterized queries to answer: /// - "What tasks are due on date X?" /// - "What tasks are overdue relative to today?" @DriftAccessor(tables: [Tasks, Rooms, TaskCompletions]) class CalendarDao extends DatabaseAccessor with _$CalendarDaoMixin { CalendarDao(super.attachedDatabase); /// Watch tasks whose [nextDueDate] falls on the given calendar day. /// /// Returns tasks sorted alphabetically by name. /// Does NOT include overdue carry-over — only tasks originally due on [date]. Stream> watchTasksForDate(DateTime date) { final startOfDay = DateTime(date.year, date.month, date.day); final endOfDay = startOfDay.add(const Duration(days: 1)); final query = select(tasks).join([ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)), ]); query.where( tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) & tasks.nextDueDate.isSmallerThanValue(endOfDay), ); query.orderBy([OrderingTerm.asc(tasks.name)]); return query.watch().map((rows) { return rows.map((row) { final task = row.readTable(tasks); final room = row.readTable(rooms); return TaskWithRoom( task: task, roomName: room.name, roomId: room.id, ); }).toList(); }); } /// Returns the total count of tasks across all rooms and dates. /// /// Used by the UI to distinguish first-run empty state from celebration state. Future getTaskCount() async { final countExp = tasks.id.count(); final query = selectOnly(tasks)..addColumns([countExp]); final result = await query.getSingle(); return result.read(countExp) ?? 0; } /// Watch tasks due on [date] within a specific [roomId]. /// /// Same as [watchTasksForDate] but filtered to a single room. Stream> watchTasksForDateInRoom( DateTime date, int roomId) { final startOfDay = DateTime(date.year, date.month, date.day); final endOfDay = startOfDay.add(const Duration(days: 1)); final query = select(tasks).join([ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)), ]); query.where( tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) & tasks.nextDueDate.isSmallerThanValue(endOfDay) & tasks.roomId.equals(roomId), ); query.orderBy([OrderingTerm.asc(tasks.name)]); return query.watch().map((rows) { return rows.map((row) { final task = row.readTable(tasks); final room = row.readTable(rooms); return TaskWithRoom(task: task, roomName: room.name, roomId: room.id); }).toList(); }); } /// Watch tasks whose [nextDueDate] is strictly before [referenceDate]. /// /// Returns tasks sorted by [nextDueDate] ascending (oldest first). /// Does NOT include tasks due on [referenceDate] itself. Stream> watchOverdueTasks(DateTime referenceDate) { final startOfReferenceDay = DateTime( referenceDate.year, referenceDate.month, referenceDate.day, ); final query = select(tasks).join([ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)), ]); query.where(tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay)); query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]); return query.watch().map((rows) { return rows.map((row) { final task = row.readTable(tasks); final room = row.readTable(rooms); return TaskWithRoom( task: task, roomName: room.name, roomId: room.id, ); }).toList(); }); } /// Watch overdue tasks (before [referenceDate]) within a specific [roomId]. /// /// Same as [watchOverdueTasks] but filtered to a single room. Stream> watchOverdueTasksInRoom( DateTime referenceDate, int roomId) { final startOfReferenceDay = DateTime( referenceDate.year, referenceDate.month, referenceDate.day, ); final query = select(tasks).join([ innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)), ]); query.where( tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay) & tasks.roomId.equals(roomId), ); query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]); return query.watch().map((rows) { return rows.map((row) { final task = row.readTable(tasks); final room = row.readTable(rooms); return TaskWithRoom( task: task, roomName: room.name, roomId: room.id, ); }).toList(); }); } /// Total task count within a specific room. /// /// Used to distinguish first-run empty state from celebration state /// in the room calendar view. Future getTaskCountInRoom(int roomId) async { final countExp = tasks.id.count(); final query = selectOnly(tasks) ..addColumns([countExp]) ..where(tasks.roomId.equals(roomId)); final result = await query.getSingle(); return result.read(countExp) ?? 0; } }