use std::io::{self, Write};

use crossterm::style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor};

use super::transcript::{character_color, format_time};
use super::{
    abbreviate_model, parse_timestamp, print_dim_line, term_width, use_color, write_dim, write_fg,
    write_row, write_row_colored, write_section_header,
};

// ---------------------------------------------------------------------------
// Status formatter -- human-readable dashboard
// ---------------------------------------------------------------------------

/// Translate a heartbeat state string to a human-readable description.
fn heartbeat_description(state: &str, ticks: u64, max_ticks: u64) -> String {
    match state {
        "Active" if ticks == 0 => "active \u{2014} in conversation".to_string(),
        "Active" => format!("active \u{2014} idle {ticks}/{max_ticks} ticks"),
        "Dormant" => "dormant \u{2014} waiting for you".to_string(),
        other => other.to_string(),
    }
}

/// Map a normalized density (0.0-1.0) to a bar character.
///
/// Uses 8 Unicode block elements for non-zero values and a light shade for
/// effectively-zero values.
fn density_to_block(normalized: f64) -> char {
    const BLOCKS: [char; 8] = [
        '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
        '\u{2588}',
    ];
    if normalized < 0.05 {
        '\u{2591}'
    } else {
        let idx = ((normalized * 7.0).round() as usize).min(7);
        BLOCKS[idx]
    }
}

/// Color for an hour classification label.
fn classification_color(class: &str) -> Color {
    match class {
        "peak" => Color::Cyan,
        "trough" => Color::DarkGrey,
        _ => Color::White,
    }
}

/// Write the activity heatmap section into the status dashboard.
///
/// Renders a 24-character bar chart (one block per hour) with hour labels
/// underneath, plus engagement and session stats.
fn write_activity_section(out: &mut impl Write, activity: &serde_json::Value, width: usize) {
    let histogram: Vec<f64> = match activity["hour_histogram"].as_array() {
        Some(arr) => arr.iter().filter_map(|v| v.as_f64()).collect(),
        None => return,
    };
    if histogram.len() != 24 {
        return;
    }
    let classifications: Vec<String> = activity["hour_classifications"]
        .as_array()
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();
    if classifications.len() != 24 {
        return;
    }

    let sufficient = activity["has_sufficient_heatmap"]
        .as_bool()
        .unwrap_or(false);
    let suffix = if sufficient { "" } else { "sparse" };
    write_section_header(out, "Activity", suffix, width);

    // -- bar chart row --
    let max_val = histogram.iter().cloned().fold(0.0_f64, f64::max);
    if use_color() {
        let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
    }
    let _ = write!(out, "  {:<13}", "");
    for (i, &density) in histogram.iter().enumerate() {
        let linear = if max_val > 0.0 {
            density / max_val
        } else {
            0.0
        };
        // Log scale: ln(1 + x*k) / ln(1+k) -- spreads low values, compresses peaks.
        let normalized = (1.0 + linear * 9.0).ln() / 10.0_f64.ln();
        let ch = density_to_block(normalized);
        if use_color() {
            let color = classification_color(&classifications[i]);
            let _ = crossterm::execute!(out, SetForegroundColor(color));
        }
        let _ = write!(out, "{ch}");
    }
    if use_color() {
        let _ = crossterm::execute!(out, ResetColor);
    }
    let _ = writeln!(out);

    // -- hour labels row --
    //    0  3  6  9  12 15 18 21
    if use_color() {
        let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
    }
    let _ = write!(out, "  {:<13}0  3  6  9  12 15 18 21", "");
    if use_color() {
        let _ = crossterm::execute!(out, ResetColor);
    }
    let _ = writeln!(out);

    // -- stats row --
    let engagement = activity["engagement_score"].as_f64().unwrap_or(0.0);
    let sessions = activity["sessions_per_day"].as_f64().unwrap_or(0.0);
    let turn_count = activity["turn_count"].as_u64().unwrap_or(0);
    write_row(
        out,
        "Engagement",
        &format!("{engagement:.2} \u{00b7} {sessions:.1} sessions/day \u{00b7} {turn_count} turns"),
    );

    let _ = writeln!(out);
}

/// Print the status dashboard.
pub fn print_status(data: &serde_json::Value, character_name: &str) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    // -- Status --
    write_section_header(&mut out, "Status", "", width);

    // Prefer the character name from the daemon response over the CLI fallback.
    let effective_name = data["character"].as_str().unwrap_or(character_name);
    let char_color = character_color(effective_name);
    write_row_colored(&mut out, "Character", effective_name, char_color);

    let model = data["active_model"].as_str().unwrap_or("(none)");
    write_row(&mut out, "Model", abbreviate_model(model));

    if let Some(count) = data["turn_count"].as_u64() {
        write_row(&mut out, "Turns", &count.to_string());
    }

    let pending_deferred_edit_count = data["pending_deferred_edit_count"].as_u64().unwrap_or(0);
    if pending_deferred_edit_count > 0 {
        let paths: Vec<&str> = data["pending_deferred_edits"]
            .as_array()
            .into_iter()
            .flatten()
            .filter_map(|path| path.as_str())
            .collect();
        let label = if pending_deferred_edit_count == 1 {
            "1 pending".to_string()
        } else {
            format!("{pending_deferred_edit_count} pending")
        };
        let detail = if paths.is_empty() {
            label
        } else {
            format!("{label}: {}", paths.join(", "))
        };
        write_row(&mut out, "Prompt Edits", &detail);
    }

    let _ = writeln!(out);

    // -- Clients --
    if let Some(clients) = data.get("clients").and_then(|c| c.as_array()) {
        if !clients.is_empty() {
            write_section_header(&mut out, "Clients", "", width);
            for client in clients {
                let ctype = client["client_type"].as_str().unwrap_or("?");
                let cname = client["client_name"].as_str().unwrap_or("?");
                write_row(&mut out, ctype, cname);
            }
            let _ = writeln!(out);
        }
    }

    // -- Autonomy --
    if let Some(autonomy) = data.get("autonomy") {
        if !autonomy.is_null() {
            write_autonomy_section(&mut out, autonomy, width);
        }
    }

    // -- Activity --
    if let Some(activity) = data.get("activity") {
        if !activity.is_null() {
            let turn_count = activity["turn_count"].as_u64().unwrap_or(0);
            if turn_count > 0 {
                write_activity_section(&mut out, activity, width);
            }
        }
    }
}

// ---------------------------------------------------------------------------
// Command-specific formatters
// ---------------------------------------------------------------------------

/// Dispatch a command response to the appropriate formatter.
/// Falls back to generic JSON output for unknown command names.
pub fn format_command(name: &str, data: &serde_json::Value) {
    match name {
        "character_info" => print_character_info(data),
        "list_models" => print_model_list(data),
        "switch_model" => print_model_switched(data),
        "reset_model" => print_model_reset(data),
        "model_info" => print_model_info(data),
        "model_settings" => print_model_settings(data),
        "set_model_setting" => print_set_model_setting(data),
        "list_providers" => print_provider_list(data),
        "list_provider_models" => print_provider_models(data),
        "refresh_provider_models" => print_provider_refresh(data),
        "refresh_all_provider_models" => print_provider_refresh_all(data),
        "memory" => print_memory(data),
        "compact" => print_compact_result(data),
        "memory_changelog" => print_changelog(data),
        "memory_dream" => print_memory_dream(data),
        "config" => print_config(data),
        "config_check" => print_config_check(data),
        "config_reset" => print_config_reset(data),
        "edit" => print_edit_confirmation(data),
        "delete" => print_delete_confirmation(data),
        "alt" => print_alt_confirmation(data),
        "list_alternatives" => print_alt_list(data),
        "inject_system" => println!("System instruction injected."),
        "diagnostics" => print_diagnostics(data),
        "usage" => print_usage(data),
        "heartbeat_tick_now" => print_heartbeat_tick_now(data),
        "heartbeat_set_dormant" => print_heartbeat_status_change(data, "dormant"),
        "heartbeat_set_active" => print_heartbeat_status_change(data, "active"),
        _ => print_command_output_fallback(name, data),
    }
}

fn print_memory_dream(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();
    let char_name = data["character"].as_str().unwrap_or("?");
    write_section_header(&mut out, "Dreaming", char_name, width);

    if data.get("state_path").is_some() {
        write_row(
            &mut out,
            "Enabled",
            if data["enabled"].as_bool().unwrap_or(false) {
                "yes"
            } else {
                "no"
            },
        );
        write_row(
            &mut out,
            "Frequency",
            data["frequency"].as_str().unwrap_or("?"),
        );
        write_row(
            &mut out,
            "Due",
            if data["due"].as_bool().unwrap_or(false) {
                "yes"
            } else {
                "no"
            },
        );
        if let Some(last) = data["last_run_at"].as_str() {
            write_row(&mut out, "Last run", last);
        }
    } else if data["status"].as_str() == Some("not_due") {
        write_row(&mut out, "Status", "not due");
    } else {
        let dry = data["dry_run"].as_bool().unwrap_or(false);
        write_row(&mut out, "Status", if dry { "dry run" } else { "ran" });
        let candidates = data["candidate_count"].as_u64().unwrap_or_else(|| {
            data["candidates"]
                .as_array()
                .map_or(0, |items| items.len() as u64)
        });
        let indexed = data["indexed_count"].as_u64().unwrap_or_else(|| {
            data["indexed"]
                .as_array()
                .map_or(0, |items| items.len() as u64)
        });
        let rejected = data["rejected_count"].as_u64().unwrap_or(0);
        write_row(&mut out, "Candidates", &candidates.to_string());
        write_row(&mut out, "Indexed", &indexed.to_string());
        write_row(&mut out, "Deferred", &rejected.to_string());
        let paths = if dry {
            data["would_write_paths"].as_array()
        } else {
            data["paths_written"].as_array()
        };
        if let Some(paths) = paths {
            write_row(
                &mut out,
                if dry { "Would write" } else { "Paths written" },
                &paths.len().to_string(),
            );
        }
    }
    let _ = writeln!(out);
}

