use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use crate::tui::app::{DashboardApp, UiKey};
use crate::tui::rail::{fit, InstanceLine, Tone};
use super::tone_style;
const SPARK_WIDTH: usize = 8;
const SPARK_MIN_WIDTH: usize = 52;
fn row_style(app: &DashboardApp, base: Style, cursor: bool, focused: bool) -> Style {
if cursor && focused {
app.styles.selected
} else if cursor {
base.add_modifier(Modifier::BOLD)
} else {
base
}
}
pub fn instance_line<'a>(
app: &DashboardApp,
line: &InstanceLine,
width: usize,
cursor: bool,
focused: bool,
) -> Line<'a> {
let marker = if cursor && !focused { "▸" } else { " " };
let busy = app
.snapshot
.llm_activity
.iter()
.any(|work| crate::tui::llm_activity::owner(work) == Some(line.key));
let glyph = if busy { app.spinner() } else { line.glyph };
let glyph_tone = if busy {
Tone::Reasoning
} else {
line.glyph_tone
};
let head = format!(
"{marker}{} #{:<3}{:<8} ",
glyph,
line.id,
fit(&line.protocol, 8)
);
let head_width = head.chars().count();
let mut right: Vec<(String, Tone)> = Vec::new();
if let Some(error) = &line.error {
let target = format!("{} ", line.target);
let room = width.saturating_sub(head_width + target.chars().count());
let mut spans = vec![
Span::styled(
head,
row_style(app, tone_style(app, glyph_tone), cursor, focused),
),
Span::styled(target, row_style(app, app.styles.normal, cursor, focused)),
Span::styled(
fit(error, room),
row_style(app, app.styles.error, cursor, focused),
),
];
pad_line(
&mut spans,
width,
row_style(app, app.styles.normal, cursor, focused),
);
return Line::from(spans);
}
if !line.peers.is_empty() {
right.push((format!("{:>3}", line.peers), Tone::Dim));
}
if width >= SPARK_MIN_WIDTH {
let spark = app
.cards
.metrics
.get(&line.key)
.map(|m| m.sparkline(SPARK_WIDTH))
.unwrap_or_else(|| " ".repeat(SPARK_WIDTH));
right.push((format!(" {spark}"), Tone::Accent));
}
if line.waiting > 0 {
right.push((format!(" ⚠{}", line.waiting), Tone::Bad));
}
right.push((format!(" {:>6}", line.driver.label()), line.driver.tone()));
let right_width: usize = right.iter().map(|(s, _)| s.chars().count()).sum();
let room = width.saturating_sub(head_width + right_width + 1);
let target = fit(&line.target, room);
let target_padded = format!("{target:<room$} ");
let mut spans = vec![
Span::styled(
head,
row_style(app, tone_style(app, glyph_tone), cursor, focused),
),
Span::styled(
target_padded,
row_style(app, app.styles.normal, cursor, focused),
),
];
for (text, tone) in right {
spans.push(Span::styled(
text,
row_style(app, tone_style(app, tone), cursor, focused),
));
}
pad_line(
&mut spans,
width,
row_style(app, app.styles.normal, cursor, focused),
);
Line::from(spans)
}
fn pad_line(spans: &mut Vec<Span<'_>>, width: usize, style: Style) {
let used: usize = spans.iter().map(|s| s.content.chars().count()).sum();
if used < width {
spans.push(Span::styled(" ".repeat(width - used), style));
}
}
pub fn kind_tone(key: UiKey) -> Tone {
match key {
UiKey::Server(_) => Tone::Server,
UiKey::Client(_) => Tone::Client,
}
}