bath 0.4.1

A TUI tool to manage and export environment variable profiles
use crate::config::ItemKind;
use crate::tui::select;
use crate::tui::state::AppState;
use crate::tui::view::View;
use ratatui::{
    layout::Rect,
    widgets::{Block, Borders, List, ListItem, Paragraph},
};

pub fn draw<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    match app.active_view {
        View::Profiles => draw_profiles(f, area, app),
        View::Vars => draw_vars(f, area, app),
        View::Parts => draw_parts(f, area, app),
        View::Items => draw_items(f, area, app),
        View::Defs => draw_defs(f, area, app),
        View::Preview => draw_preview(f, area, app),
        View::Export => draw_export(f, area, app),
        View::Help => draw_help(f, area, app),
    }
}

fn draw_profiles<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let indices = select::visible_profile_indices(app);
    let items: Vec<ListItem> = indices
        .iter()
        .map(|i| {
            let p = &app.profiles[*i];
            let active = *i == app.active_profile_index;
            let name = if active {
                format!("* {}", p.name)
            } else {
                format!("  {}", p.name)
            };
            ListItem::new(name)
        })
        .collect();

    select::clamp_list_state(&mut app.profile_list_state, items.len());
    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title("Profiles"),
        )
        .style(app.theme.text())
        .highlight_style(app.theme.list_highlight())
        .highlight_symbol("» ");
    f.render_stateful_widget(list, area, &mut app.profile_list_state);
}

fn draw_vars<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let rows = select::compute_var_rows(app);
    let items: Vec<ListItem> = rows
        .iter()
        .map(|r| {
            let badge = match r.kind {
                crate::config::VarKind::Scalar => "S",
                crate::config::VarKind::List => "L",
            };
            ListItem::new(format!(
                "{}  {:<18}  {:>3}  sep='{}'",
                badge, r.name, r.count, r.separator
            ))
        })
        .collect();

    select::clamp_list_state(&mut app.vars_list_state, items.len());
    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title("Vars"),
        )
        .style(app.theme.text())
        .highlight_style(app.theme.list_highlight())
        .highlight_symbol("» ");
    f.render_stateful_widget(list, area, &mut app.vars_list_state);
}

fn draw_parts<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let var = app
        .selected_var_name()
        .unwrap_or_else(|| "PATH".to_string());
    let parts = select::current_var_parts(app, &var);
    let indices = select::visible_part_indices(app, &parts);
    // Mark the part currently held for a deferred move (it stays in place
    // until dropped). The row is found by equality with the held entry — the
    // same relocation-by-value approach place_held_part uses — so reordering
    // or deleting parts while holding never dims the wrong row: prefer the
    // pick-time index while it still holds the same entry, fall back to the
    // first equal entry, else show no marker. Display-only; placement logic
    // is unaffected.
    let held_pos = match &app.holding {
        Some(crate::tui::state::Holding::Part {
            profile,
            var: held_var,
            from,
            entry,
        }) if *held_var == var
            && app
                .profiles
                .get(app.active_profile_index)
                .map(|p| p.name.as_str())
                == Some(profile.as_str()) =>
        {
            parts
                .get(*from)
                .filter(|e| *e == entry)
                .map(|_| *from)
                .or_else(|| parts.iter().position(|e| e == entry))
        }
        _ => None,
    };
    let items: Vec<ListItem> = indices
        .iter()
        .map(|i| {
            if held_pos == Some(*i) {
                ListItem::new(format!("{} [held]", parts[*i])).style(app.theme.dim_text())
            } else {
                ListItem::new(parts[*i].to_string())
            }
        })
        .collect();

    select::clamp_list_state(&mut app.parts_list_state, items.len());
    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title(format!("Parts for {var}")),
        )
        .style(app.theme.text())
        .highlight_style(app.theme.list_highlight())
        .highlight_symbol("» ");
    f.render_stateful_widget(list, area, &mut app.parts_list_state);
}

