gitorii 0.3.0

A human-first Git client with simplified commands, snapshots, multi-platform mirrors and built-in secret scanning
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
};

use crate::tui::app::{App, BorderStyle};
use super::super::ui::{C_WHITE, C_SUBTLE, C_DIM, C_GREEN, C_YELLOW};

struct SettingItem {
    label: &'static str,
    section: &'static str,
}

const ITEMS: &[SettingItem] = &[
    SettingItem { label: "border style",      section: "appearance" },
    SettingItem { label: "brand color",        section: "appearance" },
    SettingItem { label: "selected bg",        section: "appearance" },
    SettingItem { label: "show history",       section: "views" },
    SettingItem { label: "show remote",        section: "views" },
    SettingItem { label: "show mirror",        section: "views" },
    SettingItem { label: "show workspace",     section: "views" },
    SettingItem { label: "show help",          section: "views" },
    SettingItem { label: "key: files",         section: "keybinds" },
    SettingItem { label: "key: save",          section: "keybinds" },
    SettingItem { label: "key: sync",          section: "keybinds" },
    SettingItem { label: "key: snapshot",      section: "keybinds" },
    SettingItem { label: "key: log",           section: "keybinds" },
    SettingItem { label: "key: branch",        section: "keybinds" },
    SettingItem { label: "key: tags",          section: "keybinds" },
    SettingItem { label: "key: history",       section: "keybinds" },
    SettingItem { label: "key: remote",        section: "keybinds" },
    SettingItem { label: "key: mirror",        section: "keybinds" },
    SettingItem { label: "key: workspace",     section: "keybinds" },
    SettingItem { label: "key: config",        section: "keybinds" },
];

pub fn render(f: &mut Frame, app: &App, area: Rect) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(1), Constraint::Length(3)])
        .split(area);

    let cols = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(18), Constraint::Min(1)])
        .split(chunks[0]);

    render_sections(f, app, cols[0]);
    render_items(f, app, cols[1]);
    render_status(f, app, chunks[1]);
}

fn render_sections(f: &mut Frame, app: &App, area: Rect) {
    let current_section = ITEMS.get(app.settings_view.idx)
        .map(|i| i.section)
        .unwrap_or("");

    let bc = app.brand_color();
    let sections = ["appearance", "views", "keybinds"];
    let items: Vec<ListItem> = sections.iter().map(|s| {
        let is_active = *s == current_section;
        let prefix = if is_active { "" } else { "  " };
        let style = if is_active {
            Style::default().bg(app.selected_bg()).add_modifier(Modifier::BOLD)
        } else {
            Style::default()
        };
        ListItem::new(Line::from(vec![
            Span::styled(prefix, Style::default().fg(bc)),
            Span::styled(*s, Style::default().fg(if is_active { C_WHITE } else { C_SUBTLE })),
        ])).style(style)
    }).collect();

    let block = Block::default()
        .title(Span::styled(" sections ", Style::default().fg(bc)))
        .borders(Borders::ALL).border_type(app.border_type())
        .border_style(Style::default().fg(bc));
    f.render_widget(List::new(items).block(block), area);
}

fn render_items(f: &mut Frame, app: &App, area: Rect) {
    let s = &app.settings;
    let editing_idx = app.settings_view.editing_keybind;
    let bc = app.brand_color();

    let items: Vec<ListItem> = ITEMS.iter().enumerate().map(|(i, item)| {
        let is_sel = i == app.settings_view.idx;
        let is_editing = editing_idx == Some(i);
        let style = if is_sel {
            Style::default().bg(app.selected_bg()).add_modifier(Modifier::BOLD)
        } else {
            Style::default()
        };
        let prefix = if is_sel { "" } else { "  " };

        let value_span = if is_editing {
            Span::styled("press new key…", Style::default().fg(C_YELLOW).add_modifier(Modifier::BOLD))
        } else {
            let val = setting_value(i, s);
            let color = setting_color(i, s);
            Span::styled(val, Style::default().fg(color))
        };

        ListItem::new(Line::from(vec![
            Span::styled(prefix, Style::default().fg(bc)),
            Span::styled(format!("{:<20}", item.label), Style::default().fg(if is_sel { C_WHITE } else { C_SUBTLE })),
            value_span,
        ])).style(style)
    }).collect();

    let mut state = ListState::default();
    state.select(Some(app.settings_view.idx));

    let block = Block::default()
        .title(Span::styled(" settings ", Style::default().fg(bc)))
        .borders(Borders::ALL).border_type(app.border_type())
        .border_style(Style::default().fg(bc));
    f.render_stateful_widget(List::new(items).block(block), area, &mut state);
}

fn render_status(f: &mut Frame, app: &App, area: Rect) {
    let bc = app.brand_color();
    let (text, color) = if app.settings_view.editing_keybind.is_some() {
        ("press any key to assign  [Esc] cancel".to_string(), C_YELLOW)
    } else {
        match &app.settings_view.status {
            Some(msg) => (msg.clone(), C_GREEN),
            None => ("[Enter] toggle/edit  [s] save settings".to_string(), C_DIM),
        }
    };

    let block = Block::default()
        .title(Span::styled(" status ", Style::default().fg(bc)))
        .borders(Borders::ALL).border_type(app.border_type())
        .border_style(Style::default().fg(bc));
    f.render_widget(
        Paragraph::new(Line::from(vec![
            Span::raw(" "),
            Span::styled(text, Style::default().fg(color)),
        ])).block(block),
        area,
    );
}

fn setting_value(idx: usize, s: &crate::tui::app::TuiSettings) -> String {
    match idx {
        0  => if s.border_style == BorderStyle::Rounded { "rounded  ╭╮╯╰".to_string() } else { "sharp  ┌┐┘└".to_string() },
        1  => format!("rgb({},{},{})", s.brand_color.0, s.brand_color.1, s.brand_color.2),
        2  => format!("rgb({},{},{})", s.selected_bg.0, s.selected_bg.1, s.selected_bg.2),
        3  => if s.show_history_view   { "visible".to_string() } else { "hidden".to_string() },
        4  => if s.show_remote_view    { "visible".to_string() } else { "hidden".to_string() },
        5  => if s.show_mirror_view    { "visible".to_string() } else { "hidden".to_string() },
        6  => if s.show_workspace_view { "visible".to_string() } else { "hidden".to_string() },
        7  => if s.show_help_view      { "visible".to_string() } else { "hidden".to_string() },
        8  => s.keybind_files.to_string(),
        9  => s.keybind_save.to_string(),
        10 => s.keybind_sync.to_string(),
        11 => s.keybind_snapshot.to_string(),
        12 => s.keybind_log.to_string(),
        13 => s.keybind_branch.to_string(),
        14 => s.keybind_tag.to_string(),
        15 => s.keybind_history.to_string(),
        16 => s.keybind_remote.to_string(),
        17 => s.keybind_mirror.to_string(),
        18 => s.keybind_workspace.to_string(),
        19 => s.keybind_config.to_string(),
        _  => String::new(),
    }
}

fn setting_color(idx: usize, s: &crate::tui::app::TuiSettings) -> Color {
    match idx {
        0  => C_WHITE,
        1  => Color::Rgb(s.brand_color.0, s.brand_color.1, s.brand_color.2),
        2  => C_WHITE,
        3..=7 => {
            let visible = match idx {
                3 => s.show_history_view,
                4 => s.show_remote_view,
                5 => s.show_mirror_view,
                6 => s.show_workspace_view,
                7 => s.show_help_view,
                _ => true,
            };
            if visible { C_GREEN } else { C_DIM }
        }
        _ => C_YELLOW,
    }
}