use super::detail_pages;
use super::{app::ResultsApp, detail_page::DetailPage, detail_shortcuts::DETAIL_SHORTCUTS};
use crate::tui::theme::Theme;
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::Style,
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
};
pub fn render(frame: &mut Frame, app: &ResultsApp) {
let theme = Theme::default();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Min(0), Constraint::Length(2), ])
.split(frame.area());
render_header(frame, app, chunks[0], &theme);
let content_area = apply_content_margins(chunks[1]);
if let Some(item) = app.selected_item() {
match app.nav().detail_page {
DetailPage::Overview => {
detail_pages::overview::render(frame, app, item, content_area, &theme)
}
DetailPage::ScoreBreakdown => {
detail_pages::score_breakdown::render(frame, app, item, content_area, &theme)
}
DetailPage::Context => {
detail_pages::context::render(frame, app, item, content_area, &theme)
}
DetailPage::Dependencies => {
detail_pages::dependencies::render(frame, app, item, content_area, &theme)
}
DetailPage::GitContext => {
detail_pages::git_context::render(frame, app, item, content_area, &theme)
}
DetailPage::Patterns => {
detail_pages::patterns::render(frame, app, item, content_area, &theme)
}
DetailPage::DataFlow => detail_pages::data_flow::render(
frame,
app,
item,
&app.analysis().data_flow_graph,
content_area,
&theme,
),
DetailPage::Responsibilities => {
detail_pages::responsibilities::render(frame, app, item, content_area, &theme)
}
}
} else {
let empty = Paragraph::new("No item selected").style(Style::default().fg(theme.muted));
frame.render_widget(empty, content_area);
}
render_footer(frame, app, chunks[2], &theme);
}
const HORIZONTAL_MARGIN: u16 = 1;
fn apply_content_margins(area: Rect) -> Rect {
const VERTICAL_MARGIN: u16 = 1;
Rect {
x: area.x.saturating_add(HORIZONTAL_MARGIN),
y: area.y.saturating_add(VERTICAL_MARGIN),
width: area.width.saturating_sub(HORIZONTAL_MARGIN * 2),
height: area.height.saturating_sub(VERTICAL_MARGIN * 2),
}
}
fn apply_horizontal_margin(area: Rect) -> Rect {
Rect {
x: area.x.saturating_add(HORIZONTAL_MARGIN),
y: area.y,
width: area.width.saturating_sub(HORIZONTAL_MARGIN * 2),
height: area.height,
}
}
fn render_header(frame: &mut Frame, app: &ResultsApp, area: Rect, theme: &Theme) {
use super::page_availability;
let available =
page_availability::available_pages(app.selected_item(), &app.analysis().data_flow_graph);
let current_page = available
.iter()
.position(|&p| p == app.nav().detail_page)
.map(|i| i + 1)
.unwrap_or(1); let total_pages = available.len();
let page_name = app.nav().detail_page.name();
let grouped_item = app
.selected_group_item_position()
.filter(|(_, total)| *total > 1)
.map(|(index, total)| format!(" [Finding {}/{}]", index + 1, total))
.unwrap_or_default();
let position = format!(
"Detail View ({}/{}) [Page {}/{}] {}{}",
app.list().selected_index() + 1,
app.item_count(),
current_page,
total_pages,
page_name,
grouped_item,
);
let header_area = apply_horizontal_margin(area);
let header = Paragraph::new(vec![
Line::from(Span::styled(position, Style::default().fg(theme.accent()))),
Line::from(Span::styled(
format!("debtmap v{}", env!("CARGO_PKG_VERSION")),
Style::default().fg(theme.muted),
)),
])
.block(Block::default().borders(Borders::BOTTOM));
frame.render_widget(header, header_area);
}
fn render_footer(frame: &mut Frame, app: &ResultsApp, area: Rect, theme: &Theme) {
let shortcuts = detail_footer_shortcuts(theme);
let lines = if let Some(status) = app.status_message() {
let status_color = if status.starts_with('✓') {
theme.success()
} else {
theme.warning()
};
vec![
Line::from(vec![Span::styled(
status,
Style::default().fg(status_color),
)]),
shortcuts,
]
} else {
vec![shortcuts]
};
let footer_area = apply_horizontal_margin(area);
let footer = Paragraph::new(lines).block(Block::default().borders(Borders::TOP));
frame.render_widget(footer, footer_area);
}
fn detail_footer_shortcuts(theme: &Theme) -> Line<'static> {
let spans = DETAIL_SHORTCUTS
.iter()
.filter_map(|shortcut| shortcut.footer)
.flat_map(|(keys, description)| {
[
Span::styled(keys, Style::default().fg(theme.accent())),
Span::raw(format!(":{description} ")),
]
})
.collect::<Vec<_>>();
Line::from(spans)
}