bath 0.4.0

A TUI tool to manage and export environment variable profiles
pub mod detail;
pub mod header;
pub mod main_list;

use crate::tui::commands;
use crate::tui::state::{AppState, InputMode};
use ratatui::{
    backend::Backend,
    layout::{Constraint, Direction, Layout, Rect},
    widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph},
};

pub fn draw_main_ui<B: Backend>(f: &mut ratatui::Frame<B>, app: &mut AppState) {
    let size = f.size();
    // Paint full background so terminal default doesn't bleed through.
    f.render_widget(Block::default().style(app.theme.background()), size);

    // Three header text lines plus the bottom border row.
    let header_h = 4u16.min(size.height);
    let mut detail_h = (size.height / 3).max(7);
    detail_h = detail_h.min(size.height.saturating_sub(header_h));

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(header_h),
            Constraint::Min(0),
            Constraint::Length(detail_h),
        ])
        .split(size);

    header::draw(f, chunks[0], app);
    main_list::draw(f, chunks[1], app);
    detail::draw(f, chunks[2], app);

    draw_overlays(f, size, app);
}

fn draw_overlays<B: Backend>(f: &mut ratatui::Frame<B>, size: Rect, app: &mut AppState) {
    if matches!(app.input_mode, InputMode::Command) {
        let total = app.command_suggestions.len();
        let rows = total.min(commands::MAX_VISIBLE_SUGGESTIONS) as u16;
        // Bordered prompt (3) + suggestion rows + list borders (2).
        let overlay_h = (rows + 5).min(size.height);
        let overlay = Rect {
            x: size.x,
            y: size.y + size.height.saturating_sub(overlay_h),
            width: size.width,
            height: overlay_h,
        };

        f.render_widget(Clear, overlay);

        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
            .split(overlay);

        let prompt = Paragraph::new(format!(":{}", app.command_input))
            .style(app.theme.text())
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(app.theme.border())
                    .title("Command"),
            );
        f.render_widget(prompt, chunks[0]);

        // Derive the visible row count from the area the list actually gets
        // (minus its borders), so the scroll window always matches what is
        // drawn and the highlighted entry is the one Enter executes.
        let visible = chunks[1].height.saturating_sub(2) as usize;
        let (start, highlight_row) =
            commands::suggestion_window(app.command_selected, total, visible);

        let items: Vec<ListItem> = app
            .command_suggestions
            .iter()
            .skip(start)
            .take(visible)
            .map(|s| ListItem::new(s.clone()))
            .collect();

        let mut state = ListState::default();
        if !items.is_empty() {
            state.select(Some(highlight_row));
        }

        let list = List::new(items)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(app.theme.border())
                    .title("Suggestions (Tab to complete)"),
            )
            .style(app.theme.text())
            .highlight_style(app.theme.list_highlight())
            .highlight_symbol("ยป ");

        f.render_stateful_widget(list, chunks[1], &mut state);
    } else if matches!(app.input_mode, InputMode::Search) {
        let overlay_h = 3u16.min(size.height);
        let overlay = Rect {
            x: size.x,
            y: size.y + size.height.saturating_sub(overlay_h),
            width: size.width,
            height: overlay_h,
        };
        f.render_widget(Clear, overlay);

        let prompt = Paragraph::new(format!("/{}", app.command_input))
            .style(app.theme.text())
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(app.theme.border())
                    .title("Filter (Enter: apply, Esc: cancel)"),
            );
        f.render_widget(prompt, overlay);
    }
}