use crate::components::manager::ComponentManager;
use crate::app::AppState;
use crate::ui::style::{self, Emphasis};
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Paragraph, List, ListItem, Clear};
use ratatui::text::{Line, Span};
use crate::components::manager::utils;
pub fn render_merge_base_picker(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let max_idx = state.merge_base_candidates.len().saturating_sub(1);
let selected = state.merge_base_selected.min(max_idx);
let items: Vec<ListItem> = state
.merge_base_candidates
.iter()
.enumerate()
.map(|(i, name)| {
let style = if i == selected {
style::selection(&state.theme)
} else {
style::body_style(&state.theme)
};
ListItem::new(name.clone()).style(style)
})
.collect();
let title = "Pick merge base (Enter to apply, Esc to cancel)";
let popup = utils::center_rect(40, 60, area);
frame.render_widget(Clear, popup);
let list = List::new(items)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title.to_string(), true));
frame.render_widget(list, popup);
}
pub fn render_conflicts_popup(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let conflicts: Vec<&crate::git::parsers::status::StatusEntry> = state
.status_entries
.iter()
.filter(|e| e.conflict)
.collect();
let max_idx = conflicts.len().saturating_sub(1);
let selected = state.conflicts_popup_selected.min(max_idx);
let items: Vec<ListItem> = if conflicts.is_empty() {
vec![ListItem::new("No conflicts found").style(style::text(&state.theme, Emphasis::Muted))]
} else {
conflicts
.iter()
.enumerate()
.map(|(i, entry)| {
let style_item = if i == selected {
style::selection(&state.theme)
} else {
style::body_style(&state.theme)
};
ListItem::new(entry.path.clone()).style(style_item)
})
.collect()
};
let title = format!(
"Conflicts [{} / {}] (Enter=jump, o=open, Esc=close)",
selected.saturating_add(1),
conflicts.len().max(1)
);
let popup = utils::center_rect(50, 40, area);
frame.render_widget(Clear, popup);
let list = List::new(items)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title, true));
frame.render_widget(list, popup);
}
pub fn render_conflicts_guided(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
let conflicts: Vec<&crate::git::parsers::status::StatusEntry> = state
.status_entries
.iter()
.filter(|e| e.conflict)
.collect();
let max_idx = conflicts.len().saturating_sub(1);
let selected = state.conflicts_guided_selected.min(max_idx);
let popup = utils::center_rect(80, 50, area);
frame.render_widget(Clear, popup);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(20), Constraint::Min(10)])
.split(popup);
let items: Vec<ListItem> = if conflicts.is_empty() {
vec![ListItem::new("No conflicts").style(style::text(&state.theme, Emphasis::Muted))]
} else {
conflicts
.iter()
.enumerate()
.map(|(i, entry)| {
let style_item = if i == selected {
style::selection(&state.theme)
} else {
style::body_style(&state.theme)
};
let indicator = if i == selected { "➤" } else { " " };
let label = format!("{} {} ({}/{})", indicator, entry.path, i + 1, conflicts.len());
ListItem::new(label).style(style_item)
})
.collect()
};
let title = format!(
"Conflicts [{} / {}]",
selected.saturating_add(1),
conflicts.len().max(1)
);
let list = List::new(items)
.style(style::body_style(&state.theme))
.block(style::pane_block(&state.theme, title, true));
frame.render_widget(list, chunks[0]);
let instructions = build_conflict_instructions(&conflicts, selected, state);
let instructions_paragraph = Paragraph::new(instructions)
.style(style::text(&state.theme, Emphasis::Normal))
.block(style::pane_block(&state.theme, "Instructions", false));
frame.render_widget(instructions_paragraph, chunks[1]);
}
fn build_conflict_instructions<'a>(
conflicts: &[&crate::git::parsers::status::StatusEntry],
selected: usize,
state: &'a AppState,
) -> Vec<Line<'a>> {
if conflicts.is_empty() {
return vec![Line::from("No conflicts to resolve.")];
}
let current = selected + 1;
let total = conflicts.len();
let mut lines = vec![
Line::from(vec![
Span::styled("Step-by-Step Resolution Guide", style::text(&state.theme, Emphasis::Header)),
]),
Line::from(""),
Line::from(vec![
Span::styled(format!("Resolving conflict {} of {}:", current, total), style::text(&state.theme, Emphasis::Normal)),
]),
Line::from(""),
];
if let Some(conflict) = conflicts.get(selected) {
if let Some(suggestion) = crate::app::conflict_resolution::ConflictSuggestion::analyze_conflict(
&state.repo_path,
&conflict.path
) {
lines.push(Line::from(vec![
Span::styled(format!("💡 Suggestion: {}", suggestion.suggestion), style::text(&state.theme, Emphasis::Warning)),
]));
lines.push(Line::from(""));
}
}
lines.extend(vec![
Line::from("1. Open the conflict file: [o] Open in editor"),
Line::from("2. Look for conflict markers (<<<<<<<, =======, >>>>>>>)"),
Line::from("3. Choose: keep yours, theirs, both, or write new"),
Line::from("4. Remove all conflict markers"),
Line::from("5. Save and stage: [s] Stage resolved file"),
Line::from("6. [n] Next conflict | [c] Commit when done"),
]);
lines
}