rustpm 0.2.6

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, Tabs},
    text::Line,
    Frame,
};

use crate::kernel::detector::KernelEntry;
use crate::kernel::vanilla::{ReleaseType, VanillaRelease};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum KernelTab {
    Debian,
    Vanilla,
}

pub struct KernelPanel {
    pub kernels: Vec<KernelEntry>,
    pub vanilla: Vec<VanillaRelease>,
    pub active_tab: KernelTab,
    pub debian_state: TableState,
    pub vanilla_state: TableState,
}

impl KernelPanel {
    pub fn new(kernels: Vec<KernelEntry>, vanilla: Vec<VanillaRelease>) -> Self {
        let mut debian_state = TableState::default();
        if !kernels.is_empty() {
            debian_state.select(Some(0));
        }
        let mut vanilla_state = TableState::default();
        if !vanilla.is_empty() {
            vanilla_state.select(Some(0));
        }

        Self {
            kernels,
            vanilla,
            active_tab: KernelTab::Debian,
            debian_state,
            vanilla_state,
        }
    }

    pub fn handle_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Char('v') => self.active_tab = KernelTab::Vanilla,
            KeyCode::Char('d') => self.active_tab = KernelTab::Debian,
            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) {
        match self.active_tab {
            KernelTab::Debian => {
                let len = self.kernels.len();
                if len == 0 { return; }
                let i = self.debian_state.selected().map_or(0, |i| (i + step).min(len - 1));
                self.debian_state.select(Some(i));
            }
            KernelTab::Vanilla => {
                let len = self.vanilla.len();
                if len == 0 { return; }
                let i = self.vanilla_state.selected().map_or(0, |i| (i + step).min(len - 1));
                self.vanilla_state.select(Some(i));
            }
        }
    }

    fn prev(&mut self, step: usize) {
        match self.active_tab {
            KernelTab::Debian => {
                let i = self.debian_state.selected().map_or(0, |i| i.saturating_sub(step));
                self.debian_state.select(Some(i));
            }
            KernelTab::Vanilla => {
                let i = self.vanilla_state.selected().map_or(0, |i| i.saturating_sub(step));
                self.vanilla_state.select(Some(i));
            }
        }
    }

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

        // Inner tab bar
        let tab_titles = vec![Line::from("Debian packages"), Line::from("Vanilla / mainline")];
        let inner_tabs = Tabs::new(tab_titles)
            .block(Block::default().borders(Borders::ALL))
            .select(self.active_tab as usize)
            .style(Style::default().fg(Color::White))
            .highlight_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD));
        f.render_widget(inner_tabs, chunks[0]);

        match self.active_tab {
            KernelTab::Debian => self.render_debian(f, chunks[1]),
            KernelTab::Vanilla => self.render_vanilla(f, chunks[1]),
        }

        let actions = match self.active_tab {
            KernelTab::Debian => "  [i] install  [r] remove  [p] pin/unpin  [v] vanilla tab  [?] help",
            KernelTab::Vanilla => "  [i] download & install  [r] remove  [d] debian tab  [?] help",
        };
        let action_bar = Paragraph::new(actions)
            .block(Block::default().borders(Borders::ALL).title(" Actions "));
        f.render_widget(action_bar, chunks[2]);
    }

    fn render_debian(&self, f: &mut Frame, area: Rect) {
        let rows: Vec<Row> = self
            .kernels
            .iter()
            .map(|k| {
                let marker = if k.is_running { "* " } else { "  " };
                let version = format!("{}{}", marker, k.version);
                let image_status = if k.installed { "installed" } else { "" };
                let headers_status = if k.headers_installed { "installed" } else { "" };
                let apt_ver = k.apt_version.as_deref().unwrap_or("");
                let held = if k.is_held { "[held]" } else { "" };

                let style = if k.is_running {
                    Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
                } else if k.installed {
                    Style::default().fg(Color::White)
                } else {
                    Style::default().fg(Color::DarkGray)
                };

                Row::new(vec![
                    Cell::from(version),
                    Cell::from(image_status),
                    Cell::from(headers_status),
                    Cell::from(apt_ver),
                    Cell::from(held),
                ])
                .style(style)
            })
            .collect();

        let widths = [
            Constraint::Length(35),
            Constraint::Length(12),
            Constraint::Length(12),
            Constraint::Length(20),
            Constraint::Length(8),
        ];

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["Version", "Image", "Headers", "APT Version", "Hold"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(" Debian Kernels  (* = running) "))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.debian_state.clone();
        f.render_stateful_widget(table, area, &mut state);
    }

    fn render_vanilla(&self, f: &mut Frame, area: Rect) {
        let rows: Vec<Row> = self
            .vanilla
            .iter()
            .map(|v| {
                let installed_marker = if v.installed { "" } else { "" };
                let released = v.released.as_deref().unwrap_or("").to_string();

                let style = match v.release_type {
                    ReleaseType::Stable => Style::default().fg(Color::Green),
                    ReleaseType::Mainline => Style::default().fg(Color::Yellow),
                    ReleaseType::Longterm => Style::default().fg(Color::Cyan),
                    ReleaseType::Eol => Style::default().fg(Color::DarkGray),
                    ReleaseType::Unknown => Style::default(),
                };

                Row::new(vec![
                    Cell::from(v.version.clone()),
                    Cell::from(v.release_type.label()),
                    Cell::from(released),
                    Cell::from(installed_marker),
                ])
                .style(style)
            })
            .collect();

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

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["Release", "Type", "Released", "Installed"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(" Vanilla / Mainline Kernels "))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.vanilla_state.clone();
        f.render_stateful_widget(table, area, &mut state);
    }
}