pacc 0.1.0

A TUI assistant for pacman, paru, and AUR workflows on Arch Linux
use crossterm::event::{KeyCode, KeyEvent};

use crate::domain::{BackendStatus, PackageSource, PackageSummary, SystemSnapshot};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
    Overview,
    Packages,
    Actions,
}

impl Focus {
    pub const ALL: [Focus; 3] = [Focus::Overview, Focus::Packages, Focus::Actions];

    pub fn title(self) -> &'static str {
        match self {
            Focus::Overview => "Overview",
            Focus::Packages => "Packages",
            Focus::Actions => "Actions",
        }
    }
}

#[derive(Debug)]
pub struct App {
    running: bool,
    focus: usize,
    package_index: usize,
    actions_index: usize,
    snapshot: SystemSnapshot,
    actions: Vec<&'static str>,
    logs: Vec<String>,
}

impl App {
    pub fn new() -> Self {
        let snapshot = SystemSnapshot::detect();
        let logs = vec![
            "Welcome to pacc.".to_string(),
            "Goal: manage pacman, paru, and AUR flows from one TUI.".to_string(),
            "Next step: wire real package queries and actions.".to_string(),
        ];

        Self {
            running: true,
            focus: 0,
            package_index: 0,
            actions_index: 0,
            snapshot,
            actions: vec![
                "Sync databases",
                "Upgrade system",
                "Review orphan packages",
                "Clean cache",
                "Inspect AUR updates",
            ],
            logs,
        }
    }

    pub fn is_running(&self) -> bool {
        self.running
    }

    pub fn snapshot(&self) -> &SystemSnapshot {
        &self.snapshot
    }

    pub fn actions(&self) -> &[&'static str] {
        &self.actions
    }

    pub fn logs(&self) -> &[String] {
        &self.logs
    }

    pub fn focus(&self) -> Focus {
        Focus::ALL[self.focus]
    }

    pub fn selected_package(&self) -> usize {
        self.package_index
    }

    pub fn selected_action(&self) -> usize {
        self.actions_index
    }

    pub fn handle_key_event(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Char('q') | KeyCode::Esc => self.running = false,
            KeyCode::Tab => self.next_focus(),
            KeyCode::BackTab => self.prev_focus(),
            KeyCode::Down | KeyCode::Char('j') => self.move_next(),
            KeyCode::Up | KeyCode::Char('k') => self.move_prev(),
            KeyCode::Enter => self.activate(),
            KeyCode::Char('r') => self.refresh(),
            _ => {}
        }
    }

    fn next_focus(&mut self) {
        self.focus = (self.focus + 1) % Focus::ALL.len();
    }

    fn prev_focus(&mut self) {
        self.focus = (self.focus + Focus::ALL.len() - 1) % Focus::ALL.len();
    }

    fn move_next(&mut self) {
        match self.focus() {
            Focus::Overview => {}
            Focus::Packages => {
                if !self.snapshot.packages.is_empty() {
                    self.package_index = (self.package_index + 1) % self.snapshot.packages.len();
                }
            }
            Focus::Actions => {
                if !self.actions.is_empty() {
                    self.actions_index = (self.actions_index + 1) % self.actions.len();
                }
            }
        }
    }

    fn move_prev(&mut self) {
        match self.focus() {
            Focus::Overview => {}
            Focus::Packages => {
                if !self.snapshot.packages.is_empty() {
                    self.package_index = (self.package_index + self.snapshot.packages.len() - 1)
                        % self.snapshot.packages.len();
                }
            }
            Focus::Actions => {
                if !self.actions.is_empty() {
                    self.actions_index =
                        (self.actions_index + self.actions.len() - 1) % self.actions.len();
                }
            }
        }
    }

    fn activate(&mut self) {
        match self.focus() {
            Focus::Overview => self
                .logs
                .push("Overview is informational for now.".to_string()),
            Focus::Packages => {
                if let Some(pkg) = self.snapshot.packages.get(self.package_index) {
                    self.logs.push(format!(
                        "Selected package: {} {} from {}.",
                        pkg.name,
                        pkg.version,
                        pkg.source.label()
                    ));
                }
            }
            Focus::Actions => {
                if let Some(action) = self.actions.get(self.actions_index) {
                    self.logs.push(format!("Queued action stub: {}.", action));
                }
            }
        }
        self.trim_logs();
    }

    fn refresh(&mut self) {
        self.snapshot = SystemSnapshot::detect();
        self.logs
            .push("Refreshed backend detection and demo package snapshot.".to_string());
        self.trim_logs();
    }

    fn trim_logs(&mut self) {
        const MAX_LOGS: usize = 8;
        if self.logs.len() > MAX_LOGS {
            let overflow = self.logs.len() - MAX_LOGS;
            self.logs.drain(0..overflow);
        }
    }
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}

impl BackendStatus {
    pub fn badge(&self) -> &'static str {
        match self {
            BackendStatus::Available => "ready",
            BackendStatus::Missing => "missing",
        }
    }
}

impl PackageSource {
    pub fn label(&self) -> &'static str {
        match self {
            PackageSource::Pacman => "pacman",
            PackageSource::Paru => "paru",
            PackageSource::Aur => "AUR",
        }
    }
}

impl PackageSummary {
    pub fn state_label(&self) -> &'static str {
        if self.has_update { "update" } else { "current" }
    }
}