rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use crossterm::event::KeyCode;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    widgets::{Block, Borders, Cell, Paragraph, Row, Table, TableState},
    Frame,
};

use crate::apt::parser::{ChangeKind, PackageChange};

pub struct UpdatePanel {
    pub changes: Vec<PackageChange>,
    pub state: TableState,
}

impl UpdatePanel {
    pub fn new(changes: Vec<PackageChange>) -> Self {
        let mut state = TableState::default();
        if !changes.is_empty() {
            state.select(Some(0));
        }
        Self { changes, state }
    }

    pub fn handle_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Char('j') | KeyCode::Down => self.next(1),
            KeyCode::Char('k') | KeyCode::Up => self.prev(1),
            KeyCode::PageDown => self.next(10),
            KeyCode::PageUp => self.prev(10),
            _ => {}
        }
    }

    fn next(&mut self, step: usize) {
        let len = self.changes.len();
        if len == 0 { return; }
        let i = self.state.selected().map_or(0, |i| (i + step).min(len - 1));
        self.state.select(Some(i));
    }

    fn prev(&mut self, step: usize) {
        let i = self.state.selected().map_or(0, |i| i.saturating_sub(step));
        self.state.select(Some(i));
    }

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

        let visible: Vec<&PackageChange> = self.changes.iter()
            .filter(|c| c.kind != ChangeKind::Configure)
            .collect();

        let rows: Vec<Row> = visible.iter().map(|c| {
            let (action, style) = match c.kind {
                ChangeKind::Install => ("install", Style::default().fg(Color::Green)),
                ChangeKind::Remove  => ("remove",  Style::default().fg(Color::Red)),
                ChangeKind::Upgrade => ("upgrade", Style::default().fg(Color::Yellow)),
                ChangeKind::Configure => ("configure", Style::default().fg(Color::DarkGray)),
            };
            let old = c.old_version.as_deref().unwrap_or("(new)");
            let new = c.new_version.as_deref().unwrap_or("");
            Row::new(vec![
                Cell::from(action),
                Cell::from(c.name.clone()),
                Cell::from(old),
                Cell::from(new),
            ])
            .style(style)
        }).collect();

        let widths = [
            Constraint::Length(10),
            Constraint::Length(30),
            Constraint::Length(20),
            Constraint::Length(20),
        ];

        let title = if self.changes.is_empty() {
            " Available Updates — System is up to date "
        } else {
            " Available Updates "
        };

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["Action", "Package", "Current Version", "New Version"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(title))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.state.clone();
        f.render_stateful_widget(table, chunks[0], &mut state);

        let upgrades = self.changes.iter().filter(|c| c.kind == ChangeKind::Upgrade).count();
        let installs = self.changes.iter().filter(|c| c.kind == ChangeKind::Install).count();
        let removes  = self.changes.iter().filter(|c| c.kind == ChangeKind::Remove).count();

        let summary = if self.changes.is_empty() {
            "  System is up to date".to_string()
        } else {
            format!("  {} to upgrade  {} to install  {} to remove", upgrades, installs, removes)
        };

        f.render_widget(
            Paragraph::new(summary).block(Block::default().borders(Borders::ALL).title(" Summary ")),
            chunks[1],
        );

        f.render_widget(
            Paragraph::new("  [u] Run upgrade  [?] Help")
                .block(Block::default().borders(Borders::ALL).title(" Actions ")),
            chunks[2],
        );
    }
}