fn print_command_output_fallback(name: &str, data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    if use_color() {
        let _ = crossterm::execute!(out, SetAttribute(Attribute::Bold));
    }
    let _ = write!(out, "{name}");
    if use_color() {
        let _ = crossterm::execute!(out, SetAttribute(Attribute::Reset));
    }
    let _ = writeln!(out);
    if let Ok(pretty) = serde_json::to_string_pretty(data) {
        let _ = writeln!(out, "{pretty}");
    }
}

fn print_heartbeat_tick_now(data: &serde_json::Value) {
    let character = data["character"].as_str().unwrap_or("?");
    println!("Tick scheduled for {character}.");
    if let Some(warning) = data["warning"].as_str() {
        let stdout = io::stdout();
        let mut out = stdout.lock();
        write_fg(&mut out, Color::Yellow, warning);
        let _ = writeln!(out);
    }
}

fn print_heartbeat_status_change(data: &serde_json::Value, status: &str) {
    let character = data["character"].as_str().unwrap_or("?");
    println!("Heartbeat forced {status} for {character}.");
}

/// Print edit confirmation.
fn print_edit_confirmation(data: &serde_json::Value) {
    let msg_ref = data["ref"].as_str().unwrap_or("?");
    println!("Edited message {msg_ref}");
}

/// Print delete confirmation.
fn print_delete_confirmation(data: &serde_json::Value) {
    if let Some(arr) = data["deleted"].as_array() {
        let n = arr.len();
        if n == 1 {
            let id = arr[0].as_str().unwrap_or("?");
            println!("Deleted entry {id}");
        } else {
            println!("Deleted {n} entries");
        }
    } else if let Some(id) = data["deleted"].as_str() {
        println!("Deleted entry {id}");
    }
}

/// Print alternate-response selection confirmation.
fn print_alt_confirmation(data: &serde_json::Value) {
    let msg_ref = data["ref"].as_str().unwrap_or("?");
    let position = data["position"].as_u64().unwrap_or(0);
    let count = data["alt_count"].as_u64().unwrap_or(0);
    println!("Selected alternate {position}/{count} for {msg_ref}");
}

fn alt_preview(content: &str, max_width: usize) -> String {
    let compact = content.split_whitespace().collect::<Vec<_>>().join(" ");
    if compact.len() <= max_width {
        return compact;
    }
    let end = compact
        .char_indices()
        .take_while(|(idx, _)| *idx <= max_width.saturating_sub(3))
        .last()
        .map(|(idx, ch)| idx + ch.len_utf8())
        .unwrap_or(0);
    format!("{}...", &compact[..end])
}

/// Print alternate-response list.
fn print_alt_list(data: &serde_json::Value) {
    let msg_ref = data["ref"].as_str().unwrap_or("?");
    let alternatives = data["alternatives"]
        .as_array()
        .map(Vec::as_slice)
        .unwrap_or(&[]);
    if alternatives.is_empty() {
        println!("No alternate responses for {msg_ref}.");
        return;
    }

    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();
    write_section_header(&mut out, "Alternates", msg_ref, width);
    let preview_width = width.saturating_sub(14).max(24);
    for alt in alternatives {
        let position = alt["position"].as_u64().unwrap_or(0);
        let count = data["alt_count"]
            .as_u64()
            .unwrap_or(alternatives.len() as u64);
        let marker = if alt["active"].as_bool().unwrap_or(false) {
            "*"
        } else {
            " "
        };
        let preview = alt_preview(alt["content"].as_str().unwrap_or(""), preview_width);
        let _ = writeln!(out, "  {marker} {position}/{count}  {preview}");
    }
    let _ = writeln!(out);
}

/// Print model list.
///
/// Phase 8: rows include the source tag (`static`/`discovered`) so users
/// can tell aliases from upstream-discovered ids at a glance, and an
/// optional `hidden_count` footer points them at `--all` when filtered
/// models exist.
fn print_model_list(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let active = data["active"].as_str().unwrap_or("");
    let include_hidden = data["include_hidden"].as_bool().unwrap_or(false);
    let suffix = if include_hidden { "all" } else { "" };
    write_section_header(&mut out, "Models", suffix, width);

    if let Some(models) = data["models"].as_array() {
        for m in models {
            let name = m["name"].as_str().unwrap_or("?");
            let provider = m["provider"].as_str().unwrap_or("?");
            let source = m["source"].as_str().unwrap_or("");
            let hidden = m["hidden"].as_bool().unwrap_or(false);
            let is_active = name == active || m["qualified_name"].as_str() == Some(active);

            let marker = if is_active { "*" } else { " " };

            if use_color() && is_active {
                let _ = crossterm::execute!(out, SetForegroundColor(Color::Cyan));
            } else if use_color() {
                let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
            }
            let _ = write!(out, "  {marker} ");
            if use_color() {
                let _ = crossterm::execute!(out, ResetColor);
            }
            let _ = write!(out, "{name:<28}");
            if use_color() {
                let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
            }
            let _ = write!(out, "{provider:<14}");
            // Tag like `static` / `discovered`. Hidden rows (only seen
            // with `--all`) carry an extra `hidden` so users can spot
            // why their default list filtered them.
            if !source.is_empty() {
                let tag = if hidden {
                    format!("{source}, hidden")
                } else {
                    source.to_string()
                };
                let _ = write!(out, "[{tag}]");
            }
            if use_color() {
                let _ = crossterm::execute!(out, ResetColor);
            }
            let _ = writeln!(out);
        }
    }

    // Hint about hidden models the user is not currently seeing.
    let hidden_count = data["hidden_count"].as_u64().unwrap_or(0);
    if !include_hidden && hidden_count > 0 {
        let _ = writeln!(out);
        write_dim(
            &mut out,
            &format!("  ({hidden_count} hidden — use `shore model --all` to include them)"),
        );
        let _ = writeln!(out);
    }
    let _ = writeln!(out);
}

/// Print model switch confirmation.
fn print_model_switched(data: &serde_json::Value) {
    let model = data["active"].as_str().unwrap_or("(none)");
    println!("Switched to model: {}", abbreviate_model(model));
}

/// Print model reset confirmation.
fn print_model_reset(data: &serde_json::Value) {
    let model = data["active"].as_str().unwrap_or("(none)");
    println!("Model reset to: {}", abbreviate_model(model));
}

/// Print detailed model info.
fn print_model_info(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let name = data["name"].as_str().unwrap_or("?");
    write_section_header(&mut out, "Model", name, width);

    if let Some(qn) = data["qualified_name"].as_str() {
        write_row(&mut out, "Qualified", qn);
    }
    if let Some(mid) = data["model_id"].as_str() {
        write_row(&mut out, "Model ID", mid);
    }
    if let Some(sdk) = data["sdk"].as_str() {
        write_row(&mut out, "SDK", sdk);
    }
    if let Some(pk) = data["provider_key"].as_str() {
        write_row(&mut out, "Provider", pk);
    }
    if let Some(url) = data["base_url"].as_str() {
        write_row(&mut out, "Base URL", url);
    }
    if let Some(key) = data["api_key_env"].as_str() {
        write_row(&mut out, "API key env", &format!("${key}"));
    }

    // Cache settings
    if let Some(ttl) = data["cache_ttl_secs"].as_u64() {
        if ttl > 0 {
            write_row(&mut out, "Cache TTL", &format!("{ttl}s"));
        }
    }
    if let Some(depth) = data["cache_depth"].as_u64() {
        if depth > 0 {
            write_row(&mut out, "Cache depth", &depth.to_string());
        }
    }
    if let Some(re) = data["reasoning_effort"].as_str() {
        write_row(&mut out, "Reasoning", re);
    }
    if let Some(mt) = data["max_tokens"].as_u64() {
        write_row(&mut out, "Max tokens", &mt.to_string());
    }
    let _ = writeln!(out);
}

/// Print effective sampler settings + which scope set each value.
fn print_model_settings(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let model = data["model"].as_str().unwrap_or("?");
    write_section_header(&mut out, "Model Settings", model, width);

    let sampler = data.get("effective_sampler").cloned().unwrap_or_default();
    let scopes = data.get("scopes").cloned().unwrap_or_default();
    let keys = [
        "temperature",
        "top_p",
        "reasoning_effort",
        "thinking_enabled",
        "budget_tokens",
        "max_tokens",
        "cache_ttl",
    ];
    for key in keys {
        let value = match sampler.get(key) {
            Some(v) if v.is_null() => "(unset)".to_string(),
            Some(v) => v
                .as_str()
                .map(String::from)
                .unwrap_or_else(|| v.to_string()),
            None => "(unset)".to_string(),
        };
        let scope = scopes
            .get(key)
            .and_then(|v| v.as_str())
            .unwrap_or("(default)");
        let label = format!("{key:<17}");
        write_row(&mut out, &label, &format!("{value}  [{scope}]"));
    }
    let _ = writeln!(out);
}

