clauth 0.3.2

Simple Claude Code account switcher and usage monitor
//! Fallback chain editor screen.

use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::symbols::border;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph};

use super::super::app::{App, ChainItemKind, chain_items};
use super::super::theme;
use crate::fallback::{DEFAULT_THRESHOLD, threshold_for};

pub(super) fn draw(frame: &mut Frame<'_>, area: Rect, app: &App) {
    let items = chain_items(app);

    let block = Block::default()
        .borders(Borders::ALL)
        .border_set(border::ROUNDED)
        .border_style(Style::default().fg(theme::LINE))
        .title(Line::from(Span::styled(" FALLBACK CHAIN ", theme::label())))
        .padding(Padding::new(2, 2, 1, 1));
    let inner = block.inner(area);
    frame.render_widget(block, area);

    if app.config().state.fallback_chain.is_empty() {
        let hint = Paragraph::new(vec![
            Line::from(Span::styled("Chain is empty.", theme::muted())),
            Line::from(""),
            Line::from(Span::styled(
                "Add a profile to enable auto-switch when its 5h window crosses",
                theme::dim(),
            )),
            Line::from(Span::styled(
                "the threshold. clauth will rotate to the next chain member.",
                theme::dim(),
            )),
        ])
        .style(theme::base());
        let parts = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Length(5), Constraint::Min(1)])
            .split(inner);
        frame.render_widget(hint, parts[0]);

        let action_items = items
            .iter()
            .map(|item| ListItem::new(render_chain_row(app, *item)))
            .collect::<Vec<_>>();
        let list = List::new(action_items)
            .style(theme::base())
            .highlight_style(theme::selected_row().add_modifier(Modifier::BOLD))
            .highlight_symbol("");
        let mut state = ratatui::widgets::ListState::default();
        state.select(Some(app.chain_cursor.min(items.len().saturating_sub(1))));
        frame.render_stateful_widget(list, parts[1], &mut state);
        return;
    }

    let lines: Vec<ListItem<'_>> = items
        .iter()
        .map(|item| ListItem::new(render_chain_row(app, *item)))
        .collect();
    let list = List::new(lines)
        .style(theme::base())
        .highlight_style(theme::selected_row().add_modifier(Modifier::BOLD))
        .highlight_symbol("");
    let mut state = ratatui::widgets::ListState::default();
    state.select(Some(app.chain_cursor.min(items.len().saturating_sub(1))));
    frame.render_stateful_widget(list, inner, &mut state);
}

fn render_chain_row(app: &App, item: ChainItemKind) -> Line<'static> {
    match item {
        ChainItemKind::Member(i) => {
            let cfg = app.config();
            let Some(name) = cfg.state.fallback_chain.get(i).cloned() else {
                return Line::from("");
            };
            let profile = cfg.find(&name);
            let threshold = profile.map(threshold_for).unwrap_or(DEFAULT_THRESHOLD);
            let utilization = profile
                .and_then(|p| p.usage.as_ref())
                .and_then(|u| u.five_hour.as_ref())
                .map(|w| w.utilization);

            let active = cfg.is_active(&name);
            let active_mark = if active {
                Span::styled("", theme::accent())
            } else {
                Span::raw("  ")
            };
            let position = Span::styled(format!("{:>2}.  ", i + 1), theme::faint());
            let name_span = Span::styled(name, Style::default().fg(theme::TEXT).bold());
            let threshold_span = Span::styled(format!("  @ {threshold:.0}%"), theme::faint());
            let util_span = match utilization {
                Some(pct) => {
                    let color = if pct >= threshold {
                        theme::DANGER
                    } else if pct >= threshold * 0.8 {
                        theme::ACCENT_2
                    } else {
                        theme::TEXT_DIM
                    };
                    Span::styled(format!("  5h {pct:.0}%"), Style::default().fg(color))
                }
                None => Span::styled("  5h —", theme::faint()),
            };
            Line::from(vec![
                position,
                active_mark,
                name_span,
                threshold_span,
                util_span,
            ])
        }
        ChainItemKind::Add => Line::from(vec![
            Span::styled("    + ", theme::orange()),
            Span::styled("Add profile to chain", theme::muted()),
        ]),
        ChainItemKind::Back => Line::from(vec![
            Span::raw("    "),
            Span::styled("← Back", theme::faint()),
        ]),
    }
}