entropy-tui 0.1.0

a text editor where the letters deletes itself
use crate::app::App;
use ratatui::{
    Frame,
    layout::{Alignment, Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Clear, Paragraph},
};

fn danger_color(level: u8) -> Color {
    match level {
        0 => Color::White,
        1 => Color::Yellow,
        2 => Color::LightRed,
        _ => Color::Red,
    }
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - percent_y) / 2),
            Constraint::Percentage(percent_y),
            Constraint::Percentage((100 - percent_y) / 2),
        ])
        .split(r);

    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - percent_x) / 2),
            Constraint::Percentage(percent_x),
            Constraint::Percentage((100 - percent_x) / 2),
        ])
        .split(popup_layout[1])[1]
}

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

    let color = danger_color(app.danger_level);
    let display_text = build_display(app);

    let title = match &app.file_path {
        Some(p) => format!(
            " entropy — {} ",
            p.file_name().and_then(|n| n.to_str()).unwrap_or("unknown")
        ),
        None => " entropy ~ type faster than the void ".to_string(),
    };

    let editor = Paragraph::new(display_text.as_str()).block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(color))
            .title(Span::styled(title, Style::default().fg(color))),
    );

    f.render_widget(editor, chunks[0]);

    let status_spans = build_status(app, color);
    let status = Paragraph::new(Line::from(status_spans));
    f.render_widget(status, chunks[1]);

    if app.game_over {
        draw_game_over(f, app);
    }
}

fn build_display(app: &App) -> String {
    let mut chars: Vec<char> = app.buffer.chars().collect();
    let len = chars.len();

    if let Some(fidx) = app.flicker_pos {
        if fidx < len && fidx != app.cursor_pos {
            chars[fidx] = app.flicker_char;
        }
    }

    if app.cursor_visible {
        let pos = app.cursor_pos.min(len);
        if pos < len {
            chars[pos] = '';
        } else {
            chars.push('');
        }
    }

    chars.iter().collect()
}

fn build_status(app: &App, color: Color) -> Vec<Span<'static>> {
    if let Some(msg) = app.save_msg {
        return vec![Span::styled(
            msg,
            Style::default()
                .fg(Color::Green)
                .add_modifier(Modifier::BOLD),
        )];
    }

    if let Some(msg) = app.warning_msg {
        return vec![Span::styled(
            format!("{} ", msg),
            Style::default()
                .fg(Color::Red)
                .add_modifier(Modifier::BOLD)
                .add_modifier(Modifier::RAPID_BLINK),
        )];
    }

    let save_hint = if app.file_path.is_some() {
        "  |  ctrl+s to save"
    } else {
        ""
    };

    vec![
        Span::styled(" chars: ", Style::default().fg(Color::DarkGray)),
        Span::styled(
            app.buffer.chars().count().to_string(),
            Style::default()
                .fg(Color::White)
                .add_modifier(Modifier::BOLD),
        ),
        Span::styled("  |  deleted: ", Style::default().fg(Color::DarkGray)),
        Span::styled(
            app.chars_deleted.to_string(),
            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
        ),
        Span::styled("  |  score: ", Style::default().fg(Color::DarkGray)),
        Span::styled(
            app.score.to_string(),
            Style::default()
                .fg(Color::Green)
                .add_modifier(Modifier::BOLD),
        ),
        Span::styled("  |  ", Style::default().fg(Color::DarkGray)),
        Span::styled(
            danger_bar(app.danger_level),
            Style::default().fg(color).add_modifier(Modifier::BOLD),
        ),
        Span::styled(save_hint, Style::default().fg(Color::DarkGray)),
        Span::styled("  |  ctrl+q to quit", Style::default().fg(Color::DarkGray)),
    ]
}

fn danger_bar(level: u8) -> String {
    let filled = level as usize;
    let empty = 3usize.saturating_sub(filled);
    let mut s = String::new();
    for _ in 0..filled {
        s.push('');
    }
    for _ in 0..empty {
        s.push('');
    }
    s
}

fn draw_game_over(f: &mut Frame, app: &App) {
    let area = centered_rect(50, 40, f.size());
    f.render_widget(Clear, area);

    let lines = vec![
        Line::from(""),
        Line::from(Span::styled(
            "  ░░ ENTROPY WINS ░░",
            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
        )),
        Line::from(""),
        Line::from(vec![
            Span::styled("  chars typed:   ", Style::default().fg(Color::DarkGray)),
            Span::styled(
                app.score.to_string(),
                Style::default()
                    .fg(Color::Green)
                    .add_modifier(Modifier::BOLD),
            ),
        ]),
        Line::from(vec![
            Span::styled("  chars deleted: ", Style::default().fg(Color::DarkGray)),
            Span::styled(
                app.chars_deleted.to_string(),
                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
            ),
        ]),
        Line::from(""),
        Line::from(Span::styled(
            "  press any key to exit",
            Style::default().fg(Color::DarkGray),
        )),
    ];

    let popup = Paragraph::new(lines).block(
        Block::default()
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::Red))
            .title(" game over ")
            .title_alignment(Alignment::Center),
    );

    f.render_widget(popup, area);
}