cashflow 0.1.1

A terminal-based expense tracker built with Ratatui
use chrono::{Datelike, Local};
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Bar, BarChart, BarGroup, Block, Borders, Paragraph, Sparkline},
    Frame,
};

use crate::app::App;

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

    render_summary_cards(f, app, chunks[0]);
    render_category_chart(f, app, chunks[1]);
    render_sparkline(f, app, chunks[2]);
}

fn render_summary_cards(f: &mut Frame, app: &App, area: Rect) {
    let now = Local::now();
    let month_total = app.total_for_month(now.year(), now.month());
    let year_total = app.total_for_year(now.year());
    let count = app.expenses.len();

    let cols = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage(33),
            Constraint::Percentage(34),
            Constraint::Percentage(33),
        ])
        .split(area);

    let card_style = Style::default().fg(Color::White);

    let month_card = Paragraph::new(vec![
        Line::from(Span::styled(
            "This Month",
            Style::default().add_modifier(Modifier::DIM),
        )),
        Line::from(""),
        Line::from(Span::styled(
            app.fmt(month_total),
            Style::default()
                .fg(Color::Green)
                .add_modifier(Modifier::BOLD),
        )),
    ])
    .style(card_style)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::DarkGray)),
    );

    let year_card = Paragraph::new(vec![
        Line::from(Span::styled(
            "This Year",
            Style::default().add_modifier(Modifier::DIM),
        )),
        Line::from(""),
        Line::from(Span::styled(
            app.fmt(year_total),
            Style::default()
                .fg(Color::Yellow)
                .add_modifier(Modifier::BOLD),
        )),
    ])
    .style(card_style)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::DarkGray)),
    );

    let count_card = Paragraph::new(vec![
        Line::from(Span::styled(
            "Total Expenses",
            Style::default().add_modifier(Modifier::DIM),
        )),
        Line::from(""),
        Line::from(Span::styled(
            format!("{}", count),
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD),
        )),
    ])
    .style(card_style)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::DarkGray)),
    );

    f.render_widget(month_card, cols[0]);
    f.render_widget(year_card, cols[1]);
    f.render_widget(count_card, cols[2]);
}

fn render_category_chart(f: &mut Frame, app: &App, area: Rect) {
    let now = Local::now();
    let data = app.spending_by_category(now.year(), now.month());

    let colors = [
        Color::Green,
        Color::Yellow,
        Color::Blue,
        Color::Red,
        Color::Magenta,
        Color::Cyan,
        Color::LightGreen,
        Color::LightYellow,
        Color::LightBlue,
        Color::LightRed,
    ];

    let bars: Vec<Bar> = data
        .iter()
        .enumerate()
        .map(|(i, (cat, amount))| {
            let label = if cat.len() > 10 {
                format!("{}...", &cat[..8])
            } else {
                cat.clone()
            };
            Bar::default()
                .value(*amount as u64)
                .label(Line::from(label))
                .style(Style::default().fg(colors[i % colors.len()]))
                .value_style(
                    Style::default()
                        .fg(Color::White)
                        .add_modifier(Modifier::BOLD),
                )
        })
        .collect();

    let chart = BarChart::default()
        .block(
            Block::default()
                .title(" Spending by Category (This Month) ")
                .borders(Borders::ALL)
                .border_style(Style::default().fg(Color::DarkGray)),
        )
        .data(BarGroup::default().bars(&bars))
        .bar_width(
            if data.is_empty() {
                5
            } else {
                let available = area.width.saturating_sub(2);
                let per = available / data.len().max(1) as u16;
                per.max(3).min(12)
            },
        )
        .bar_gap(1);

    f.render_widget(chart, area);
}

fn render_sparkline(f: &mut Frame, app: &App, area: Rect) {
    let data = app.daily_spending_last_30_days();

    let sparkline = Sparkline::default()
        .block(
            Block::default()
                .title(" Daily Spending (Last 30 Days) ")
                .borders(Borders::ALL)
                .border_style(Style::default().fg(Color::DarkGray)),
        )
        .data(&data)
        .style(Style::default().fg(Color::Cyan));

    f.render_widget(sparkline, area);
}