eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use crate::components::manager::ComponentManager;
use crate::app::{AppState, state::AmendField};
use crate::ui::style::{self, Emphasis};
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Paragraph, Clear};
use ratatui::text::{Line, Span};
use crate::components::manager::utils;

pub fn render_commit_input(_manager: &ComponentManager, frame: &mut Frame, area: Rect, state: &AppState) {
    // Calculate popup height based on whether we're showing author fields
    // Layout: Author info (6-7 lines) + Message (min 3 lines) + Hints (2 lines) = ~12-13 lines minimum
    let popup_height = if state.amend_mode && state.last_commit_info.is_some() {
        22 // More space for amend info with last commit details + message + hints
    } else if state.commit_mode {
        21 // Space for author fields in regular commit mode + message + hints
    } else {
        15
    };
    
    let popup = utils::center_rect(80, popup_height, area);
    frame.render_widget(Clear, popup);

    let title = get_commit_title(state);
    
    // Create layout: header with commit info (amend) or author fields (both modes), then commit message input, then hints
    // Always show author fields, even if last_commit_info isn't loaded yet (for amend)
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints(if state.amend_mode {
            vec![
                Constraint::Length(7), // Author info
                Constraint::Min(3),   // Message input (minimum 3 lines for visibility)
                Constraint::Length(2), // Hints area
            ]
        } else {
            vec![
                Constraint::Length(6), // Author fields
                Constraint::Min(3),    // Message input (minimum 3 lines for visibility)
                Constraint::Length(2), // Hints area
            ]
        })
        .split(popup);
    
    // Render Author/Commit Info
    if state.commit_mode && state.amend_mode {
        if let Some(ref last_info) = state.last_commit_info {
            render_last_commit_info(frame, chunks[0], state, last_info);
        } else {
            render_author_info(frame, chunks[0], state, "Amend Author Info");
        }
    } else if state.commit_mode {
        render_author_info(frame, chunks[0], state, "Commit Author Info");
    }
    
    // Render Message Input
    render_message_input(frame, chunks[1], state, title);

    // Render Hints
    if state.commit_mode {
        render_hints(frame, chunks[2], state);
    }
}

fn get_commit_title(state: &AppState) -> &str {
    if state.amend_mode {
        match state.amend_editing_field {
            Some(AmendField::Message) => "Amend: Message (Tab=name, Enter=amend, Esc=cancel)",
            Some(AmendField::AuthorName) => "Amend: Author Name (Tab=email, Enter=amend, Esc=cancel)",
            Some(AmendField::AuthorEmail) => "Amend: Author Email (Tab=message, Enter=amend, Esc=cancel)",
            None => "Amend commit (Tab=switch field, Enter=amend, Esc=cancel)",
        }
    } else {
        match state.amend_editing_field {
            Some(AmendField::Message) => "Commit: Message (Tab=name, Enter=commit, Esc=cancel)",
            Some(AmendField::AuthorName) => "Commit: Author Name (Tab=email, Enter=commit, Esc=cancel)",
            Some(AmendField::AuthorEmail) => "Commit: Author Email (Tab=message, Enter=commit, Esc=cancel)",
            None => "Commit message (Tab=switch field, Enter=commit, Esc=cancel)",
        }
    }
}

fn render_message_input(frame: &mut Frame, area: Rect, state: &AppState, title: &str) {
    let block = style::pane_block(&state.theme, title.to_string(), true);
    // Highlight the input area when editing any field (Message, AuthorName, or AuthorEmail)
    let message_style = if state.amend_editing_field.is_some() || state.commit_mode {
        style::selection(&state.theme)
    } else {
        style::body_style(&state.theme)
    };
    
    // Show the correct field content based on what's being edited
    let message_text = match state.amend_editing_field {
        Some(AmendField::Message) | None => {
            format!("{}{}", state.commit_input, "")
        }
        Some(AmendField::AuthorName) => {
            format!("{}{}", state.amend_author_name, "")
        }
        Some(AmendField::AuthorEmail) => {
            format!("{}{}", state.amend_author_email, "")
        }
    };
    
    let paragraph = Paragraph::new(message_text)
        .style(message_style)
        .block(block);

    frame.render_widget(paragraph, area);
}

