eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Conflict resolution renderers.

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);

    // Conflict list
    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]);

    // Instructions
    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(""),
    ];
    
    // Smart suggestion
    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
}