use crate::agent_sessions::{AgentSession, Freshness, WorktreeAgents};
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DetailMode {
#[default]
List,
Input,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DetailRole {
Normal,
Active,
Muted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DetailRow {
pub label: String,
pub value: String,
pub role: DetailRole,
pub meta: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct DetailOverlay {
pub title: String,
pub rows: Vec<DetailRow>,
pub selected: usize,
pub mode: DetailMode,
pub input: String,
pub input_selected: usize,
}
impl DetailOverlay {
pub fn open(&mut self, title: String, rows: Vec<DetailRow>) {
self.title = title;
self.rows = rows;
self.selected = 0;
self.mode = DetailMode::List;
self.input.clear();
self.input_selected = 0;
}
pub fn set_rows(&mut self, rows: Vec<DetailRow>) {
self.rows = rows;
self.selected = self.selected.min(self.rows.len().saturating_sub(1));
}
pub fn select_next(&mut self) {
self.selected = (self.selected + 1).min(self.rows.len().saturating_sub(1));
}
pub fn select_prev(&mut self) {
self.selected = self.selected.saturating_sub(1);
}
pub fn selected_meta(&self) -> Option<&str> {
self.rows.get(self.selected).and_then(|r| r.meta.as_deref())
}
}
pub fn agent_detail_rows(agents: Option<&WorktreeAgents>, pinned: &[String], now: SystemTime) -> Vec<DetailRow> {
let sessions = agents.map(|a| a.sessions.as_slice()).unwrap_or_default();
if sessions.is_empty() {
return vec![DetailRow {
label: "agents".into(),
value: "no agent session found".into(),
role: DetailRole::Muted,
meta: None,
}];
}
sessions
.iter()
.map(|s| {
let freshness = Freshness::classify(s.last_activity, s.ended, now);
let (word, role) = match freshness {
Freshness::Active => ("active", DetailRole::Active),
Freshness::Idle => ("idle", DetailRole::Muted),
};
let ago = now
.duration_since(s.last_activity)
.map(crate::worktree::format_relative_duration)
.unwrap_or_else(|_| "now".into());
let identity = s.name.as_deref().unwrap_or(&s.id);
let pin_mark = if pinned.iter().any(|p| p == &s.id) {
" · pinned"
} else {
""
};
DetailRow {
label: s.kind.display().to_string(),
value: format!("{word} · {ago} ago · {identity}{pin_mark}"),
role,
meta: Some(s.id.clone()),
}
})
.collect()
}
pub fn filter_sessions<'a>(all: &'a [AgentSession], query: &str) -> Vec<&'a AgentSession> {
let q = query.to_lowercase();
all
.iter()
.filter(|s| {
q.is_empty()
|| s.id.to_lowercase().contains(&q)
|| s.name.as_deref().is_some_and(|n| n.to_lowercase().contains(&q))
|| s.kind.display().contains(&q)
})
.collect()
}