#![allow(dead_code)]
use super::{Priority, TabId};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DelegationStatus {
Pending,
InProgress,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationTask {
pub task_id: String,
pub from_tab: TabId,
pub to_tab: TabId,
pub description: String,
pub priority: Priority,
pub status: DelegationStatus,
pub created_at: DateTime<Utc>,
pub deadline: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub result: Option<String>,
}
impl DelegationTask {
pub fn new(
task_id: String,
from: TabId,
to: TabId,
description: String,
priority: Priority,
) -> Self {
Self {
task_id,
from_tab: from,
to_tab: to,
description,
priority,
status: DelegationStatus::Pending,
created_at: Utc::now(),
deadline: None,
completed_at: None,
result: None,
}
}
pub fn with_deadline(mut self, deadline: DateTime<Utc>) -> Self {
self.deadline = Some(deadline);
self
}
pub fn start(&mut self) {
self.status = DelegationStatus::InProgress;
}
pub fn complete(&mut self, result: String) {
self.status = DelegationStatus::Completed;
self.result = Some(result);
self.completed_at = Some(Utc::now());
}
pub fn fail(&mut self) {
self.status = DelegationStatus::Failed;
self.completed_at = Some(Utc::now());
}
pub fn cancel(&mut self) {
self.status = DelegationStatus::Cancelled;
self.completed_at = Some(Utc::now());
}
pub fn is_pending(&self) -> bool {
self.status == DelegationStatus::Pending
}
pub fn is_completed(&self) -> bool {
self.status == DelegationStatus::Completed
}
pub fn is_active(&self) -> bool {
matches!(
self.status,
DelegationStatus::Pending | DelegationStatus::InProgress
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationResult {
pub task_id: String,
pub from_tab: TabId,
pub to_tab: TabId,
pub result: String,
pub completed_at: DateTime<Utc>,
pub was_successful: bool,
}
pub struct TaskDelegator {
pub(crate) pending_tasks: Vec<DelegationTask>,
completed_results: VecDeque<DelegationResult>,
next_id: u64,
}
const MAX_COMPLETED_RESULTS: usize = 256;
impl TaskDelegator {
pub fn new() -> Self {
Self {
pending_tasks: Vec::new(),
completed_results: VecDeque::new(),
next_id: 1,
}
}
pub fn create_delegation(
&mut self,
from: TabId,
to: TabId,
description: String,
priority: Priority,
) -> Option<String> {
let task_id = self.generate_task_id();
let task = DelegationTask::new(task_id.clone(), from, to, description, priority);
self.pending_tasks.push(task);
Some(task_id)
}
pub fn create_delegation_with_deadline(
&mut self,
from: TabId,
to: TabId,
description: String,
priority: Priority,
deadline: DateTime<Utc>,
) -> Option<String> {
let task_id = self.generate_task_id();
let task = DelegationTask::new(task_id.clone(), from, to, description, priority)
.with_deadline(deadline);
self.pending_tasks.push(task);
Some(task_id)
}
pub fn pending_for_tab(&self, tab_id: TabId) -> Vec<&DelegationTask> {
self.pending_tasks
.iter()
.filter(|t| t.to_tab == tab_id && t.is_pending())
.collect()
}
pub fn active_for_tab(&self, tab_id: TabId) -> Vec<&DelegationTask> {
self.pending_tasks
.iter()
.filter(|t| t.to_tab == tab_id && t.is_active())
.collect()
}
pub fn all_pending(&self) -> &[DelegationTask] {
&self.pending_tasks
}
pub fn pending_sorted_by_priority(&self) -> Vec<&DelegationTask> {
let mut tasks: Vec<&DelegationTask> = self
.pending_tasks
.iter()
.filter(|t| t.is_pending())
.collect();
tasks.sort_by_key(|t| std::cmp::Reverse(t.priority));
tasks
}
pub fn start_task(&mut self, task_id: &str) -> bool {
if let Some(task) = self.pending_tasks.iter_mut().find(|t| t.task_id == task_id) {
task.start();
true
} else {
false
}
}
pub fn take_pending_for_tab(&mut self, tab_id: TabId) -> Option<DelegationTask> {
let mut best_idx: Option<usize> = None;
for (i, task) in self.pending_tasks.iter().enumerate() {
if task.to_tab != tab_id || !task.is_pending() {
continue;
}
match best_idx {
None => best_idx = Some(i),
Some(b) => {
let best = &self.pending_tasks[b];
if task.priority > best.priority
|| (task.priority == best.priority && task.created_at < best.created_at)
{
best_idx = Some(i);
}
}
}
}
best_idx.map(|i| {
self.pending_tasks[i].start();
self.pending_tasks[i].clone()
})
}
pub fn peek_pending_for_tab(&self, tab_id: TabId) -> Option<&DelegationTask> {
self.pending_tasks
.iter()
.filter(|t| t.to_tab == tab_id && t.is_pending())
.max_by(|a, b| {
a.priority
.cmp(&b.priority)
.then_with(|| b.created_at.cmp(&a.created_at))
})
}
pub fn complete(&mut self, task_id: &str, result: String) {
let pos = self.pending_tasks.iter().position(|t| t.task_id == task_id);
let Some(pos) = pos else { return };
let mut task = self.pending_tasks.swap_remove(pos);
let from = task.from_tab;
let to = task.to_tab;
task.complete(result.clone());
self.completed_results.push_back(DelegationResult {
task_id: task_id.to_string(),
from_tab: from,
to_tab: to,
result,
completed_at: Utc::now(),
was_successful: true,
});
if self.completed_results.len() > MAX_COMPLETED_RESULTS {
self.completed_results.pop_front();
}
}
pub fn fail_task(&mut self, task_id: &str) {
let pos = self.pending_tasks.iter().position(|t| t.task_id == task_id);
let Some(pos) = pos else { return };
let mut task = self.pending_tasks.swap_remove(pos);
let from = task.from_tab;
let to = task.to_tab;
task.fail();
self.completed_results.push_back(DelegationResult {
task_id: task_id.to_string(),
from_tab: from,
to_tab: to,
result: String::new(),
completed_at: Utc::now(),
was_successful: false,
});
if self.completed_results.len() > MAX_COMPLETED_RESULTS {
self.completed_results.pop_front();
}
}
pub fn cancel_task(&mut self, task_id: &str) -> bool {
let Some(pos) = self.pending_tasks.iter().position(|t| t.task_id == task_id) else {
return false;
};
let mut task = self.pending_tasks.swap_remove(pos);
task.cancel();
true
}
pub fn results_for_tab(&self, tab_id: TabId) -> Vec<&DelegationResult> {
self.completed_results
.iter()
.filter(|r| r.to_tab == tab_id)
.collect()
}
pub fn pending_count(&self, tab_id: TabId) -> usize {
self.pending_tasks
.iter()
.filter(|t| t.to_tab == tab_id && t.is_pending())
.count()
}
pub fn prune_results(&mut self, keep_last: usize) {
while self.completed_results.len() > keep_last {
self.completed_results.pop_front();
}
}
pub fn recent_results(&self, limit: usize) -> Vec<&DelegationResult> {
let mut results: Vec<&DelegationResult> = self.completed_results.iter().collect();
results.sort_by_key(|r| std::cmp::Reverse(r.completed_at));
results.into_iter().take(limit).collect()
}
fn generate_task_id(&mut self) -> String {
let id = self.next_id;
self.next_id += 1;
format!("delegation_{}", id)
}
pub(crate) fn advance_next_id_past_existing_tasks(&mut self) {
let max_seen = self
.pending_tasks
.iter()
.filter_map(|task| task.task_id.strip_prefix("delegation_"))
.filter_map(|suffix| suffix.parse::<u64>().ok())
.max()
.unwrap_or(0);
self.next_id = self.next_id.max(max_seen + 1);
}
}
impl Default for TaskDelegator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_and_complete_delegation() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
let task_id = delegator
.create_delegation(from, to, "Fix the bug".to_string(), Priority::High)
.unwrap();
assert_eq!(delegator.pending_count(to), 1);
delegator.complete(&task_id, "Fixed successfully".to_string());
let results = delegator.results_for_tab(from);
assert!(results.is_empty());
let results_to = delegator.results_for_tab(to);
assert_eq!(results_to.len(), 1);
assert!(results_to[0].was_successful);
}
#[test]
fn test_priority_ordering() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
delegator.create_delegation(from, to, "Low priority".to_string(), Priority::Low);
delegator.create_delegation(from, to, "Urgent".to_string(), Priority::Urgent);
delegator.create_delegation(from, to, "Normal".to_string(), Priority::Normal);
delegator.create_delegation(from, to, "High".to_string(), Priority::High);
let sorted = delegator.pending_sorted_by_priority();
assert_eq!(sorted[0].description, "Urgent");
assert_eq!(sorted[1].description, "High");
assert_eq!(sorted[2].description, "Normal");
assert_eq!(sorted[3].description, "Low priority");
}
#[test]
fn test_take_pending_priority_order() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
delegator.create_delegation(from, to, "Low task".to_string(), Priority::Low);
delegator.create_delegation(from, to, "Urgent task".to_string(), Priority::Urgent);
delegator.create_delegation(from, to, "Normal task".to_string(), Priority::Normal);
let task = delegator.take_pending_for_tab(to).unwrap();
assert_eq!(task.description, "Urgent task");
assert_eq!(task.priority, Priority::Urgent);
let task = delegator.take_pending_for_tab(to).unwrap();
assert_eq!(task.description, "Normal task");
assert_eq!(task.priority, Priority::Normal);
let task = delegator.take_pending_for_tab(to).unwrap();
assert_eq!(task.description, "Low task");
assert_eq!(task.priority, Priority::Low);
assert!(delegator.take_pending_for_tab(to).is_none());
}
#[test]
fn test_take_pending_filters_by_tab() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to_a = TabId::new(2);
let to_b = TabId::new(3);
delegator.create_delegation(from, to_a, "For A".to_string(), Priority::High);
delegator.create_delegation(from, to_b, "For B".to_string(), Priority::High);
let task = delegator.take_pending_for_tab(to_a).unwrap();
assert_eq!(task.description, "For A");
let task = delegator.take_pending_for_tab(to_b).unwrap();
assert_eq!(task.description, "For B");
assert!(delegator.take_pending_for_tab(to_a).is_none());
assert!(delegator.take_pending_for_tab(to_b).is_none());
}
#[test]
fn test_peek_pending_does_not_remove() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
delegator.create_delegation(from, to, "Task".to_string(), Priority::High);
assert!(delegator.peek_pending_for_tab(to).is_some());
assert!(delegator.peek_pending_for_tab(to).is_some());
assert_eq!(delegator.pending_count(to), 1);
let task = delegator.take_pending_for_tab(to).unwrap();
assert_eq!(task.description, "Task");
assert!(delegator.peek_pending_for_tab(to).is_none());
}
#[test]
fn test_auto_prune_bounded_results() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
for i in 0..1000 {
let task_id = delegator
.create_delegation(from, to, format!("Task {}", i), Priority::Normal)
.unwrap();
delegator.complete(&task_id, format!("Result {}", i));
}
let results = delegator.results_for_tab(to);
assert!(
results.len() <= 256,
"Results should be bounded, got {}",
results.len()
);
}
#[test]
fn test_prune_results_o1() {
let mut delegator = TaskDelegator::new();
let from = TabId::new(1);
let to = TabId::new(2);
for i in 0..100 {
let task_id = delegator
.create_delegation(from, to, format!("Task {}", i), Priority::Normal)
.unwrap();
delegator.complete(&task_id, format!("Result {}", i));
}
assert_eq!(delegator.results_for_tab(to).len(), 100);
delegator.prune_results(5);
assert_eq!(delegator.results_for_tab(to).len(), 5);
delegator.prune_results(3);
assert_eq!(delegator.results_for_tab(to).len(), 3);
delegator.prune_results(10);
assert_eq!(delegator.results_for_tab(to).len(), 3);
}
#[test]
fn test_advance_next_id_after_restore() {
let mut delegator = TaskDelegator::new();
delegator.pending_tasks.push(DelegationTask::new(
"delegation_42".to_string(),
TabId::new(1),
TabId::new(2),
"restored".to_string(),
Priority::Normal,
));
delegator.advance_next_id_past_existing_tasks();
let new_id = delegator
.create_delegation(
TabId::new(1),
TabId::new(2),
"fresh".to_string(),
Priority::Normal,
)
.unwrap();
assert_eq!(new_id, "delegation_43");
}
}