import '../models/task_activity_log.dart'; /// Finds the canonical execution start time for a task. /// /// [logs] is expected in DESCENDING order (newest first), matching the /// `order('created_at', ascending: false)` used by Supabase queries. /// The reversed iteration therefore walks chronologically forward and stops /// at the earliest 'started' event. DateTime? resolveTaskExecutionStart( List logs, DateTime? fallbackStartedAt, ) { DateTime? started; for (final entry in logs.reversed) { if (entry.actionType == 'started') { started = entry.createdAt; break; } } return started ?? fallbackStartedAt; } /// Computes the effective worked duration for a task, subtracting all /// paused intervals between [fallbackStartedAt] (or the first 'started' /// log event) and [endAt]. /// /// [logs] must be in DESCENDING order (newest first) — the same order /// returned by `task_activity_logs` queries with `ascending: false`. Duration computeEffectiveTaskDuration({ required DateTime? fallbackStartedAt, required DateTime endAt, required List logs, }) { final start = resolveTaskExecutionStart(logs, fallbackStartedAt); if (start == null || !endAt.isAfter(start)) return Duration.zero; Duration pausedTotal = Duration.zero; DateTime? pausedSince; // logs.reversed → ascending (chronological) order within [start, endAt] final events = logs.reversed.where((e) { if (e.createdAt.isBefore(start)) return false; if (e.createdAt.isAfter(endAt)) return false; return e.actionType == 'paused' || e.actionType == 'resumed'; }); for (final event in events) { if (event.actionType == 'paused') { pausedSince ??= event.createdAt; } else if (event.actionType == 'resumed' && pausedSince != null) { if (event.createdAt.isAfter(pausedSince)) { pausedTotal += event.createdAt.difference(pausedSince); } pausedSince = null; } } // Task is still paused at endAt — count the open pause interval. if (pausedSince != null && endAt.isAfter(pausedSince)) { pausedTotal += endAt.difference(pausedSince); } final total = endAt.difference(start) - pausedTotal; return total.isNegative ? Duration.zero : total; }