/// Print confirmation after `set_model_setting`.
fn print_set_model_setting(data: &serde_json::Value) {
    let key = data["key"].as_str().unwrap_or("?");
    let scope = data["scope"].as_str().unwrap_or("?");
    let value = match data.get("value") {
        Some(v) if v.is_null() => "(cleared)".to_string(),
        Some(v) => v
            .as_str()
            .map(String::from)
            .unwrap_or_else(|| v.to_string()),
        None => "(cleared)".to_string(),
    };
    let model = data["model"].as_str().unwrap_or("?");
    println!("[{scope}] {key} = {value}  ({})", abbreviate_model(model));
}

/// Print the configured provider list with key + cache status.
fn print_provider_list(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    write_section_header(&mut out, "Providers", "", width);

    let providers = match data["providers"].as_array() {
        Some(p) if !p.is_empty() => p,
        _ => {
            print_dim_line(&mut out, "(no providers configured)");
            let _ = writeln!(out);
            return;
        }
    };

    for p in providers {
        let name = p["name"].as_str().unwrap_or("?");
        let enabled = p["enabled"].as_bool().unwrap_or(true);
        let sdk = p["sdk"].as_str().unwrap_or("?");
        let base_url = p["base_url"].as_str().unwrap_or("");
        let discovery = p["discovery_enabled"].as_bool().unwrap_or(false);

        // Header line: bold provider name + sdk tag, dim base_url.
        if use_color() {
            let _ = crossterm::execute!(out, SetAttribute(Attribute::Bold));
        }
        let _ = write!(out, "  {name}");
        if use_color() {
            let _ = crossterm::execute!(out, SetAttribute(Attribute::Reset));
        }
        if !enabled {
            write_fg(&mut out, Color::Yellow, "  [disabled]");
        }
        if discovery {
            write_dim(&mut out, "  [discovery]");
        }
        let _ = writeln!(out);

        write_row(&mut out, "SDK", sdk);
        if !base_url.is_empty() {
            write_row(&mut out, "Base URL", base_url);
        }

        if let Some(keys) = p["keys"].as_array() {
            if keys.is_empty() {
                write_row(&mut out, "Keys", "(none configured)");
            } else {
                let mut parts: Vec<String> = Vec::new();
                for k in keys {
                    let kn = k["name"].as_str().unwrap_or("?");
                    let env_set = k["env_set"].as_bool().unwrap_or(false);
                    let warn = k["warn_on_fallback"].as_bool().unwrap_or(false);
                    let mark = if env_set { "set" } else { "missing" };
                    let warn_tag = if warn { "*" } else { "" };
                    parts.push(format!("{kn}{warn_tag}={mark}"));
                }
                write_row(&mut out, "Keys", &parts.join(", "));
            }
        }

        let cache = &p["cache"];
        if cache["present"].as_bool().unwrap_or(false) {
            let total = cache["models"].as_u64().unwrap_or(0);
            let visible = cache["visible"].as_u64().unwrap_or(total);
            let hidden = cache["hidden"].as_u64().unwrap_or(0);
            let fetched = cache["fetched_at"].as_str().unwrap_or("?");
            let summary = if hidden > 0 {
                format!("{visible} visible / {hidden} hidden / {total} total · fetched {fetched}")
            } else {
                format!("{total} models · fetched {fetched}")
            };
            write_row(&mut out, "Cache", &summary);
        } else {
            write_row(
                &mut out,
                "Cache",
                "(empty — `shore provider refresh <name>`)",
            );
        }
        let _ = writeln!(out);
    }
}

/// Print discovered + static models for a single provider.
fn print_provider_models(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let provider = data["provider"].as_str().unwrap_or("?");
    let include_hidden = data["include_hidden"].as_bool().unwrap_or(false);
    let suffix = if include_hidden { "all" } else { "" };
    write_section_header(&mut out, &format!("Provider — {provider}"), suffix, width);

    let static_models = data["static"].as_array().cloned().unwrap_or_default();
    if !static_models.is_empty() {
        write_dim(&mut out, "  static\n");
        for m in &static_models {
            let name = m["name"].as_str().unwrap_or("?");
            let id = m["model_id"].as_str().unwrap_or("?");
            let _ = writeln!(out, "    {name:<28}{id}");
        }
        let _ = writeln!(out);
    }

    let discovered = data["discovered"].as_array().cloned().unwrap_or_default();
    if !discovered.is_empty() {
        write_dim(&mut out, "  discovered\n");
        for m in &discovered {
            let id = m["model_id"].as_str().unwrap_or("?");
            let display = m["display_name"].as_str().unwrap_or("");
            if display.is_empty() {
                let _ = writeln!(out, "    {id}");
            } else {
                let _ = writeln!(out, "    {id:<48}{display}");
            }
        }
        let _ = writeln!(out);
    }

    let hidden = data["hidden"].as_array().cloned().unwrap_or_default();
    if !hidden.is_empty() {
        write_dim(
            &mut out,
            &format!("  hidden ({} — pass --all to include)\n", hidden.len()),
        );
        for m in &hidden {
            let id = m["model_id"].as_str().unwrap_or("?");
            let _ = writeln!(out, "    {id}");
        }
        let _ = writeln!(out);
    }

    if static_models.is_empty() && discovered.is_empty() && hidden.is_empty() {
        print_dim_line(
            &mut out,
            "no models — run `shore provider refresh <name>` if discovery is configured",
        );
        let _ = writeln!(out);
    }

    if let Some(cache) = data.get("cache") {
        if let Some(fetched) = cache["fetched_at"].as_str() {
            write_dim(&mut out, &format!("  cache fetched {fetched}\n"));
        }
    }
}

/// Print the result of `shore provider refresh <name>`.
fn print_provider_refresh(data: &serde_json::Value) {
    let provider = data["provider"].as_str().unwrap_or("?");
    let count = data["model_count"].as_u64().unwrap_or(0);
    let fetched = data["fetched_at"].as_str().unwrap_or("?");
    println!("Refreshed {provider}: {count} models (fetched {fetched})");
}

/// Print the result of `shore provider refresh` (no name) — one row per
/// provider with ok/FAIL status, plus a `skipped` section listing every
/// provider that was excluded with a reason.
fn print_provider_refresh_all(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();
    write_section_header(&mut out, "Provider refresh", "all", width);

    let mut ok_count: u64 = 0;
    let mut fail_count: u64 = 0;

    if let Some(results) = data["results"].as_array() {
        for r in results {
            let provider = r["provider"].as_str().unwrap_or("?");
            let ok = r["ok"].as_bool().unwrap_or(false);
            if ok {
                ok_count += 1;
                let count = r["model_count"].as_u64().unwrap_or(0);
                let fetched = r["fetched_at"].as_str().unwrap_or("?");
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::Green));
                    let _ = write!(out, "  ok  ");
                    let _ = crossterm::execute!(out, ResetColor);
                } else {
                    let _ = write!(out, "  ok  ");
                }
                let _ = writeln!(out, "{provider}: {count} models (fetched {fetched})");
            } else {
                fail_count += 1;
                let err = r["error"].as_str().unwrap_or("unknown error");
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::Red));
                    let _ = write!(out, "  FAIL");
                    let _ = crossterm::execute!(out, ResetColor);
                } else {
                    let _ = write!(out, "  FAIL");
                }
                let _ = writeln!(out, " {provider}: {err}");
            }
        }
    }

    if let Some(skipped) = data["skipped"].as_array() {
        if !skipped.is_empty() {
            let _ = writeln!(out);
            write_section_header(&mut out, "Skipped", "", width);
            for s in skipped {
                let provider = s["provider"].as_str().unwrap_or("?");
                let reason = s["reason"].as_str().unwrap_or("?");
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
                }
                let _ = writeln!(out, "  {provider}: {reason}");
                if use_color() {
                    let _ = crossterm::execute!(out, ResetColor);
                }
            }
        }
    }

    let _ = writeln!(out);
    let _ = writeln!(
        out,
        "Refreshed {ok_count} provider(s); {fail_count} failed."
    );
}

