use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TodoList {
pub todos: Vec<Todo>,
pub metadata: TodoListMetadata,
pub project: Option<ProjectContext>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Todo {
pub id: String,
pub content: String,
pub status: TodoStatus,
pub priority: TodoPriority,
pub estimated_hours: Option<f32>,
pub dependencies: Vec<String>,
pub quality_gates: TodoQualityGates,
pub tags: Vec<String>,
pub assignee: Option<String>,
#[cfg(feature = "todo-validation")]
pub due_date: Option<chrono::DateTime<chrono::Utc>>,
#[cfg(feature = "todo-validation")]
pub created_at: chrono::DateTime<chrono::Utc>,
pub custom_fields: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
Blocked,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TodoPriority {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TodoQualityGates {
pub complexity_check: bool,
pub completeness_check: bool,
pub actionability_check: bool,
pub time_estimate_check: bool,
pub custom_checks: HashMap<String, bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TodoListMetadata {
pub total_count: usize,
pub status_counts: HashMap<TodoStatus, usize>,
pub priority_counts: HashMap<TodoPriority, usize>,
pub total_estimated_hours: f32,
pub avg_estimated_hours: f32,
pub completion_percentage: f32,
pub dependency_graph_valid: bool,
#[cfg(feature = "todo-validation")]
pub generated_at: chrono::DateTime<chrono::Utc>,
pub template_version: String,
pub custom_metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectContext {
pub name: String,
pub description: Option<String>,
pub project_type: Option<String>,
#[cfg(feature = "todo-validation")]
pub target_date: Option<chrono::DateTime<chrono::Utc>>,
pub stakeholders: Vec<String>,
pub tech_stack: Vec<String>,
pub budget_hours: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TodoInput {
pub project_name: String,
pub requirements: Vec<String>,
pub granularity: TodoGranularity,
pub project_context: Option<ProjectContext>,
pub quality_config: Option<TodoQualityConfig>,
pub max_todos: Option<usize>,
pub include_estimates: bool,
pub default_priority: Option<TodoPriority>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TodoGranularity {
High,
Medium,
Low,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TodoQualityConfig {
pub max_todos_per_batch: Option<usize>,
pub min_task_detail_chars: Option<usize>,
pub max_task_detail_chars: Option<usize>,
pub max_complexity_per_task: Option<u8>,
pub require_time_estimates: bool,
pub require_specific_actions: bool,
pub require_dependency_graph: bool,
pub prevent_circular_dependencies: bool,
pub min_estimated_hours: Option<f32>,
pub max_estimated_hours: Option<f32>,
}
impl Todo {
pub fn new<S: Into<String>>(content: S) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
content: content.into(),
status: TodoStatus::Pending,
priority: TodoPriority::Medium,
estimated_hours: None,
dependencies: Vec::new(),
quality_gates: TodoQualityGates::default(),
tags: Vec::new(),
assignee: None,
#[cfg(feature = "todo-validation")]
due_date: None,
#[cfg(feature = "todo-validation")]
created_at: chrono::Utc::now(),
custom_fields: HashMap::new(),
}
}
pub fn is_actionable(&self) -> bool {
let actionable_verbs = [
"implement",
"create",
"build",
"write",
"add",
"remove",
"update",
"fix",
"test",
"deploy",
"configure",
"setup",
"install",
"design",
"develop",
"refactor",
"optimize",
"migrate",
"integrate",
"debug",
"analyze",
"research",
"document",
"validate",
"verify",
"review",
];
let lower_content = self.content.to_lowercase();
actionable_verbs
.iter()
.any(|verb| lower_content.starts_with(verb))
}
pub fn has_valid_length(&self, min_chars: usize, max_chars: usize) -> bool {
let len = self.content.len();
len >= min_chars && len <= max_chars
}
pub fn complexity_score(&self) -> u8 {
let content = &self.content.to_lowercase();
let mut score = 1;
let complexity_words = [
"integrate",
"refactor",
"optimize",
"migrate",
"analyze",
"algorithm",
"performance",
"security",
"architecture",
];
for word in &complexity_words {
if content.contains(word) {
score += 1;
}
}
if content.contains("database") || content.contains("api") || content.contains("system") {
score += 1;
}
let action_count = content.matches(" and ").count() + content.matches(", ").count();
score += (action_count / 2) as u8;
score.min(10)
}
pub fn has_reasonable_estimate(&self, min_hours: f32, max_hours: f32) -> bool {
match self.estimated_hours {
Some(hours) => hours >= min_hours && hours <= max_hours,
None => false,
}
}
pub const fn progress(&self) -> f32 {
match self.status {
TodoStatus::Pending | TodoStatus::Blocked => 0.0,
TodoStatus::InProgress => 0.5,
TodoStatus::Completed => 1.0,
TodoStatus::Cancelled => 0.0,
}
}
}
impl TodoList {
pub fn new() -> Self {
Self {
todos: Vec::new(),
metadata: TodoListMetadata::default(),
project: None,
}
}
pub fn add_todo(&mut self, todo: Todo) {
self.todos.push(todo);
self.update_metadata_internal(false);
}
pub fn update_metadata(&mut self) {
self.update_metadata_internal(true);
}
fn update_metadata_internal(&mut self, check_cycles: bool) {
let total_count = self.todos.len();
let mut status_counts = HashMap::new();
for todo in &self.todos {
*status_counts.entry(todo.status).or_insert(0) += 1;
}
let mut priority_counts = HashMap::new();
for todo in &self.todos {
*priority_counts.entry(todo.priority).or_insert(0) += 1;
}
let total_estimated_hours: f32 = self.todos.iter().filter_map(|t| t.estimated_hours).sum();
let avg_estimated_hours = if total_count > 0 {
total_estimated_hours / total_count as f32
} else {
0.0
};
let completion_percentage = if total_count > 0 {
let completed_count = status_counts.get(&TodoStatus::Completed).unwrap_or(&0);
*completed_count as f32 / total_count as f32
} else {
0.0
};
let dependency_graph_valid = if check_cycles {
self.validate_dependencies().is_ok()
} else {
true };
self.metadata = TodoListMetadata {
total_count,
status_counts,
priority_counts,
total_estimated_hours,
avg_estimated_hours,
completion_percentage,
dependency_graph_valid,
#[cfg(feature = "todo-validation")]
generated_at: chrono::Utc::now(),
template_version: "1.0.0".to_string(),
custom_metadata: HashMap::new(),
};
}
pub fn validate_dependencies(&self) -> Result<(), Vec<String>> {
use std::collections::{HashMap, HashSet, VecDeque};
let mut graph: HashMap<String, Vec<String>> = HashMap::new();
let mut in_degree: HashMap<String, usize> = HashMap::new();
for todo in &self.todos {
graph.insert(todo.id.clone(), todo.dependencies.clone());
in_degree.insert(todo.id.clone(), 0);
}
for todo in &self.todos {
for dep in &todo.dependencies {
if let Some(degree) = in_degree.get_mut(dep) {
*degree += 1;
}
}
}
let mut queue: VecDeque<String> = in_degree
.iter()
.filter(|(_, °ree)| degree == 0)
.map(|(id, _)| id.clone())
.collect();
let mut processed = 0;
while let Some(current) = queue.pop_front() {
processed += 1;
if let Some(neighbors) = graph.get(¤t) {
for neighbor in neighbors {
if let Some(degree) = in_degree.get_mut(neighbor) {
*degree -= 1;
if *degree == 0 {
queue.push_back(neighbor.clone());
}
}
}
}
}
if processed != self.todos.len() {
let mut cycle = Vec::new();
let remaining: HashSet<String> = in_degree
.iter()
.filter(|(_, °ree)| degree > 0)
.map(|(id, _)| id.clone())
.collect();
if let Some(start) = remaining.iter().next() {
let mut current = start.clone();
let mut visited = HashSet::new();
while !visited.contains(¤t) {
visited.insert(current.clone());
cycle.push(current.clone());
if let Some(deps) = graph.get(¤t) {
if let Some(next) = deps.iter().find(|dep| remaining.contains(*dep)) {
current = next.clone();
} else {
break;
}
} else {
break;
}
}
}
Err(cycle)
} else {
Ok(())
}
}
pub fn todos_by_status(&self, status: TodoStatus) -> Vec<&Todo> {
self.todos.iter().filter(|t| t.status == status).collect()
}
pub fn todos_by_priority(&self, priority: TodoPriority) -> Vec<&Todo> {
self.todos
.iter()
.filter(|t| t.priority == priority)
.collect()
}
pub fn critical_path(&self) -> Vec<String> {
Vec::new()
}
}
impl Default for TodoList {
fn default() -> Self {
Self::new()
}
}
impl Default for TodoQualityGates {
fn default() -> Self {
Self {
complexity_check: false,
completeness_check: false,
actionability_check: false,
time_estimate_check: false,
custom_checks: HashMap::new(),
}
}
}
impl Default for TodoListMetadata {
fn default() -> Self {
Self {
total_count: 0,
status_counts: HashMap::new(),
priority_counts: HashMap::new(),
total_estimated_hours: 0.0,
avg_estimated_hours: 0.0,
completion_percentage: 0.0,
dependency_graph_valid: true,
#[cfg(feature = "todo-validation")]
generated_at: chrono::Utc::now(),
template_version: "1.0.0".to_string(),
custom_metadata: HashMap::new(),
}
}
}
impl Default for TodoQualityConfig {
fn default() -> Self {
Self {
max_todos_per_batch: Some(50),
min_task_detail_chars: Some(10),
max_task_detail_chars: Some(100),
max_complexity_per_task: Some(8),
require_time_estimates: true,
require_specific_actions: true,
require_dependency_graph: true,
prevent_circular_dependencies: true,
min_estimated_hours: Some(0.5),
max_estimated_hours: Some(40.0),
}
}
}
impl Default for TodoInput {
fn default() -> Self {
Self {
project_name: "Sample Project".to_string(),
requirements: vec!["Create basic functionality".to_string()],
granularity: TodoGranularity::Medium,
project_context: None,
quality_config: None,
max_todos: Some(20),
include_estimates: true,
default_priority: Some(TodoPriority::Medium),
}
}
}
impl std::fmt::Display for TodoStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TodoStatus::Pending => write!(f, "pending"),
TodoStatus::InProgress => write!(f, "in_progress"),
TodoStatus::Completed => write!(f, "completed"),
TodoStatus::Blocked => write!(f, "blocked"),
TodoStatus::Cancelled => write!(f, "cancelled"),
}
}
}
impl std::fmt::Display for TodoPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TodoPriority::Low => write!(f, "low"),
TodoPriority::Medium => write!(f, "medium"),
TodoPriority::High => write!(f, "high"),
TodoPriority::Critical => write!(f, "critical"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_todo_creation() {
let todo = Todo::new("Implement user authentication");
assert!(!todo.id.is_empty());
assert_eq!(todo.content, "Implement user authentication");
assert_eq!(todo.status, TodoStatus::Pending);
assert_eq!(todo.priority, TodoPriority::Medium);
}
#[test]
fn test_todo_actionability() {
let actionable = Todo::new("Implement user login system");
assert!(actionable.is_actionable());
let not_actionable = Todo::new("User login stuff");
assert!(!not_actionable.is_actionable());
}
#[test]
fn test_todo_complexity_score() {
let simple = Todo::new("Add button to form");
assert_eq!(simple.complexity_score(), 1);
let complex = Todo::new("Integrate complex authentication system with database and API");
let score = complex.complexity_score();
assert!(score >= 3, "Expected complexity score >= 3, got {}", score);
}
#[test]
fn test_todo_list_metadata() {
let mut list = TodoList::new();
let todo1 = Todo::new("Task 1");
let mut todo2 = Todo::new("Task 2");
todo2.status = TodoStatus::Completed;
list.add_todo(todo1);
list.add_todo(todo2);
assert_eq!(list.metadata.total_count, 2);
assert_eq!(list.metadata.completion_percentage, 0.5);
assert_eq!(
*list
.metadata
.status_counts
.get(&TodoStatus::Pending)
.unwrap_or(&0),
1
);
assert_eq!(
*list
.metadata
.status_counts
.get(&TodoStatus::Completed)
.unwrap_or(&0),
1
);
}
#[test]
fn test_dependency_validation() {
let mut list = TodoList::new();
let mut todo1 = Todo::new("Task 1");
todo1.id = "task1".to_string();
let mut todo2 = Todo::new("Task 2");
todo2.id = "task2".to_string();
todo2.dependencies = vec!["task1".to_string()];
list.add_todo(todo1);
list.add_todo(todo2);
assert!(list.validate_dependencies().is_ok());
list.todos[0].dependencies = vec!["task2".to_string()];
list.update_metadata_internal(false);
assert!(list.validate_dependencies().is_err());
}
#[test]
fn test_todo_progress() {
let pending = Todo::new("Pending task");
assert_eq!(pending.progress(), 0.0);
let mut in_progress = Todo::new("In progress task");
in_progress.status = TodoStatus::InProgress;
assert_eq!(in_progress.progress(), 0.5);
let mut completed = Todo::new("Completed task");
completed.status = TodoStatus::Completed;
assert_eq!(completed.progress(), 1.0);
}
}