//! Layer 4: MCP tool description compression.
//!
//! Compresses lean-ctx's 56+ tool descriptions to reduce the token overhead
//! of the initial `tools/list` response. Two modes:
//! - Terse: Natural-language descriptions shortened via abbreviations
//! - Lazy: Only tool name + 1-line summary, full description on-demand

use super::dictionaries::{self, DictLevel};
use crate::core::config::CompressionLevel;

/// Compression mode for tool descriptions.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DescriptionMode {
    Full,
    Terse,
    Lazy,
}

impl DescriptionMode {
    pub fn from_compression_level(level: &CompressionLevel) -> Self {
        match level {
            CompressionLevel::Off | CompressionLevel::Lite => Self::Full,
            CompressionLevel::Standard => Self::Terse,
            CompressionLevel::Max => Self::Lazy,
        }
    }
}

/// Compresses a single tool description according to the mode.
pub fn compress_description(name: &str, description: &str, mode: DescriptionMode) -> String {
    match mode {
        DescriptionMode::Full => description.to_string(),
        DescriptionMode::Terse => terse_description(description),
        DescriptionMode::Lazy => lazy_description(name, description),
    }
}

fn terse_description(desc: &str) -> String {
    let abbreviated = dictionaries::apply_dictionaries(desc, DictLevel::General);

    let mut lines: Vec<&str> = abbreviated.lines().collect();

    lines.retain(|line| {
        let trimmed = line.trim();
        !trimmed.is_empty()
            && !trimmed.starts_with("Example")
            && !trimmed.starts_with("Note:")
            && !trimmed.starts_with("See also")
    });

    if lines.len() > 3 {
        lines.truncate(3);
    }

    lines.join("\n")
}

fn lazy_description(name: &str, desc: &str) -> String {
    let first_line = desc.lines().next().unwrap_or(name);
    let summary = if first_line.len() > 80 {
        format!("{}…", &first_line[..77])
    } else {
        first_line.to_string()
    };
    format!("{summary} (use ctx_discover_tools for full docs)")
}

/// Estimates token savings from compressing all tool descriptions.
pub fn estimate_savings(descriptions: &[(&str, &str)], mode: DescriptionMode) -> (u32, u32) {
    let mut total_before = 0u32;
    let mut total_after = 0u32;

    for (name, desc) in descriptions {
        let before = super::counter::count(desc);
        let compressed = compress_description(name, desc, mode);
        let after = super::counter::count(&compressed);
        total_before += before;
        total_after += after;
    }

    (total_before, total_after)
}

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

    #[test]
    fn full_mode_unchanged() {
        let desc = "Read a file from disk with caching.";
        assert_eq!(
            compress_description("ctx_read", desc, DescriptionMode::Full),
            desc
        );
    }

    #[test]
    fn terse_mode_abbreviates() {
        let desc = "Read a configuration file from the directory.";
        let result = compress_description("ctx_read", desc, DescriptionMode::Terse);
        assert!(
            result.contains("cfg") || result.contains("dir"),
            "should abbreviate: {result}"
        );
    }

    #[test]
    fn lazy_mode_short() {
        let desc = "Read a file from disk with intelligent caching and compression modes.\nSupports 10 different read modes for optimal token efficiency.";
        let result = compress_description("ctx_read", desc, DescriptionMode::Lazy);
        assert!(
            result.contains("ctx_discover_tools"),
            "lazy should reference ctx_discover_tools"
        );
        assert!(result.lines().count() == 1, "lazy should be 1 line");
    }

    #[test]
    fn mode_from_compression_level() {
        assert_eq!(
            DescriptionMode::from_compression_level(&CompressionLevel::Off),
            DescriptionMode::Full
        );
        assert_eq!(
            DescriptionMode::from_compression_level(&CompressionLevel::Standard),
            DescriptionMode::Terse
        );
        assert_eq!(
            DescriptionMode::from_compression_level(&CompressionLevel::Max),
            DescriptionMode::Lazy
        );
    }

    #[test]
    fn estimate_savings_returns_values() {
        let descs = vec![
            (
                "ctx_read",
                "Read a configuration file from the directory with caching.",
            ),
            (
                "ctx_shell",
                "Execute a shell command with pattern compression.",
            ),
        ];
        let (before, after) = estimate_savings(&descs, DescriptionMode::Terse);
        assert!(before > 0);
        assert!(after > 0);
        assert!(after <= before);
    }
}
