use anyhow::Result;
use rusqlite::Connection;
use horae_core::model::task::{Status, Task};
use horae_core::repo::tags;
use horae_core::repo::tasks;
use horae_core::schedule;
use horae_core::time;
pub fn run(conn: &Connection, start: bool) -> Result<()> {
let filter = tasks::ListFilter {
status: None,
tags: vec![],
query: None,
review_stale: false,
};
let all_tasks = tasks::list(conn, &filter)?;
let task_ids: Vec<&str> = all_tasks.iter().map(|t| t.id.as_str()).collect();
let tags_map = tags::get_tags_for_tasks(conn, &task_ids)?;
let now = time::now_ms();
let mut best_task: Option<(i64, &Task)> = None;
for task in &all_tasks {
if matches!(
task.status,
Status::Someday | Status::Reference | Status::Done
) {
continue;
}
let due = schedule::effective_due(task);
if task.status == Status::Waiting {
if due.map(|d| d > now).unwrap_or(true) {
continue;
}
}
if task.status == Status::Scheduled {
let started = task.scheduled_start_at.map(|s| s <= now).unwrap_or(false);
let is_overdue = due.map(|d| d <= now).unwrap_or(false);
if !started && !is_overdue {
continue;
}
}
let has_quote = tags_map
.get(&task.id)
.map(|tags| tags.iter().any(|t| t == tasks::QUOTE_TAG))
.unwrap_or(false);
if has_quote {
continue;
}
let mut score = 0_i64;
match task.priority.as_deref() {
Some("high") => score += 10000,
Some("medium") => score += 5000,
Some("low") => score += 1000,
_ => {}
}
if task.status == Status::Next {
score += 500;
}
if let Some(d) = due {
if d < now {
score += 2000;
let days_overdue = (now - d) / (1000 * 60 * 60 * 24);
score += days_overdue.min(50) * 10;
} else {
let ms_to_due = d - now;
let days_to_due = ms_to_due / (1000 * 60 * 60 * 24);
if days_to_due == 0 {
score += 1000;
}
}
}
if let Some(s) = task.scheduled_start_at {
if s <= now {
score += 1500;
let days_started = (now - s) / (1000 * 60 * 60 * 24);
score += days_started.min(50) * 5;
}
}
if let Some((best_score, best_t)) = best_task {
if score > best_score || (score == best_score && task.created_at < best_t.created_at) {
best_task = Some((score, task));
}
} else {
best_task = Some((score, task));
}
}
if let Some((score, task)) = best_task {
println!("🎯 专注目标 (推荐分数: {})", score);
println!("📝 {}", task.title);
if let Some(due) = schedule::effective_due(task) {
let label = if task.due_at.is_some() {
"截止"
} else if task.rrule.is_some() {
"周期发生"
} else {
"计划开始"
};
println!("⏰ {}: {}", label, time::format_local(Some(due)));
}
if start {
println!("🚀 启动番茄钟...");
crate::commands::pomo::start(conn, &task.id)?;
} else {
println!("\n💡 提示: 附加 --start 直接开启番茄钟,或在 TUI 中按 P");
}
} else {
println!("🎉 没有紧迫任务,太棒了!");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::capture::{run as capture, CaptureArgs};
use horae_core::testutil::test_conn;
#[test]
fn test_focus_empty() {
let (_dir, conn) = test_conn();
assert!(run(&conn, false).is_ok());
}
#[test]
fn test_focus_picks_best_task() {
let (_dir, conn) = test_conn();
capture(
&conn,
CaptureArgs {
title: "low priority".into(),
clip: false,
tags: vec![],
high: false,
medium: false,
low: true,
due: None,
status: None,
notes: None,
json: false,
},
)
.unwrap();
capture(
&conn,
CaptureArgs {
title: "high priority".into(),
clip: false,
tags: vec![],
high: true,
medium: false,
low: false,
due: None,
status: Some("next".into()),
notes: None,
json: false,
},
)
.unwrap();
assert!(run(&conn, false).is_ok());
}
#[test]
fn test_focus_picks_scheduled_task() {
let (_dir, conn) = test_conn();
capture(
&conn,
CaptureArgs {
title: "normal task".into(),
clip: false,
tags: vec![],
high: false,
medium: false,
low: false,
due: None,
status: None,
notes: None,
json: false,
},
)
.unwrap();
capture(
&conn,
CaptureArgs {
title: "scheduled task ~2000-01-01 09:00".into(),
clip: false,
tags: vec![],
high: false,
medium: false,
low: false,
due: None,
status: None,
notes: None,
json: false,
},
)
.unwrap();
assert!(run(&conn, false).is_ok());
}
}