fn draw_items<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let indices = select::visible_item_indices(app);
    let items: Vec<ListItem> = indices
        .iter()
        .map(|i| {
            let it = &app.items[*i];
            let k = match it.kind {
                ItemKind::Text => "TXT",
                ItemKind::Path => "PATH",
            };
            let tags = if it.tags.is_empty() {
                String::new()
            } else {
                format!("  [{}]", it.tags.join(","))
            };
            ListItem::new(format!("{:<4} {}{}", k, it.value, tags))
        })
        .collect();

    select::clamp_list_state(&mut app.items_list_state, items.len());
    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title("Items"),
        )
        .style(app.theme.text())
        .highlight_style(app.theme.list_highlight())
        .highlight_symbol("» ");
    f.render_stateful_widget(list, area, &mut app.items_list_state);
}

fn draw_defs<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let mut defs = app.var_options.clone();
    defs.sort_by(|a, b| a.name.cmp(&b.name));
    if !app.defs_filter.is_empty() {
        let q = app.defs_filter.to_lowercase();
        defs.retain(|d| d.name.to_lowercase().contains(&q));
    }
    let items: Vec<ListItem> = defs
        .iter()
        .map(|d| {
            let kind = match d.kind {
                crate::config::VarKind::Scalar => "Scalar",
                crate::config::VarKind::List => "List",
            };
            ListItem::new(format!(
                "{:<18}  {:<6}  sep='{}'",
                d.name, kind, d.separator
            ))
        })
        .collect();

    select::clamp_list_state(&mut app.defs_list_state, items.len());
    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title("Var defs"),
        )
        .style(app.theme.text())
        .highlight_style(app.theme.list_highlight())
        .highlight_symbol("» ");
    f.render_stateful_widget(list, area, &mut app.defs_list_state);
}

fn draw_preview<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let var = app
        .selected_var_name()
        .unwrap_or_else(|| "PATH".to_string());
    let parts = select::current_var_parts(app, &var);
    let def_sep = app
        .var_options
        .iter()
        .find(|o| o.name == var)
        .map(|o| o.separator.clone())
        .unwrap_or_else(|| ":".to_string());
    // Shared with export so the preview always predicts what export emits.
    let sep = crate::export::join_separator(&parts, &def_sep);
    let joined = parts
        .iter()
        .map(select::preview_value)
        .collect::<Vec<_>>()
        .join(&sep);
    let text = format!("{var} = {joined}\n\n(parts: {})", parts.len());
    let p = Paragraph::new(text).style(app.theme.text()).block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(app.theme.border())
            .title("Preview"),
    );
    f.render_widget(p, area);
}

fn draw_export<B: ratatui::backend::Backend>(
    f: &mut ratatui::Frame<B>,
    area: Rect,
    app: &mut AppState,
) {
    let var = app
        .selected_var_name()
        .unwrap_or_else(|| "PATH".to_string());
    let profile = &app.profiles[app.active_profile_index];
    let full = crate::export::generate_full_export(profile, crate::export::OperationMode::Prepend);
    let line = full
        .lines()
        .find(|l| l.starts_with(&format!("export {var}=")))
        .unwrap_or("");
    let p = Paragraph::new(line.to_string())
        .style(app.theme.text())
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(app.theme.border())
                .title("Export"),
        );
    f.render_widget(p, area);
}

fn draw_help<B: ratatui::backend::Backend>(f: &mut ratatui::Frame<B>, area: Rect, app: &AppState) {
    let mut theme_names = crate::tui::daisyui_themes::names();
    theme_names.sort();
    let themes_block = theme_names
        .chunks(6)
        .map(|chunk| chunk.join("  "))
        .collect::<Vec<_>>()
        .join("\n");

    let text = format!(
        r#"Navigation
  :  command palette (jump to views, actions)
  /  filter current view
  Tab  cycle views
  q  quit

Views
  profiles  vars  parts  items  defs  preview  export  help

Theme
  current: {current}
  preset:  {preset}
  :themes (count: {count})
  :theme <name>

Available DaisyUI themes:
{themes}

Notes
  Items can be picked with m and dropped with p (in Items/Vars/Parts depending on context).
"#,
        current = app.theme.name,
        preset = app.theme_preset,
        count = crate::tui::daisyui_themes::THEMES.len(),
        themes = themes_block
    );
    let p = Paragraph::new(text).style(app.theme.text()).block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(app.theme.border())
            .title("Help"),
    );
    f.render_widget(p, area);
}