use ratatui::{
style::{Color, Modifier, Style},
text::{Line, Span},
};
use crate::matrix::Matrix;
use crate::parser::Step;
use crate::verifier::VerificationResult;
pub fn build_content(
ori: &Matrix,
steps: &[Step],
results: &[VerificationResult],
show_detail: bool,
col_widths: &[usize],
) -> Vec<Line<'static>> {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(Span::styled(
"── ori ──",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
)));
for r in 0..ori.nrows() {
lines.push(fmt_row(ori, r, &Style::default(), col_widths));
}
lines.push(Line::raw(""));
for (step_idx, step) in steps.iter().enumerate() {
let result = results.get(step_idx);
let (_, color) = match result {
Some(VerificationResult::Correct { .. }) => ("✓", Color::Green),
Some(VerificationResult::Incorrect { .. }) => ("✗", Color::Red),
Some(VerificationResult::Error { .. }) => ("⚠", Color::Yellow),
None => ("?", Color::Gray),
};
lines.push(Line::from(Span::styled(
format!("step {}", step_idx + 1),
Style::default().fg(color).add_modifier(Modifier::BOLD),
)));
lines.push(Line::raw(""));
let is_col = step.kind == crate::parser::OpKind::Col;
let ref_indices: Vec<usize> = step
.ops
.iter()
.filter_map(|(_, s)| {
crate::math::parse_operation(s, 0).ok().and_then(|op| {
if is_col {
op.source_col()
} else {
op.source_row()
}
})
})
.collect();
for r in 0..step.expected.nrows() {
let op = if !is_col {
step.ops.iter().find(|(row, _)| *row == r + 1)
} else {
None
};
let is_read = !op.is_some() && !is_col && ref_indices.contains(&(r + 1));
let (row_style, op_text, op_style) = match op {
Some((_, op_str)) => {
let s = match result {
Some(VerificationResult::Correct { .. }) => {
Style::default().fg(Color::Green)
}
Some(VerificationResult::Incorrect { .. }) => {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
}
_ => Style::default(),
};
(Style::default(), format!(" {}", op_str), s)
}
None if is_read => (
Style::default().fg(Color::Blue),
String::new(),
Style::default().fg(Color::Blue),
),
None => (Style::default(), String::new(), Style::default()),
};
let mut spans: Vec<Span> = Vec::new();
for c in 0..step.expected.ncols() {
let ev = step.expected[(r, c)];
let wrong = match result {
Some(VerificationResult::Incorrect { got, .. }) => {
(ev - got[(r, c)]).abs() > 1e-10
}
_ => false,
};
let col_style = if is_col && ref_indices.contains(&(c + 1)) {
Style::default().fg(Color::Blue)
} else {
row_style
};
let cs = if wrong {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
} else {
col_style
};
let w = col_widths.get(c).copied().unwrap_or(6);
spans.push(Span::styled(format!("{:<w$}", fmt_num(ev), w = w), cs));
}
spans.push(Span::styled(op_text, op_style));
lines.push(Line::from(spans));
}
if is_col {
for op_row in &step.op_slices {
let mut spans: Vec<Span> = Vec::new();
for (c, cell) in op_row.iter().enumerate() {
let is_target = step.ops.iter().any(|(col, _)| *col == c + 1);
let is_ref = !is_target && ref_indices.contains(&(c + 1));
let style = if cell == "|" {
Style::default().fg(Color::DarkGray)
} else if is_target {
match result {
Some(VerificationResult::Correct { .. }) => {
Style::default().fg(Color::Green)
}
Some(VerificationResult::Incorrect { .. }) => {
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
}
_ => Style::default(),
}
} else if is_ref {
Style::default().fg(Color::Blue)
} else {
Style::default()
};
let display = if cell == "." { "" } else { cell.as_str() };
let w = col_widths.get(c).copied().unwrap_or(6);
let txt = format!("{:<w$}", display, w = w);
spans.push(Span::styled(txt, style));
}
lines.push(Line::from(spans));
}
}
lines.push(Line::raw(""));
if show_detail {
if let Some(VerificationResult::Error { message, .. }) = result {
lines.push(Line::from(Span::styled(
format!(" Error: {}", message),
Style::default().fg(Color::Yellow),
)));
lines.push(Line::raw(""));
}
}
}
lines
}
pub fn fmt_row(m: &Matrix, r: usize, style: &Style, widths: &[usize]) -> Line<'static> {
let spans: Vec<Span> = (0..m.ncols())
.map(|c| {
let w = widths.get(c).copied().unwrap_or(6);
Span::styled(format!("{:<w$}", fmt_num(m[(r, c)]), w = w), *style)
})
.collect();
Line::from(spans)
}
pub fn fmt_num(v: f64) -> String {
if (v - v.round()).abs() < 1e-10 {
format!("{:.0}", v)
} else {
format!("{:.2}", v)
}
}