/// Print character info.
fn print_character_info(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let name = data["name"].as_str().unwrap_or("?");
    let char_color = character_color(name);

    write_section_header(&mut out, "Character", "", width);
    write_row_colored(&mut out, "Name", name, char_color);

    let active = data["active"].as_bool().unwrap_or(false);
    if active {
        write_row_colored(&mut out, "Active", "yes", Color::Green);
    }

    if let Some(dir) = data["config_dir"].as_str() {
        write_row(&mut out, "Config", dir);
    }

    let has_def = data["has_definition"].as_bool().unwrap_or(false);
    let has_user = data["has_user_definition"].as_bool().unwrap_or(false);
    write_row(&mut out, "Definition", if has_def { "yes" } else { "no" });
    if has_user {
        write_row(&mut out, "User def", "yes");
    }

    if data["has_config_override"].as_bool().unwrap_or(false) {
        write_row_colored(&mut out, "Config override", "yes", Color::Yellow);
    }

    if let Some(overrides) = data["prompt_overrides"].as_array() {
        if !overrides.is_empty() {
            let names: Vec<&str> = overrides.iter().filter_map(|v| v.as_str()).collect();
            write_row(&mut out, "Prompts", &names.join(", "));
        }
    }

    if let Some(dir) = data["data_dir"].as_str() {
        write_row(&mut out, "Data", dir);
    }

    // Definition preview
    if let Some(preview) = data["definition_preview"].as_str() {
        if !preview.is_empty() {
            let _ = writeln!(out);
            write_section_header(&mut out, "Preview", "", width);
            // Show first few lines, dimmed
            if use_color() {
                let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
            }
            for line in preview.lines().take(8) {
                let _ = writeln!(out, "  {line}");
            }
            if use_color() {
                let _ = crossterm::execute!(out, ResetColor);
            }
        }
    }
    let _ = writeln!(out);
}

/// Print memory status or query result.
fn print_memory(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    // If there's a "result" field, this is a query response.
    if let Some(result) = data["result"].as_str() {
        let _ = writeln!(out, "{result}");
        return;
    }

    // Otherwise it's a status response.
    let char_name = data["character"].as_str().unwrap_or("?");
    write_section_header(&mut out, "Memory", char_name, width);

    let files = data["entries"].as_u64().unwrap_or(0);
    let curated = data["curated_files"].as_u64().unwrap_or(0);
    let daily = data["daily_files"].as_u64().unwrap_or(0);
    let images = data["image_files"].as_u64().unwrap_or(0);

    write_row(&mut out, "Files", &files.to_string());
    if files > 0 {
        write_row(
            &mut out,
            "Breakdown",
            &format!("{curated} curated, {daily} daily, {images} images"),
        );
    }
    let _ = writeln!(out);
}

/// Print memory changelog.
fn print_changelog(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let char_name = data["character"].as_str().unwrap_or("?");
    write_section_header(&mut out, "Memory Changelog", char_name, width);

    if let Some(entries) = data["changelog"].as_array() {
        if entries.is_empty() {
            if use_color() {
                let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
            }
            let _ = writeln!(out, "  (no entries)");
            if use_color() {
                let _ = crossterm::execute!(out, ResetColor);
            }
        } else {
            for entry in entries {
                let ts = entry["timestamp"].as_str().unwrap_or("");
                let op = entry["operation"].as_str().unwrap_or("?");
                let desc = entry["description"].as_str().unwrap_or("");

                let time_display = parse_timestamp(ts)
                    .map(|dt| dt.format("%b %d %H:%M").to_string())
                    .unwrap_or_else(|| ts.to_string());

                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
                }
                let _ = write!(out, "  {time_display:<16}");

                let op_color = match op {
                    s if s.starts_with("create") || s.starts_with("compaction") => Color::Green,
                    s if s.starts_with("update") => Color::DarkYellow,
                    s if s.starts_with("supersede")
                        || s.starts_with("delete")
                        || s.starts_with("decay") =>
                    {
                        Color::Red
                    }
                    _ => Color::White,
                };
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(op_color));
                }
                let _ = write!(out, "{op:<18}");
                if use_color() {
                    let _ = crossterm::execute!(out, ResetColor);
                }
                let _ = writeln!(out, "{desc}");
            }
        }
    }
    let _ = writeln!(out);
}

/// Print compaction result.
fn print_compact_result(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let status = data["status"].as_str().unwrap_or("?");
    let suffix = if status == "dry_run" { "dry run" } else { "" };
    write_section_header(&mut out, "Compaction", suffix, width);

    let char_name = data["character"].as_str().unwrap_or("?");
    write_row(&mut out, "Character", char_name);

    if status == "dry_run" {
        let would = data["would_write_files"].as_u64().unwrap_or(0);
        write_row(&mut out, "Would write", &format!("{would} files"));
        let turns = data["compacted_turns"]
            .as_u64()
            .or_else(|| data["turn_count"].as_u64())
            .unwrap_or(0);
        let retained_turns = data["retained_turns"].as_u64().unwrap_or(0);
        write_row(
            &mut out,
            "Turns",
            &format!("{turns} compacted, {retained_turns} retained"),
        );
    } else {
        let files = data["memory_files_written"]
            .as_array()
            .map_or(0, |files| files.len());
        write_row(&mut out, "Memory files", &format!("{files} written"));
        let turns = data["compacted_turns"]
            .as_u64()
            .or_else(|| data["turn_count"].as_u64())
            .unwrap_or(0);
        let retained_turns = data["retained_turns"].as_u64().unwrap_or(0);
        write_row(
            &mut out,
            "Turns",
            &format!("{turns} compacted, {retained_turns} retained"),
        );
    }

    let _ = writeln!(out);
}

/// Print config display.
fn print_config(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    // Config set confirmation: { "set": "key", "value": ... }
    if let Some(key) = data["set"].as_str() {
        let value = &data["value"];
        let _ = writeln!(out, "Set {key} = {value}");
        return;
    }

    // Section view: { "key": "name", "config": { ... } }
    if let Some(key) = data["key"].as_str() {
        write_section_header(&mut out, "Config", key, width);
        print_config_section(&mut out, &data["config"], 1);
        let _ = writeln!(out);
        return;
    }

    // Full config: { "config": { ... } }
    if let Some(config) = data.get("config") {
        write_section_header(&mut out, "Config", "", width);
        print_config_section(&mut out, config, 1);
        let _ = writeln!(out);
    }
}

/// Recursively print config as indented key-value pairs.
fn print_config_section(out: &mut impl Write, value: &serde_json::Value, depth: usize) {
    let indent = "  ".repeat(depth);
    match value {
        serde_json::Value::Object(map) => {
            for (k, v) in map {
                match v {
                    serde_json::Value::Object(_) => {
                        if use_color() {
                            let _ = crossterm::execute!(out, SetForegroundColor(Color::White));
                        }
                        let _ = writeln!(out, "{indent}{k}:");
                        if use_color() {
                            let _ = crossterm::execute!(out, ResetColor);
                        }
                        print_config_section(out, v, depth + 1);
                    }
                    serde_json::Value::Null => {} // skip nulls
                    _ => {
                        if use_color() {
                            let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
                        }
                        let _ = write!(out, "{indent}{k:<24}");
                        if use_color() {
                            let _ = crossterm::execute!(out, ResetColor);
                        }
                        let display = match v {
                            serde_json::Value::String(s) => s.clone(),
                            serde_json::Value::Bool(b) => b.to_string(),
                            serde_json::Value::Number(n) => n.to_string(),
                            serde_json::Value::Array(arr) => {
                                let items: Vec<String> = arr
                                    .iter()
                                    .map(|i| {
                                        i.as_str()
                                            .map(String::from)
                                            .unwrap_or_else(|| i.to_string())
                                    })
                                    .collect();
                                items.join(", ")
                            }
                            _ => v.to_string(),
                        };
                        let _ = writeln!(out, "{display}");
                    }
                }
            }
        }
        _ => {
            let _ = writeln!(out, "{indent}{value}");
        }
    }
}

/// Print config check results.
fn print_config_check(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    let valid = data["valid"].as_bool().unwrap_or(false);
    let suffix = if valid { "valid" } else { "warnings" };
    write_section_header(&mut out, "Config Check", suffix, width);

    if let Some(dir) = data["config_dir"].as_str() {
        write_row(&mut out, "Config dir", dir);
    }
    if let Some(dir) = data["data_dir"].as_str() {
        write_row(&mut out, "Data dir", dir);
    }

    let chat = data["chat_models"].as_u64().unwrap_or(0);
    let tool = data["tool_models"].as_u64().unwrap_or(0);
    let embed = data["embedding_models"].as_u64().unwrap_or(0);
    write_row(
        &mut out,
        "Models",
        &format!("{chat} chat, {tool} tool, {embed} embedding"),
    );

    let _ = writeln!(out);

    // Warnings
    if let Some(warnings) = data["warnings"].as_array() {
        for w in warnings {
            if let Some(msg) = w.as_str() {
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkYellow));
                }
                let _ = write!(out, "  ! ");
                if use_color() {
                    let _ = crossterm::execute!(out, ResetColor);
                }
                let _ = writeln!(out, "{msg}");
            }
        }
    }

    // Info
    if let Some(info) = data["info"].as_array() {
        for i in info {
            if let Some(msg) = i.as_str() {
                if use_color() {
                    let _ = crossterm::execute!(out, SetForegroundColor(Color::Green));
                }
                let _ = write!(out, "  ");
                if use_color() {
                    let _ = crossterm::execute!(out, ResetColor);
                }
                let _ = writeln!(out, "{msg}");
            }
        }
    }
    let _ = writeln!(out);
}

/// Print config reset confirmation.
fn print_config_reset(data: &serde_json::Value) {
    let msg = data["message"]
        .as_str()
        .unwrap_or("Configuration reloaded from disk");
    println!("{msg}");
}

