use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, Paragraph, Wrap},
Frame,
};
use crate::state::AppState;
pub fn render_annotation_menu(frame: &mut Frame, state: &AppState) {
let theme = &state.theme;
let area = frame.area();
let dialog_width = 60.min(area.width.saturating_sub(4));
let dialog_height = 20.min(area.height.saturating_sub(4)).max(10);
let x = (area.width.saturating_sub(dialog_width)) / 2;
let y = (area.height.saturating_sub(dialog_height)) / 2;
let dialog_area = Rect::new(x, y, dialog_width, dialog_height);
frame.render_widget(Clear, dialog_area);
let title = if let Some(item) = state.annotation_menu_items.first() {
let lineno = item.sort_line();
format!(" Annotations at line {lineno} ")
} else {
" Annotations ".to_string()
};
let block = Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.secondary));
let inner = block.inner(dialog_area);
frame.render_widget(block, dialog_area);
let list_items = state.annotation_menu_items.len() as u16;
let list_height = list_items.min(inner.height.saturating_sub(4));
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(list_height), Constraint::Length(1), Constraint::Min(1), Constraint::Length(1), ])
.split(inner);
let mut lines: Vec<Line> = Vec::new();
for (idx, item) in state.annotation_menu_items.iter().enumerate() {
let is_selected = idx == state.annotation_menu_selected;
let prefix = if is_selected { " \u{25b6} " } else { " " };
let range_text = item.range_text();
let category_text = format!("[{}|{}]", item.category.label(), item.severity.label());
let first_line = item
.comment
.lines()
.next()
.unwrap_or("")
.chars()
.take(
(inner.width as usize)
.saturating_sub(prefix.len() + range_text.len() + category_text.len() + 6),
)
.collect::<String>();
let name_style = if is_selected {
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(theme.text)
};
let range_style = if is_selected {
Style::default().fg(theme.warning)
} else {
Style::default().fg(theme.text_muted)
};
let category_style = if is_selected {
Style::default().fg(theme.success)
} else {
Style::default().fg(theme.text_muted)
};
lines.push(Line::from(vec![
Span::styled(prefix, name_style),
Span::styled(format!("{} ", category_text), category_style),
Span::styled(format!("{range_text}: "), range_style),
Span::styled(first_line, name_style),
]));
}
frame.render_widget(Paragraph::new(lines), rows[0]);
let sep = "\u{2500}".repeat(inner.width as usize);
frame.render_widget(
Paragraph::new(sep).style(Style::default().fg(theme.text_muted)),
rows[1],
);
if let Some(item) = state
.annotation_menu_items
.get(state.annotation_menu_selected)
{
let detail = Paragraph::new(format!(" {}", item.comment))
.style(Style::default().fg(theme.text))
.wrap(Wrap { trim: false });
frame.render_widget(detail, rows[2]);
}
let hints = Line::from(vec![
Span::styled(
" [j/k]",
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD),
),
Span::styled("navigate ", Style::default().fg(theme.text_muted)),
Span::styled(
"[e]",
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD),
),
Span::styled("edit ", Style::default().fg(theme.text_muted)),
Span::styled(
"[d]",
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD),
),
Span::styled("delete ", Style::default().fg(theme.text_muted)),
Span::styled(
"[Esc]",
Style::default()
.fg(theme.accent)
.add_modifier(Modifier::BOLD),
),
Span::styled("close", Style::default().fg(theme.text_muted)),
]);
frame.render_widget(Paragraph::new(hints), rows[3]);
}