cinchdb 0.2.0

CLI for CinchDB - database and scope management
//! Output formatting: human-readable tables and JSON

use comfy_table::{presets::UTF8_FULL_CONDENSED, Table, ContentArrangement};

/// Print data as a table or JSON depending on the flag
pub fn print_table_or_json<T: serde::Serialize>(
    headers: &[&str],
    rows: Vec<Vec<String>>,
    data: &T,
    json: bool,
) {
    if json {
        println!(
            "{}",
            serde_json::to_string_pretty(data).expect("json serialization failed")
        );
        return;
    }

    if rows.is_empty() {
        println!("No results.");
        return;
    }

    let mut table = Table::new();
    table.load_preset(UTF8_FULL_CONDENSED);
    table.set_content_arrangement(ContentArrangement::Dynamic);
    table.set_header(headers);

    for row in rows {
        table.add_row(row);
    }

    println!("{table}");
}

/// Print a single key-value set or JSON
pub fn print_kv_or_json<T: serde::Serialize>(
    pairs: &[(&str, &str)],
    data: &T,
    json: bool,
) {
    if json {
        println!(
            "{}",
            serde_json::to_string_pretty(data).expect("json serialization failed")
        );
        return;
    }

    let max_key_len = pairs.iter().map(|(k, _)| k.len()).max().unwrap_or(0);
    for (key, value) in pairs {
        println!(
            "  {:<width$}  {}",
            colored::Colorize::bold(key.to_owned()),
            value,
            width = max_key_len
        );
    }
}

/// Format a Unix timestamp in milliseconds to a human-readable date string.
/// Returns "YYYY-MM-DD HH:MM" in UTC, or the raw number if conversion fails.
pub fn format_timestamp_ms(ts_ms: i64) -> String {
    let secs = ts_ms / 1000;
    // Manual UTC formatting (no chrono dependency needed)
    // Using time calculation from Unix epoch
    const SECS_PER_MIN: i64 = 60;
    const SECS_PER_HOUR: i64 = 3600;
    const SECS_PER_DAY: i64 = 86400;

    let mut days = secs / SECS_PER_DAY;
    let day_secs = secs % SECS_PER_DAY;
    let hour = day_secs / SECS_PER_HOUR;
    let min = (day_secs % SECS_PER_HOUR) / SECS_PER_MIN;

    // Days since 1970-01-01 to Y-M-D
    let mut year = 1970i64;
    loop {
        let days_in_year = if is_leap(year) { 366 } else { 365 };
        if days < days_in_year {
            break;
        }
        days -= days_in_year;
        year += 1;
    }
    let month_days: &[i64] = if is_leap(year) {
        &[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        &[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };
    let mut month = 0usize;
    for &md in month_days {
        if days < md {
            break;
        }
        days -= md;
        month += 1;
    }

    format!(
        "{:04}-{:02}-{:02} {:02}:{:02} UTC",
        year,
        month + 1,
        days + 1,
        hour,
        min,
    )
}

fn is_leap(y: i64) -> bool {
    y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
}

/// Print a success message with a hint for what to do next
pub fn print_success(message: &str, hint: Option<&str>) {
    println!("{} {message}", colored::Colorize::green("ok:"));
    if let Some(hint) = hint {
        println!("\n  {}: {hint}", colored::Colorize::cyan("next"));
    }
}