/// Print diagnostics from ring buffers.
pub fn print_diagnostics(data: &serde_json::Value) {
    let stdout = io::stdout();
    let mut out = stdout.lock();
    let width = term_width();

    // -- API Calls --
    print_diagnostics_section(
        &mut out,
        "API Calls",
        &data["api_calls"],
        width,
        |out, call| {
            let model = abbreviate_model(call["model"].as_str().unwrap_or("?"));
            let input = call["input_tokens"].as_u64().unwrap_or(0);
            let output_t = call["output_tokens"].as_u64().unwrap_or(0);
            let cr = call["cache_read_tokens"].as_u64().unwrap_or(0);
            let cw = call["cache_write_tokens"].as_u64().unwrap_or(0);
            let total = call["total_ms"].as_u64().unwrap_or(0);
            let secs = total as f64 / 1000.0;

            let _ = write!(out, "{model:<24}");
            write_dim(
                out,
                &format!("in:{input:<5} out:{output_t:<5} cache:{cr}/{cw}  {secs:.1}s"),
            );

            if let Some(err) = call.get("error").filter(|v| !v.is_null()) {
                write_fg(
                    out,
                    Color::Red,
                    &format!("  ERR: {}", err.as_str().unwrap_or("?")),
                );
            }
            let _ = writeln!(out);
        },
    );

    // -- Tool Calls --
    print_diagnostics_section(
        &mut out,
        "Tool Calls",
        &data["tool_calls"],
        width,
        |out, call| {
            let name = call["tool_name"].as_str().unwrap_or("?");
            let dur = call["duration_ms"].as_u64().unwrap_or(0);
            let ok = call["success"].as_bool().unwrap_or(true);

            let _ = write!(out, "{name:<24}");
            write_dim(out, &format!("{dur}ms  "));
            let (marker_color, marker_text) = if ok {
                (Color::Green, "ok")
            } else {
                (Color::Red, "FAIL")
            };
            write_fg(out, marker_color, marker_text);
            let _ = writeln!(out);
        },
    );

    // -- Errors --
    print_diagnostics_section(&mut out, "Errors", &data["errors"], width, |out, err| {
        let etype = err["error_type"].as_str().unwrap_or("?");
        let msg = err["message"].as_str().unwrap_or("?");

        write_fg(out, Color::Red, &format!("{etype:<12}"));
        let _ = writeln!(out, "{msg}");
    });
}

/// Print a diagnostics section with a header, shared timestamp formatting,
/// and a per-entry formatter.
fn print_diagnostics_section<W: Write>(
    out: &mut W,
    title: &str,
    section: &serde_json::Value,
    width: usize,
    mut format_row: impl FnMut(&mut W, &serde_json::Value),
) {
    let count = section["count"].as_u64().unwrap_or(0);
    write_section_header(out, title, &format!("{count} total"), width);

    if let Some(entries) = section["recent"].as_array() {
        if entries.is_empty() {
            print_dim_line(out, "(none)");
        } else {
            for entry in entries {
                let ts = entry["timestamp"].as_str().unwrap_or("");
                let time = parse_timestamp(ts)
                    .map(|dt| dt.format("%H:%M:%S").to_string())
                    .unwrap_or_else(|| ts.chars().take(8).collect());

                write_dim(out, &format!("  {time}  "));
                format_row(out, entry);
            }
        }
    }
    let _ = writeln!(out);
}

fn format_k(tokens: u64) -> String {
    if tokens == 0 {
        "\u{2014}".into()
    } else if tokens < 1000 {
        tokens.to_string()
    } else {
        format!("{:.1}K", tokens as f64 / 1000.0)
    }
}

/// Truncate `s` so it fits in `max_width` display columns, appending `…` when
/// truncation occurs. Width is counted in chars (the table columns assume
/// monospace single-width glyphs).
fn ellipsize(s: &str, max_width: usize) -> String {
    if max_width == 0 {
        return String::new();
    }
    let count = s.chars().count();
    if count <= max_width {
        return s.to_string();
    }
    let mut out: String = s.chars().take(max_width.saturating_sub(1)).collect();
    out.push('\u{2026}');
    out
}

/// Render an RFC 3339 reset timestamp as the user's local clock time with an
/// explicit offset (e.g. `2026-05-23 10:00 +10:00`). The raw daemon payload
/// is always UTC, which reads as midnight even when `reset_hour` is set —
/// surfacing the local offset makes the value match the configured anchor.
fn format_reset_at(rfc3339: &str) -> String {
    parse_timestamp(rfc3339)
        .map(|dt| dt.format("%Y-%m-%d %H:%M %:z").to_string())
        .unwrap_or_else(|| rfc3339.to_string())
}

fn print_budget_table(data: &serde_json::Value) {
    let budgets = data["budgets"].as_array();
    if budgets.is_none_or(|rows| rows.is_empty()) {
        println!("  No usage budgets configured.");
        return;
    }

    // `Reset` column matches the width of `format_reset_at`'s output
    // (`YYYY-MM-DD HH:MM ±HH:MM` = 23 chars); raw-string fallbacks are
    // ellipsized to the same width so the divider always spans the table.
    const RESET_W: usize = 23;
    let table_w = 24 + 1 + 6 + 1 + 11 + 1 + 7 + 2 + 15 + 1 + 16 + 1 + RESET_W;

    println!(
        "{:<24} {:<6} {:>11} {:>7}  {:<15} {:<16} {:<RESET_W$}",
        "Budget", "Period", "Spend", "Used", "Status", "Action", "Reset"
    );
    println!("{}", "-".repeat(table_w));
    if let Some(rows) = budgets {
        for budget in rows {
            let current = budget["current_cost"].as_f64().unwrap_or(0.0);
            let limit = budget["cost_limit"].as_f64().unwrap_or(0.0);
            let percent = budget["percent_used"].as_f64().unwrap_or(0.0) * 100.0;
            let reset = budget["reset_at"]
                .as_str()
                .map(format_reset_at)
                .map(|s| ellipsize(&s, RESET_W))
                .unwrap_or_else(|| "?".into());
            println!(
                "{:<24} {:<6} {:>5.2}/{:<5.2} {:>6.0}%  {:<15} {:<16} {reset:<RESET_W$}",
                budget["name"].as_str().unwrap_or("budget"),
                budget["period"].as_str().unwrap_or("day"),
                current,
                limit,
                percent,
                budget["status"].as_str().unwrap_or("ok"),
                budget["action"].as_str().unwrap_or("warn"),
            );
        }
    }
}

fn print_spike_warnings(data: &serde_json::Value) {
    let warnings = data["spike_warnings"].as_array();
    if warnings.is_none_or(|rows| rows.is_empty()) {
        return;
    }
    println!("\nSpike Warnings:");
    if let Some(rows) = warnings {
        for warning in rows {
            println!(
                "  {}",
                warning["message"]
                    .as_str()
                    .unwrap_or("Usage spike detected.")
            );
        }
    }
}

fn usage_display_date(data: &serde_json::Value) -> String {
    if data["timezone"].as_str() == Some("utc") {
        chrono::Utc::now().format("%Y-%m-%d").to_string()
    } else {
        chrono::Local::now().format("%Y-%m-%d").to_string()
    }
}

/// Render the per-provider/model summary table for `shore usage`.
///
/// Column widths for `Provider` and `Model` are computed from the rendered
/// rows so that an oversized name (e.g. `openrouter-anthropic`) can no longer
/// shove every subsequent value under the wrong header. Values that exceed
/// the per-column cap are truncated with `…`.
fn write_usage_summary_table(out: &mut impl Write, data: &serde_json::Value) -> io::Result<()> {
    const PROVIDER_HEADER: &str = "Provider";
    const MODEL_HEADER: &str = "Model";
    // Caps keep one freakishly long name from blowing the table out across
    // the terminal; anything longer is truncated with `…`.
    const MAX_PROVIDER_W: usize = 28;
    const MAX_MODEL_W: usize = 40;

    let period = data["period"].as_str().unwrap_or("today");
    let today = usage_display_date(data);
    writeln!(out, "Shore Usage \u{2014} {today} (period: {period})\n")?;

    let summary = data["summary"].as_array();

    let mut provider_w = PROVIDER_HEADER.chars().count();
    let mut model_w = MODEL_HEADER.chars().count();
    if let Some(rows) = summary {
        for s in rows {
            if let Some(p) = s["provider"].as_str() {
                provider_w = provider_w.max(p.chars().count());
            }
            if let Some(m) = s["model"].as_str() {
                model_w = model_w.max(m.chars().count());
            }
        }
    }
    provider_w = provider_w.min(MAX_PROVIDER_W);
    model_w = model_w.min(MAX_MODEL_W);

    writeln!(
        out,
        "{:<provider_w$} {:<model_w$} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
        PROVIDER_HEADER, MODEL_HEADER, "Calls", "Input", "Output", "Cache R", "Cache W", "Cost"
    )?;

    let total_w = provider_w + 1 + model_w + 1 + 5 + 2 + 9 + 2 + 9 + 2 + 9 + 2 + 9 + 2 + 8;
    writeln!(out, "{}", "-".repeat(total_w))?;

    let mut grand_total = 0.0f64;
    if let Some(rows) = summary {
        for s in rows {
            let cost_str = s["total_cost"]
                .as_f64()
                .map(|c| {
                    grand_total += c;
                    format!("${c:.2}")
                })
                .unwrap_or_else(|| "\u{2014}".into());
            let provider = ellipsize(s["provider"].as_str().unwrap_or(""), provider_w);
            let model = ellipsize(s["model"].as_str().unwrap_or(""), model_w);
            writeln!(
                out,
                "{provider:<provider_w$} {model:<model_w$} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                s["call_count"].as_u64().unwrap_or(0),
                format_k(s["total_input"].as_u64().unwrap_or(0)),
                format_k(s["total_output"].as_u64().unwrap_or(0)),
                format_k(s["total_cache_read"].as_u64().unwrap_or(0)),
                format_k(s["total_cache_write"].as_u64().unwrap_or(0)),
                cost_str,
            )?;
        }
        if !rows.is_empty() {
            // "Total:" sits in the column space before Cost; the dollar amount
            // right-aligns inside the 8-char Cost column so it lines up with
            // the per-row costs above it.
            let label_w = total_w.saturating_sub(9);
            let total_str = format!("${grand_total:.2}");
            writeln!(out, "{:>label_w$} {total_str:>8}", "Total:")?;
        } else {
            writeln!(out, "  No usage data for this period.")?;
        }
    }
    Ok(())
}

