soar-cli 0.12.1

A modern package manager for Linux
use nu_ansi_term::Color::{Blue, Cyan, Green, Red, Yellow};
use soar_core::SoarResult;
use soar_operations::{health, SoarContext};
use tabled::{
    builder::Builder,
    settings::{peaker::PriorityMax, themes::BorderCorrection, Panel, Style, Width},
};
use tracing::info;

use crate::utils::{icon_or, term_width, Colored, Icons};

pub async fn display_health(ctx: &SoarContext) -> SoarResult<()> {
    let report = health::check_health(ctx)?;

    let mut builder = Builder::new();

    let path_status = if report.path_configured {
        format!("{} Configured", Colored(Green, icon_or(Icons::CHECK, "OK")))
    } else {
        format!(
            "{} {} not in PATH",
            Colored(Yellow, icon_or(Icons::WARNING, "!")),
            Colored(Blue, report.bin_path.display())
        )
    };
    builder.push_record(["PATH".to_string(), path_status]);

    let pkg_status = if report.broken_packages.is_empty() {
        format!("{} None", Colored(Green, icon_or(Icons::CHECK, "OK")))
    } else {
        format!(
            "{} {} found",
            Colored(Red, icon_or(Icons::CROSS, "!")),
            Colored(Red, report.broken_packages.len())
        )
    };
    builder.push_record(["Broken Packages".to_string(), pkg_status]);

    let sym_status = if report.broken_symlinks.is_empty() {
        format!("{} None", Colored(Green, icon_or(Icons::CHECK, "OK")))
    } else {
        format!(
            "{} {} found",
            Colored(Red, icon_or(Icons::CROSS, "!")),
            Colored(Red, report.broken_symlinks.len())
        )
    };
    builder.push_record(["Broken Symlinks".to_string(), sym_status]);

    let table = builder
        .build()
        .with(Panel::header("System Health Check"))
        .with(Style::rounded())
        .with(BorderCorrection {})
        .with(Width::wrap(term_width()).priority(PriorityMax::default()))
        .to_string();

    info!("\n{table}");

    if !report.broken_packages.is_empty() {
        info!("\nBroken packages:");
        for pkg in &report.broken_packages {
            info!(
                "  {} {}#{}: {}",
                Icons::ARROW,
                Colored(Blue, &pkg.pkg_name),
                Colored(Cyan, &pkg.pkg_id),
                Colored(Yellow, &pkg.installed_path)
            );
        }
        info!("Run {} to remove", Colored(Green, "soar clean --broken"));
    }

    if !report.broken_symlinks.is_empty() {
        info!("\nBroken symlinks:");
        for path in &report.broken_symlinks {
            info!("  {} {}", Icons::ARROW, Colored(Yellow, path.display()));
        }
        info!(
            "Run {} to remove",
            Colored(Green, "soar clean --broken-symlinks")
        );
    }

    Ok(())
}

pub async fn remove_broken_packages(ctx: &SoarContext) -> SoarResult<()> {
    let report = health::remove_broken_packages(ctx).await?;

    if report.removed.is_empty() && report.failed.is_empty() {
        info!("No broken packages found.");
        return Ok(());
    }

    for removed in &report.removed {
        info!("Removed {}#{}", removed.pkg_name, removed.pkg_id);
    }

    for failed in &report.failed {
        tracing::error!(
            "Failed to remove {}#{}: {}",
            failed.pkg_name,
            failed.pkg_id,
            failed.error
        );
    }

    if !report.removed.is_empty() && report.failed.is_empty() {
        info!("Removed all broken packages");
    } else if !report.failed.is_empty() {
        tracing::warn!(
            "Some broken packages could not be removed: {}",
            report
                .failed
                .iter()
                .map(|f| format!("{}#{}", f.pkg_name, f.pkg_id))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    Ok(())
}