fn render_hints(frame: &mut Frame, area: Rect, state: &AppState) {
    let mut issues = Vec::new();
    // For amend, message is optional if author is being changed
    let has_author_change = !state.amend_author_name.trim().is_empty() || !state.amend_author_email.trim().is_empty();
    if state.commit_input.trim().is_empty() && (!state.amend_mode || !has_author_change) {
        if state.amend_mode {
            issues.push("Commit message or author change required");
        } else {
            issues.push("Commit message is required");
        }
    }
    
    let mut hint_lines = vec![];
    
    // Show issues if any
    if !issues.is_empty() {
        let issues_text = issues.join("");
        hint_lines.push(Line::from(vec![
            Span::styled("Issues: ", style::text(&state.theme, Emphasis::Muted)),
            Span::styled(issues_text, style::text(&state.theme, Emphasis::Normal)),
        ]));
    }
    
    // Always show navigation hint
    let nav_hint = match state.amend_editing_field {
        Some(AmendField::Message) => "Tab → Author Name | Enter → Commit | Esc → Cancel",
        Some(AmendField::AuthorName) => "Tab → Author Email | Enter → Commit | Esc → Cancel",
        Some(AmendField::AuthorEmail) => "Tab → Message | Enter → Commit | Esc → Cancel",
        None => "Tab → Switch fields | Enter → Commit | Esc → Cancel",
    };
    hint_lines.push(Line::from(vec![
        Span::styled("Navigation: ", style::text(&state.theme, Emphasis::Muted)),
        Span::styled(nav_hint, style::text(&state.theme, Emphasis::Normal)),
    ]));
    
    let hint = Paragraph::new(hint_lines)
        .style(style::body_style(&state.theme));
    frame.render_widget(hint, area);
}

fn render_last_commit_info(frame: &mut Frame, area: Rect, state: &AppState, last_info: &crate::app::state::LastCommitInfo) {
    let info_text = vec![
        Line::from(vec![
            Span::styled("Last commit: ", style::text(&state.theme, Emphasis::Muted)),
            Span::styled(&last_info.short_hash, style::text(&state.theme, Emphasis::Normal)),
        ]),
        Line::from(vec![
            Span::styled("Author: ", style::text(&state.theme, Emphasis::Muted)),
            Span::styled(&last_info.author_name, style::text(&state.theme, Emphasis::Normal)),
            Span::styled(" <", style::text(&state.theme, Emphasis::Muted)),
            Span::styled(&last_info.author_email, style::text(&state.theme, Emphasis::Normal)),
            Span::styled(">", style::text(&state.theme, Emphasis::Muted)),
        ]),
        Line::from(vec![
            Span::styled("Date: ", style::text(&state.theme, Emphasis::Muted)),
            Span::styled(&last_info.date, style::text(&state.theme, Emphasis::Normal)),
        ]),
        Line::from(""),
        build_author_name_line(state),
        build_author_email_line(state),
    ];
    
    let info_block = style::pane_block(&state.theme, "Last Commit Info".to_string(), false);
    let info_paragraph = Paragraph::new(info_text)
        .style(style::body_style(&state.theme))
        .block(info_block);
    frame.render_widget(info_paragraph, area);
}

fn render_author_info(frame: &mut Frame, area: Rect, state: &AppState, title: &str) {
    let info_text = vec![
        Line::from(vec![
            Span::styled(title, style::text(&state.theme, Emphasis::Normal)),
        ]),
        Line::from(""),
        build_author_name_line(state),
        build_author_email_line(state),
    ];
    
    let info_block = style::pane_block(&state.theme, title.to_string(), false);
    let info_paragraph = Paragraph::new(info_text)
        .style(style::body_style(&state.theme))
        .block(info_block);
    frame.render_widget(info_paragraph, area);
}

fn build_author_name_line(state: &AppState) -> Line<'static> {
    let name_style = if state.amend_editing_field == Some(AmendField::AuthorName) {
        style::selection(&state.theme)
    } else {
        style::text(&state.theme, Emphasis::Normal)
    };
    let label_style = if state.amend_editing_field == Some(AmendField::AuthorName) {
        style::text(&state.theme, Emphasis::Normal)
    } else {
        style::text(&state.theme, Emphasis::Muted)
    };
    let name_display = if state.amend_author_name.is_empty() {
        "<name: optional>".to_string()
    } else {
        state.amend_author_name.clone()
    };
    Line::from(vec![
        Span::styled("▶ Author Name: ", label_style),
        Span::styled(name_display, name_style),
        if state.amend_editing_field == Some(AmendField::AuthorName) {
            Span::styled("", style::text(&state.theme, Emphasis::Normal))
        } else {
            Span::raw("")
        },
    ])
}

fn build_author_email_line(state: &AppState) -> Line<'static> {
    let email_style = if state.amend_editing_field == Some(AmendField::AuthorEmail) {
        style::selection(&state.theme)
    } else {
        style::text(&state.theme, Emphasis::Normal)
    };
    let label_style = if state.amend_editing_field == Some(AmendField::AuthorEmail) {
        style::text(&state.theme, Emphasis::Normal)
    } else {
        style::text(&state.theme, Emphasis::Muted)
    };
    let email_display = if state.amend_author_email.is_empty() {
        "<email: optional>".to_string()
    } else {
        state.amend_author_email.clone()
    };
    Line::from(vec![
        Span::styled("▶ Author Email: ", label_style),
        Span::styled(email_display, email_style),
        if state.amend_editing_field == Some(AmendField::AuthorEmail) {
            Span::styled("", style::text(&state.theme, Emphasis::Normal))
        } else {
            Span::raw("")
        },
    ])
}