pub fn print_usage(data: &serde_json::Value) {
    let mode = data["mode"].as_str().unwrap_or("summary");

    match mode {
        "tsv" | "csv" => {
            if let Some(d) = data["data"].as_str() {
                print!("{d}");
            }
        }
        "summary_by_call_type" => {
            let period = data["period"].as_str().unwrap_or("today");
            let today = usage_display_date(data);
            println!("Shore Usage by Call Type \u{2014} {today} (period: {period})\n");
            println!(
                "{:<18} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                "Call Type", "Calls", "Input", "Output", "Cache R", "Cache W", "Cost"
            );
            println!("{}", "-".repeat(78));
            let summary = data["summary"].as_array();
            let mut grand_total = 0.0f64;
            if let Some(rows) = summary {
                for s in rows {
                    let cost_str = s["total_cost"]
                        .as_f64()
                        .map(|c| {
                            grand_total += c;
                            format!("${c:.2}")
                        })
                        .unwrap_or_else(|| "\u{2014}".into());
                    println!(
                        "{:<18} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                        s["call_type"].as_str().unwrap_or(""),
                        s["call_count"].as_u64().unwrap_or(0),
                        format_k(s["total_input"].as_u64().unwrap_or(0)),
                        format_k(s["total_output"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_read"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_write"].as_u64().unwrap_or(0)),
                        cost_str,
                    );
                }
                if !rows.is_empty() {
                    println!("{:>70} ${grand_total:.2}", "Total:");
                } else {
                    println!("  No usage data for this period.");
                }
            }
        }
        "summary_by_usage_kind" => {
            let period = data["period"].as_str().unwrap_or("today");
            let today = usage_display_date(data);
            println!("Shore Usage by Kind - {today} (period: {period})\n");
            println!(
                "{:<20} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                "Usage Kind", "Calls", "Input", "Output", "Cache R", "Cache W", "Cost"
            );
            println!("{}", "-".repeat(80));
            let summary = data["summary"].as_array();
            let mut grand_total = 0.0f64;
            if let Some(rows) = summary {
                for s in rows {
                    let cost_str = s["total_cost"]
                        .as_f64()
                        .map(|c| {
                            grand_total += c;
                            format!("${c:.2}")
                        })
                        .unwrap_or_else(|| "\u{2014}".into());
                    println!(
                        "{:<20} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                        s["usage_kind"].as_str().unwrap_or(""),
                        s["call_count"].as_u64().unwrap_or(0),
                        format_k(s["total_input"].as_u64().unwrap_or(0)),
                        format_k(s["total_output"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_read"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_write"].as_u64().unwrap_or(0)),
                        cost_str,
                    );
                }
                if !rows.is_empty() {
                    println!("{:>72} ${grand_total:.2}", "Total:");
                } else {
                    println!("  No usage data for this period.");
                }
            }
        }
        "summary_by_api_key" => {
            let period = data["period"].as_str().unwrap_or("today");
            let today = usage_display_date(data);
            println!("Shore Usage by API Key - {today} (period: {period})\n");
            println!(
                "{:<22} {:<18} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                "Provider", "API Key", "Calls", "Input", "Output", "Cache R", "Cache W", "Cost"
            );
            println!("{}", "-".repeat(102));
            let summary = data["summary"].as_array();
            let mut grand_total = 0.0f64;
            if let Some(rows) = summary {
                for s in rows {
                    let cost_str = s["total_cost"]
                        .as_f64()
                        .map(|c| {
                            grand_total += c;
                            format!("${c:.2}")
                        })
                        .unwrap_or_else(|| "\u{2014}".into());
                    println!(
                        "{:<22} {:<18} {:>5}  {:>9}  {:>9}  {:>9}  {:>9}  {:>8}",
                        s["provider"].as_str().unwrap_or(""),
                        s["api_key_name"].as_str().unwrap_or("unknown"),
                        s["call_count"].as_u64().unwrap_or(0),
                        format_k(s["total_input"].as_u64().unwrap_or(0)),
                        format_k(s["total_output"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_read"].as_u64().unwrap_or(0)),
                        format_k(s["total_cache_write"].as_u64().unwrap_or(0)),
                        cost_str,
                    );
                }
                if !rows.is_empty() {
                    println!("{:>94} ${grand_total:.2}", "Total:");
                } else {
                    println!("  No usage data for this period.");
                }
            }
        }
        "budget" => {
            let today = usage_display_date(data);
            let timezone = data["timezone"].as_str().unwrap_or("local");
            println!("Shore Usage Budgets - {today} (timezone: {timezone})\n");
            print_budget_table(data);
            print_spike_warnings(data);
        }
        "anomalies" => {
            let anomalies = data["anomalies"].as_array();
            if anomalies.is_none() || anomalies.unwrap().is_empty() {
                println!("No cache anomalies found.");
            } else if let Some(rows) = anomalies {
                println!("Cache Anomalies:\n");
                for r in rows {
                    println!(
                        "  {} {} {} {} \u{2014} {} (read: {}, write: {})",
                        r["ts"].as_str().unwrap_or("?"),
                        r["character"].as_str().unwrap_or("?"),
                        r["model"].as_str().unwrap_or("?"),
                        r["call_type"].as_str().unwrap_or("?"),
                        r["anomaly"].as_str().unwrap_or("?"),
                        r["cache_read_tokens"].as_u64().unwrap_or(0),
                        r["cache_write_tokens"].as_u64().unwrap_or(0),
                    );
                }
                println!("\nTotal: {} anomalies", rows.len());
            }
        }
        "refresh_pricing" => {
            println!("Pricing cache cleared. Prices will be re-fetched on next daemon use.");
        }
        "recalculate" => {
            let updated = data["updated"].as_u64().unwrap_or(0);
            let total = data["total"].as_u64().unwrap_or(0);
            if total == 0 {
                println!("All rows already have costs calculated.");
            } else {
                println!(
                    "Updated {updated}/{total} rows. {} still missing pricing data.",
                    total - updated
                );
                if let Some(failures) = data["failures"].as_array() {
                    if !failures.is_empty() {
                        println!("\nFailed models:");
                        for f in failures {
                            println!(
                                "  {} — {}",
                                f["model"].as_str().unwrap_or("?"),
                                f["reason"].as_str().unwrap_or("unknown")
                            );
                        }
                    }
                }
            }
        }
        _ => {
            let mut stdout = io::stdout().lock();
            let _ = write_usage_summary_table(&mut stdout, data);
            drop(stdout);

            if let Some(health) = data["cache_health"].as_array() {
                if !health.is_empty() {
                    println!("\nCache Health (anthropic):");
                    for entry in health {
                        let char_name = entry["character"].as_str().unwrap_or("?");
                        let state = entry["state"].as_str().unwrap_or("cold");
                        let streak = entry["streak"].as_u64().unwrap_or(0);
                        let state_str = if state == "warm" {
                            format!("Warm (streak: {streak} calls)")
                        } else {
                            "Cold".into()
                        };
                        println!("  {char_name:<8} \u{2014} {state_str}");
                    }
                }
            }

            if let Some(budgets) = data["budgets"].as_array() {
                if !budgets.is_empty() {
                    println!("\nBudgets:");
                    print_budget_table(data);
                }
            }
            print_spike_warnings(data);

            let anomaly_count = data["anomaly_count_7d"].as_u64().unwrap_or(0);
            println!("\nAnomalies (last 7d): {anomaly_count}");
        }
    }
}

// ---------------------------------------------------------------------------
// Autonomy section — rendered inside `shore status`
// ---------------------------------------------------------------------------

/// Format a duration in seconds into a compact label like "1h 8m" or "32m".
/// Negative inputs render with a leading "-".
fn format_duration_compact(secs: i64) -> String {
    let neg = secs < 0;
    let mut s = secs.unsigned_abs();
    let days = s / 86_400;
    s %= 86_400;
    let hours = s / 3_600;
    s %= 3_600;
    let minutes = s / 60;
    let seconds = s % 60;

    let body = if days > 0 {
        format!("{days}d {hours}h")
    } else if hours > 0 {
        format!("{hours}h {minutes}m")
    } else if minutes > 0 {
        format!("{minutes}m")
    } else {
        format!("{seconds}s")
    };
    if neg {
        format!("-{body}")
    } else {
        body
    }
}

/// Format a duration in seconds for "threshold" rows like "100m" or "48h".
fn format_threshold(secs: u64) -> String {
    if secs >= 3_600 && secs.is_multiple_of(3_600) {
        format!("{}h", secs / 3_600)
    } else if secs >= 60 && secs.is_multiple_of(60) {
        format!("{}m", secs / 60)
    } else if secs >= 3_600 {
        format!("{}h {}m", secs / 3_600, (secs % 3_600) / 60)
    } else if secs >= 60 {
        format!("{}m {}s", secs / 60, secs % 60)
    } else {
        format!("{secs}s")
    }
}

/// Format an RFC3339 timestamp as "YYYY-MM-DD HH:MM" in local time, or the
/// raw string on parse failure.
fn format_local_timestamp(rfc3339: &str) -> String {
    parse_timestamp(rfc3339)
        .map(|dt| dt.format("%Y-%m-%d %H:%M").to_string())
        .unwrap_or_else(|| rfc3339.to_string())
}

/// Render the autonomy block of `shore status`. Reads the `AutonomyStatus`
/// JSON snapshot from the daemon and renders state, schedule, thresholds,
/// and the most recent heartbeat events.
fn write_autonomy_section(out: &mut impl Write, autonomy: &serde_json::Value, width: usize) {
    let paused = autonomy["paused"].as_bool().unwrap_or(false);
    let suffix = if paused { "paused" } else { "" };
    write_section_header(out, "Autonomy", suffix, width);

    let int_state = autonomy["heartbeat_state"].as_str().unwrap_or("Active");
    let ticks = autonomy["ticks_without_user"].as_u64().unwrap_or(0);
    let max_ticks = autonomy["dormant_after_heartbeat_turns"]
        .as_u64()
        .unwrap_or(0);
    let description = heartbeat_description(int_state, ticks, max_ticks);

    // Heartbeat row: description + state label.
    if use_color() {
        let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
    }
    let _ = write!(out, "  {:<13}", "Heartbeat");
    if use_color() {
        let _ = crossterm::execute!(out, ResetColor);
    }
    let _ = write!(out, "{description}  ");
    if use_color() {
        let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
    }
    let _ = write!(out, "({int_state})");
    if use_color() {
        let _ = crossterm::execute!(out, ResetColor);
    }
    let _ = writeln!(out);

    if let Some(eff) = autonomy["effective_interval_secs"].as_u64() {
        let mins = eff / 60;
        let secs = eff % 60;
        let label = if secs == 0 {
            format!("{mins}m")
        } else {
            format!("{mins}m{secs}s")
        };
        write_row(out, "Interval", &label);
    }

    if let Some(secs) = autonomy["seconds_until_wake"].as_i64() {
        let abs_label = autonomy["next_wake_at"]
            .as_str()
            .map(format_local_timestamp)
            .unwrap_or_default();
        let rel = if secs >= 0 {
            format!("in {}", format_duration_compact(secs))
        } else {
            format!("{} overdue", format_duration_compact(-secs))
        };
        let detail = if abs_label.is_empty() {
            rel
        } else {
            format!("{rel}  ({abs_label})")
        };
        write_row(out, "Next Wake", &detail);
    } else {
        write_row(out, "Next Wake", "(none scheduled)");
    }

    if let Some(secs) = autonomy["seconds_since_user"].as_i64() {
        let abs_label = autonomy["last_user_at"]
            .as_str()
            .map(format_local_timestamp)
            .unwrap_or_default();
        let rel = format!("{} ago", format_duration_compact(secs));
        let detail = if abs_label.is_empty() {
            rel
        } else {
            format!("{rel}  ({abs_label})")
        };
        write_row(out, "Last User", &detail);
    }

    write_row(out, "Idle Ticks", &format!("{ticks} / {max_ticks}"));

    if let Some(secs) = autonomy["minimum_heartbeat_latency_secs"].as_u64() {
        write_row(out, "Min Latency", &format_threshold(secs));
    }
    if let Some(secs) = autonomy["dormant_after_idle_time_secs"].as_u64() {
        write_row(out, "Idle Limit", &format_threshold(secs));
    }

    // Recent events. Skip silently if the daemon didn't include any —
    // there's nothing useful to show and the schedule rows above already
    // tell the same story.
    let events: Vec<serde_json::Value> = autonomy
        .get("recent_events")
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();
    if events.is_empty() {
        let _ = writeln!(out);
        return;
    }

    let _ = writeln!(out);
    if use_color() {
        let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
    }
    let _ = writeln!(out, "  Recent events:");
    if use_color() {
        let _ = crossterm::execute!(out, ResetColor);
    }
    let mut prev_date: Option<String> = None;
    for event in &events {
        let ts = event["timestamp"].as_str().unwrap_or("");
        let kind = event["kind"].as_str().unwrap_or("?");
        let detail = event["detail"].as_str().unwrap_or("");
        let time_str = parse_timestamp(ts)
            .map(|dt| {
                let formatted = format_time(&dt, prev_date.as_deref());
                prev_date = Some(dt.format("%Y-%m-%d").to_string());
                formatted
            })
            .unwrap_or_else(|| ts.chars().take(8).collect());
        let kind_color = match kind {
            "tick_fired" => Color::Blue,
            "message_sent" => Color::Green,
            "message_skipped" => Color::DarkGrey,
            "tool_use" => Color::Cyan,
            "dormant" => Color::Red,
            "wake" => Color::Green,
            "dormant_ping" => Color::Magenta,
            "timeout" => Color::Yellow,
            _ => Color::White,
        };
        if use_color() {
            let _ = crossterm::execute!(out, SetForegroundColor(Color::DarkGrey));
        }
        let _ = write!(out, "    {time_str:<12}");
        if use_color() {
            let _ = crossterm::execute!(out, SetForegroundColor(kind_color));
        }
        let _ = write!(out, "{kind:<17}");
        if use_color() {
            let _ = crossterm::execute!(out, ResetColor);
        }
        let _ = writeln!(out, "{detail}");
    }
    let _ = writeln!(out);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::output::set_color_enabled;

    #[test]
    fn heartbeat_description_maps_states() {
        assert_eq!(
            heartbeat_description("Active", 0, 3),
            "active \u{2014} in conversation"
        );
        assert_eq!(
            heartbeat_description("Active", 2, 3),
            "active \u{2014} idle 2/3 ticks"
        );
        assert_eq!(
            heartbeat_description("Dormant", 4, 3),
            "dormant \u{2014} waiting for you"
        );
    }

    #[test]
    fn print_status_does_not_panic() {
        set_color_enabled(false);
        let data = serde_json::json!({
            "character": "Sable",
            "message_count": 142,
            "turn_count": 142,
            "active_model": "claude-sonnet-4-20250514",
            "pending_deferred_edit_count": 2,
            "pending_deferred_edits": ["SOUL.md", "TOOLS.md"],
            "tokens": {
                "input": 12450,
                "output": 3218,
                "cache_read": 8100,
                "cache_write": 1024,
            },
            "autonomy": {
                "paused": false,
                "heartbeat_state": "Active",
                "ticks_without_user": 1,
                "dormant_after_heartbeat_turns": 3,
                "effective_interval_secs": 3540,
            }
        });
        print_status(&data, "Sable");
    }

    #[test]
    fn print_status_minimal_does_not_panic() {
        set_color_enabled(false);
        let data = serde_json::json!({
            "character": "Sable",
            "message_count": 5,
            "turn_count": 5,
            "active_model": null,
        });
        print_status(&data, "Sable");
    }

    #[test]
    fn print_status_paused_does_not_panic() {
        set_color_enabled(false);
        let data = serde_json::json!({
            "character": "Sable",
            "message_count": 50,
            "turn_count": 50,
            "active_model": "test-model",
            "autonomy": {
                "paused": true,
                "heartbeat_state": "Active",
                "ticks_without_user": 0,
                "dormant_after_heartbeat_turns": 3,
                "effective_interval_secs": 3600,
            }
        });
        print_status(&data, "Sable");
    }

    #[test]
    fn print_status_with_activity_does_not_panic() {
        set_color_enabled(false);
        // Simulate a realistic hour histogram: busier in afternoon/evening.
        let histogram: Vec<f64> = (0..24)
            .map(|h| match h {
                0..=5 => 0.01,
                6..=8 => 0.04,
                9..=11 => 0.06,
                12..=14 => 0.08,
                15..=17 => 0.05,
                18..=21 => 0.10,
                _ => 0.02,
            })
            .collect();
        let classifications: Vec<&str> = (0..24)
            .map(|h| match h {
                0..=5 => "trough",
                18..=21 => "peak",
                _ => "normal",
            })
            .collect();

        let data = serde_json::json!({
            "character": "Sable",
            "message_count": 200,
            "turn_count": 200,
            "active_model": "claude-sonnet-4-20250514",
            "tokens": { "input": 5000, "output": 1200, "cache_read": 0, "cache_write": 0 },
            "activity": {
                "hour_histogram": histogram,
                "hour_classifications": classifications,
                "has_sufficient_heatmap": true,
                "engagement_score": 0.72,
                "sessions_per_day": 2.3,
                "message_count": 200,
                "turn_count": 200,
            }
        });
        print_status(&data, "Sable");
    }

    #[test]
    fn print_status_sparse_activity_does_not_panic() {
        set_color_enabled(false);
        let data = serde_json::json!({
            "character": "Sable",
            "message_count": 3,
            "turn_count": 3,
            "active_model": "test-model",
            "activity": {
                "hour_histogram": vec![0.0_f64; 24],
                "hour_classifications": vec!["normal"; 24],
                "has_sufficient_heatmap": false,
                "engagement_score": 0.0,
                "sessions_per_day": 0.0,
                "message_count": 3,
                "turn_count": 3,
            }
        });
        print_status(&data, "Sable");
    }

    // ── classification_color ─────────────────────────────────────────

    #[test]
    fn classification_color_maps_correctly() {
        assert!(matches!(classification_color("peak"), Color::Cyan));
        assert!(matches!(classification_color("trough"), Color::DarkGrey));
        assert!(matches!(classification_color("normal"), Color::White));
        assert!(matches!(classification_color("unknown"), Color::White));
    }

    // ── heartbeat_description edge cases ──────────────────────────

    #[test]
    fn heartbeat_description_unknown_state() {
        // Unknown states pass through as-is.
        assert_eq!(heartbeat_description("CustomState", 0, 8), "CustomState");
    }

    // ── format_command dispatch ─────────────────────────────────────

    #[test]
    fn format_command_dispatches_known_commands() {
        set_color_enabled(false);
        // These should all run without panic and hit their formatters.
        format_command("config_reset", &serde_json::json!({"message": "reloaded"}));
        format_command("inject_system", &serde_json::json!({}));
        format_command("edit", &serde_json::json!({"ref": "m42"}));
    }

    #[test]
    fn format_command_fallback_for_unknown() {
        set_color_enabled(false);
        // Unknown commands should use fallback (JSON pretty print), not panic.
        format_command("totally_unknown", &serde_json::json!({"key": "val"}));
    }

    #[test]
    fn print_delete_confirmation_single_and_multiple() {
        set_color_enabled(false);
        // Single deletion.
        print_delete_confirmation(&serde_json::json!({"deleted": ["msg_1"]}));
        // Multiple deletions.
        print_delete_confirmation(&serde_json::json!({"deleted": ["msg_1", "msg_2", "msg_3"]}));
        // String form.
        print_delete_confirmation(&serde_json::json!({"deleted": "msg_42"}));
    }

    #[test]
    fn print_model_switched_shows_abbreviated_name() {
        set_color_enabled(false);
        // Should not panic and should abbreviate the date suffix.
        print_model_switched(&serde_json::json!({"active": "claude-sonnet-4-20250514"}));
    }

    #[test]
    fn format_k_correctness() {
        // Regression for SHA 31f20cb: verify correct formatting across boundary.
        // 0 maps to em-dash (—), not a number.
        assert_eq!(format_k(0), "\u{2014}");
        // Numbers < 1000 display without K suffix.
        assert_eq!(format_k(1), "1");
        assert_eq!(format_k(500), "500");
        assert_eq!(format_k(999), "999");
        // 1000 is the first value that rounds to K.
        assert_eq!(format_k(1000), "1.0K");
        // 1500 → "1.5K"
        assert_eq!(format_k(1500), "1.5K");
        // 10000 → "10.0K"
        assert_eq!(format_k(10000), "10.0K");
    }

    #[test]
    fn format_reset_at_shows_local_time_with_offset() {
        // UTC midnight should render as the local equivalent with an explicit
        // offset suffix so users can tell at a glance whether the displayed
        // hour matches their configured `reset_hour`.
        let rendered = format_reset_at("2026-05-23T00:00:00+00:00");
        // "YYYY-MM-DD HH:MM " (17) + "±HH:MM" (6) = 23 chars, regardless of
        // which local timezone the test runner is in.
        assert_eq!(rendered.len(), 23, "unexpected length: {rendered:?}");
        // Offset has a colon; raw RFC 3339 form contained 'T' which we drop.
        assert!(
            !rendered.contains('T'),
            "should not contain 'T': {rendered:?}"
        );
        assert!(
            rendered.contains('+') || rendered.contains('-'),
            "should contain offset sign: {rendered:?}"
        );
        // Malformed input falls back to the raw string.
        assert_eq!(format_reset_at("not-a-timestamp"), "not-a-timestamp");
    }

    #[test]
    fn ellipsize_pads_and_truncates() {
        assert_eq!(ellipsize("short", 10), "short");
        assert_eq!(ellipsize("exactly10!", 10), "exactly10!");
        // Truncated values end with U+2026 and fit exactly in `max_width` chars.
        let truncated = ellipsize("openrouter-anthropic", 12);
        assert_eq!(truncated.chars().count(), 12);
        assert!(truncated.ends_with('\u{2026}'));
        assert!(truncated.starts_with("openrouter-"));
    }

    #[test]
    fn usage_summary_table_keeps_columns_aligned_with_long_provider() {
        // Regression for issue #32: an over-long provider name used to push
        // every subsequent value one column to the right.
        let data = serde_json::json!({
            "mode": "summary",
            "period": "today",
            "timezone": "local",
            "summary": [
                {
                    "provider": "openrouter-anthropic",
                    "model": "anthropic/claude-opus-4.6",
                    "call_count": 96,
                    "total_input": 189_900,
                    "total_output": 52_000,
                    "total_cache_read": 2_513_200,
                    "total_cache_write": 246_300,
                    "total_cost": 5.11,
                },
                {
                    "provider": "anthropic",
                    "model": "claude-sonnet-4.6",
                    "call_count": 12,
                    "total_input": 1_200,
                    "total_output": 800,
                    "total_cache_read": 0,
                    "total_cache_write": 0,
                    "total_cost": 0.42,
                },
            ],
        });
        let mut buf: Vec<u8> = Vec::new();
        write_usage_summary_table(&mut buf, &data).expect("write");
        let rendered = String::from_utf8(buf).expect("utf8");
        let lines: Vec<&str> = rendered.lines().collect();
        // Layout: title, blank, header, separator, two rows, total.
        let header = lines[2];
        let separator = lines[3];
        let row1 = lines[4];
        let row2 = lines[5];

        // Every body line shares the same width as the separator, so no row
        // can shift columns relative to the header.
        assert_eq!(
            row1.chars().count(),
            separator.chars().count(),
            "row1 width"
        );
        assert_eq!(
            row2.chars().count(),
            separator.chars().count(),
            "row2 width"
        );
        assert_eq!(
            header.chars().count(),
            separator.chars().count(),
            "header width"
        );

        // The Cost column right-aligns to the table edge — `$5.11` must end
        // at the last column of the separator.
        assert!(row1.ends_with("$5.11"));
        assert!(row2.ends_with("$0.42"));

        // The grand-total line's cost must end at the same column as the
        // per-row cost (i.e. share the table's right edge). Before the fix
        // the dollar amount sat ~3 columns left of the Cost column.
        let total_line = lines[6];
        assert!(
            total_line.ends_with("$5.53"),
            "total line should right-align with Cost column: {total_line:?}"
        );
        assert_eq!(
            total_line.chars().count(),
            separator.chars().count(),
            "total line width should equal table width"
        );

        // The Calls column ("Calls" header is 5 wide) must contain the number
        // at the same byte offset on both rows.
        let calls_idx_header = header.find("Calls").expect("Calls header present");
        let calls_end = calls_idx_header + "Calls".len();
        assert_eq!(&row1[calls_end - 2..calls_end], "96");
        assert_eq!(&row2[calls_end - 2..calls_end], "12");
    }

    #[test]
    fn usage_summary_table_truncates_runaway_provider_names() {
        // A provider name longer than MAX_PROVIDER_W should be ellipsized so
        // the column width stays bounded.
        let absurd = "a".repeat(80);
        let data = serde_json::json!({
            "mode": "summary",
            "period": "today",
            "summary": [{
                "provider": absurd,
                "model": "m",
                "call_count": 1,
                "total_input": 0,
                "total_output": 0,
                "total_cache_read": 0,
                "total_cache_write": 0,
                "total_cost": 0.01,
            }],
        });
        let mut buf: Vec<u8> = Vec::new();
        write_usage_summary_table(&mut buf, &data).expect("write");
        let rendered = String::from_utf8(buf).expect("utf8");
        assert!(
            rendered.contains('\u{2026}'),
            "expected ellipsis for runaway provider"
        );
        for line in rendered.lines().skip(2) {
            // Sanity: no line should exceed a reasonable cap width.
            assert!(line.chars().count() <= 120, "line too wide: {line:?}");
        }
    }

    #[test]
    fn density_to_block_ranges() {
        assert_eq!(density_to_block(0.0), '\u{2591}'); // below threshold
        assert_eq!(density_to_block(0.04), '\u{2591}'); // below threshold
        assert_eq!(density_to_block(0.06), '\u{2581}'); // 0.06 * 7 = 0.42 -> round 0 -> first block
        assert_eq!(density_to_block(0.5), '\u{2585}'); // 0.5 * 7 = 3.5 -> round 4 -> fifth block
        assert_eq!(density_to_block(1.0), '\u{2588}'); // 1.0 * 7 = 7.0 -> index 7 -> full block
    }
}
