gregg 1.0.6

Compact keyboard-first terminal monitor that polls greggd endpoints and renders each system in a compact five-row base block.
#![allow(dead_code)]

use crate::normalized::NormalizedDrive;
use crate::state::SystemState;

const KIB: u64 = 1024;
const MIB: u64 = KIB * 1024;
const GIB: u64 = MIB * 1024;
const TIB: u64 = GIB * 1024;

/// Format a byte count as a human-readable string using binary units.
#[allow(clippy::cast_precision_loss)]
pub fn format_bytes(bytes: u64) -> String {
    if bytes == 0 {
        return "0 B".to_string();
    }

    if bytes >= TIB {
        format!("{:.1} TiB", bytes as f64 / TIB as f64)
    } else if bytes >= GIB {
        format!("{:.1} GiB", bytes as f64 / GIB as f64)
    } else if bytes >= MIB {
        format!("{:.1} MiB", bytes as f64 / MIB as f64)
    } else if bytes >= KIB {
        format!("{:.1} KiB", bytes as f64 / KIB as f64)
    } else {
        format!("{bytes} B")
    }
}

/// Format a percentage value.
pub fn format_pct(pct: f32) -> String {
    let clamped = pct.clamp(0.0, 100.0);
    if clamped >= 100.0 {
        "100%".to_string()
    } else if clamped <= 0.0 {
        "0.0%".to_string()
    } else {
        format!("{clamped:.1}%")
    }
}

/// Format load averages as a compact string.
pub fn format_load(load: &gregg_protocol::LoadAverage) -> String {
    format!("{:.2}/{:.2}/{:.2}", load.one, load.five, load.fifteen)
}

/// Compose a priority-aware header line for an online system.
///
/// Priority (dropped as width decreases):
/// 1. Display name or hostname
/// 2. I/O-wait value or "--" for unsupported
/// 3. Load averages or "--" for unsupported
/// 4. Logical core count
/// 5. OS name/version
/// 6. Kernel release
/// 7. Architecture
pub fn header_line(system: &SystemState, width: u16) -> String {
    let Some(snap) = &system.latest else {
        return format!("{} (no data)", display_name(system));
    };

    let name = display_name(system);

    let io_str = if snap.cpu_iowait_supported {
        match snap.iowait_pct {
            Some(iowait) => format!("IO {iowait:.1}%"),
            None => "IO \u{2014}".to_string(),
        }
    } else {
        "IO \u{2014}".to_string()
    };

    let load_str = match &snap.load {
        Some(l) => format_load(l),
        None => "L \u{2014}".to_string(),
    };
    let cores_str = format!("{}c", snap.logical_cores);
    let os_str = format!("{} {}", snap.system.os_name, snap.system.os_version);
    let kernel_str = format!("{} {}", snap.system.kernel_name, snap.system.kernel_release);
    let arch_str = &snap.system.architecture;

    if width >= 80 {
        format!("{name}  {io_str}  {load_str}  {cores_str}  {os_str}  {kernel_str}  {arch_str}")
    } else if width >= 50 {
        format!("{name}  {io_str}  {load_str}  {cores_str}  {os_str}")
    } else if width >= 32 {
        format!("{name}  {io_str}  {load_str}  {cores_str}")
    } else {
        format!("{name}  {io_str}")
    }
}

/// Return the display name for a system.
///
/// If a name was configured by the operator, it is preferred for stable
/// identity in the TUI regardless of what the daemon reports. The
/// endpoint host is used as a fallback when no configured name exists.
fn display_name(system: &SystemState) -> &str {
    system
        .configured_name
        .as_deref()
        .unwrap_or(&system.endpoint.host)
}

/// Format one selected-system drive detail row without allowing the mount
/// name to overwrite its numeric value columns.
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn drive_detail_line(drive: &NormalizedDrive, width: u16) -> String {
    let percentage = if drive.total_bytes > 0 && drive.used_bytes <= drive.total_bytes {
        format_pct((drive.used_bytes as f64 * 100.0 / drive.total_bytes as f64) as f32)
    } else {
        "".to_string()
    };
    let values = format!(
        "{} / {}  {percentage}",
        format_bytes(drive.used_bytes),
        format_bytes(drive.total_bytes)
    );
    let width = usize::from(width);
    let value_width = unicode_width::UnicodeWidthStr::width(values.as_str());
    if width > value_width + 3 {
        let name_width = width - value_width - 3;
        format!("  {}  {values}", truncate_width(&drive.name, name_width))
    } else if width > percentage.len() + 3 {
        let name_width = width - percentage.len() - 3;
        format!(
            "  {}  {percentage}",
            truncate_width(&drive.name, name_width)
        )
    } else {
        format!("  {}", truncate_width(&drive.name, width.saturating_sub(2)))
    }
}

pub(crate) fn truncate_width(s: &str, max_width: usize) -> String {
    use unicode_width::UnicodeWidthChar;

    let mut width = 0;
    let mut end = 0;
    for (index, ch) in s.char_indices() {
        let char_width = ch.width().unwrap_or(0);
        if width + char_width > max_width {
            break;
        }
        width += char_width;
        end = index + ch.len_utf8();
    }
    if end == s.len() {
        s.to_string()
    } else if max_width > 0 && width < max_width {
        format!("{}", &s[..end])
    } else {
        s[..end].to_string()
    }
}