http-diff 0.0.5

http-diff - CLI tool to verify consistency across web server versions. Ideal for large-scale refactors, sanity tests and maintaining data integrity across versions.
use crate::http_diff::config::APP_VERSION;
use crate::http_diff::types::JobStatus;
use crate::{app_state::AppState, http_diff::config::APP_NAME};
use ratatui::{prelude::*, widgets::*};

pub const LOGO: [&str; 6] = [
    "",
    "█▄█ ▀█▀ ▀█▀ █▀▄",
    "█ █  █   █  █▀ ",
    "",
    "█▀▄  █  █▀  █▀",
    "█▄▀  █  █▀  █▀",
];

pub fn render_top_block(frame: &mut Frame, area: Rect, app: &mut AppState) {
    let chunks = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(70), Constraint::Percentage(30)])
        .split(area);

    render_welcome_block(frame, chunks[0], app);
    render_status_bars(frame, chunks[1], app);
}

fn render_status_bars(frame: &mut Frame, area: Rect, app: &mut AppState) {
    let bar_chart_block = Block::default()
        .title("status")
        .borders(Borders::ALL)
        .title_style(Style::default().fg(app.theme.white))
        .border_style(Style::default().fg(app.theme.white))
        .padding(Padding::horizontal(1));

    let label_style = Style::default().fg(app.theme.white);

    let bar_group = create_status_bar_groups(app);

    let group_gap = 8;
    let bar_width = ((area.width - 2) - (group_gap / 2) - 1) / 4;

    let barchart = BarChart::default()
        .block(bar_chart_block)
        .bar_width(bar_width)
        .data(bar_group)
        .group_gap(group_gap)
        .label_style(label_style);

    frame.render_widget(barchart, area);
}

fn render_welcome_block(frame: &mut Frame, area: Rect, app: &AppState) {
    let chunks = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(17), Constraint::Percentage(100)])
        .split(area);

    render_logo(frame, chunks[0], app);
    render_welcome_text(frame, chunks[1], app);
}

fn render_logo(frame: &mut Frame, area: Rect, app: &AppState) {
    let area = area.inner(&Margin { vertical: 0, horizontal: 1 });

    let mut text: Vec<Line> = LOGO
        .iter()
        .map(|text| Line::from((*text).fg(app.theme.white)))
        .collect();

    let highlighted_row_color = if app.has_failed_jobs() {
        app.theme.warning
    } else {
        app.theme.success
    };

    text.get_mut(app.highlight_logo_row_index)
        .unwrap()
        .patch_style(Style::default().fg(highlighted_row_color));

    text.push(Line::from(
        format!("v{}", APP_VERSION).italic().fg(app.theme.white),
    ));

    let paragraph = Paragraph::new(text);

    frame.render_widget(paragraph, area);
}

fn render_welcome_text(frame: &mut Frame, area: Rect, app: &AppState) {
    let paragraph = keys_explanation_paragraph(app);

    let block = Block::new()
        .borders(Borders::ALL)
        .title(APP_NAME)
        .title_style(Style::default().fg(app.theme.white))
        .border_style(Style::default().fg(app.theme.white))
        .padding(Padding::new(1, 1, 1, 0));

    frame.render_widget(paragraph.clone().block(block), area);
}

fn create_status_bar_groups(app: &mut AppState) -> BarGroup {
    let mut data = vec![
        ("Pending", 0, Style::default().fg(app.theme.gray)),
        ("Running", 0, Style::default().fg(app.theme.warning)),
        ("Failed", 0, Style::default().fg(app.theme.error)),
        ("Success", 0, Style::default().fg(app.theme.success)),
    ];

    for job in &app.jobs {
        match job.status {
            JobStatus::Pending => data[0].1 += 1,
            JobStatus::Running => data[1].1 += 1,
            JobStatus::Failed => data[2].1 += 1,
            JobStatus::Finished => data[3].1 += 1,
        }
    }

    let bars: Vec<Bar> = data
        .iter()
        .map(|c| {
            Bar::default()
                .value(c.1)
                .style(c.2)
                .label(c.0.into())
                .text_value(c.1.to_string())
                .value_style(
                    Style::default()
                        .bg(c.2.fg.unwrap())
                        .fg(app.theme.background),
                )
        })
        .collect();

    BarGroup::default().bars(&bars)
}

fn keys_explanation_paragraph(app: &AppState) -> Paragraph<'static> {
    let text = vec![
        Line::from("Welcome,".italic().bold().fg(app.theme.white)),
        Line::from(
            "press `h` to show help screen".italic().fg(app.theme.white),
        ),
        Line::from(vec![
            "concurrent requests: ".italic().fg(app.theme.white),
            Span::styled(
                app.concurrency_level.to_string(),
                Style::default()
                    .fg(app.theme.success)
                    .add_modifier(Modifier::BOLD),
            ),
        ]),
    ];

    Paragraph::new(text).wrap(Wrap { trim: true })
}

pub fn print_logo() {
    for line in LOGO.iter() {
        println!("{}", line)
    }

    println!("v{}\n", APP_VERSION)
}