//! Text Processing and Slug Generation Utilities
//!
//! Provides functions to convert strings to URL-safe slugs and normalize text.
//! All functions follow consistent patterns for URL safety and
//! internationalization.

/// Normalize a string to a URL-safe slug
///
/// Rules:
/// - Convert to lowercase
/// - Replace spaces with dashes
/// - Remove accents and diacritics using simple ASCII conversion
/// - Remove non-alphanumeric characters except dashes
/// - Collapse multiple consecutive dashes
/// - Trim leading/trailing dashes
pub fn to_slug(input: &str) -> String {
    // Simple ASCII conversion for diacritics (no external dependencies)
    let no_accents = remove_diacritics(input);

    no_accents
        .to_lowercase()
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() {
                c
            } else if c.is_whitespace() || c == '_' || c == '/' {
                '-'
            } else if c.is_ascii_punctuation() && c != '-' {
                // Remove other punctuation
                '\0' // Will be filtered out
            } else {
                c
            }
        })
        .filter(|&c| c != '\0')
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<&str>>()
        .join("-")
}

/// Remove common diacritics and accents using simple ASCII replacements
fn remove_diacritics(input: &str) -> String {
    // Simple replacement table for common accented characters
    input
        .chars()
        .map(|c| match c {
            'à' | 'á' | 'â' | 'ã' | 'ä' | 'å' | 'À' | 'Á' | 'Â' | 'Ã' | 'Ä' | 'Å' => {
                'a'
            }
            'è' | 'é' | 'ê' | 'ë' | 'È' | 'É' | 'Ê' | 'Ë' => 'e',
            'ì' | 'í' | 'î' | 'ï' | 'Ì' | 'Í' | 'Î' | 'Ï' => 'i',
            'ò' | 'ó' | 'ô' | 'õ' | 'ö' | 'ø' | 'Ò' | 'Ó' | 'Ô' | 'Õ' | 'Ö' | 'Ø' => {
                'o'
            }
            'ù' | 'ú' | 'û' | 'ü' | 'Ù' | 'Ú' | 'Û' | 'Ü' => 'u',
            'ý' | 'ÿ' | 'Ý' | 'Ÿ' => 'y',
            'ñ' | 'Ñ' => 'n',
            'ç' | 'Ç' => 'c',
            'ß' => 's',
            _ => c,
        })
        .collect()
}

/// Normalize a category name to slug format
pub fn normalize_category(category: &str) -> String {
    to_slug(category)
}

/// Normalize a tag name to slug format
pub fn normalize_tag(tag: &str) -> String {
    to_slug(tag)
}

/// Generate a URL slug from a title
/// More aggressive normalization for titles that will be used in URLs
pub fn title_to_slug(title: &str) -> String {
    // Remove common words that don't add SEO value
    let stop_words = [
        "the",
        "a",
        "an",
        "and",
        "or",
        "but",
        "in",
        "on",
        "at",
        "to",
        "for",
        "of",
        "with",
        "by",
        "from",
        "up",
        "about",
        "into",
        "through",
        "during",
        "before",
        "after",
        "above",
        "below",
        "between",
        "among",
        "throughout",
        "within",
        "without",
        "against",
        "across",
        "behind",
        "beyond",
        "except",
        "under",
        "over",
    ];

    let base_slug = to_slug(title);

    // Don't remove stop words if the result would be too short
    let words: Vec<&str> = base_slug.split('-').collect();
    if words.len() <= 3 {
        return base_slug;
    }

    words
        .into_iter()
        .filter(|word| !stop_words.contains(word) || word.len() > 3)
        .collect::<Vec<&str>>()
        .join("-")
}

/// Truncate text to a maximum length, preserving word boundaries
pub fn truncate_text(text: &str, max_length: usize) -> String {
    if text.len() <= max_length {
        return text.to_string();
    }

    // Find the last space before the max length
    if let Some(pos) = text[..max_length].rfind(' ') {
        format!("{}...", &text[..pos])
    } else {
        format!("{}...", &text[..max_length.saturating_sub(3)])
    }
}

/// Extract a summary from text content (first paragraph or sentence)
pub fn extract_summary(text: &str, max_length: usize) -> String {
    // Try to get first paragraph
    let first_paragraph = text.split("\n\n").next().unwrap_or(text);

    // If first paragraph is too long, try first sentence
    if first_paragraph.len() > max_length {
        let first_sentence = first_paragraph
            .split(". ")
            .next()
            .unwrap_or(first_paragraph);
        truncate_text(first_sentence, max_length)
    } else {
        truncate_text(first_paragraph, max_length)
    }
}

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

    #[test]
    fn test_to_slug() {
        assert_eq!(to_slug("Rust Programming"), "rust-programming");
        assert_eq!(to_slug("Docker Multi-Stage"), "docker-multi-stage");
        assert_eq!(to_slug("CI/CD Pipeline"), "ci-cd-pipeline");
        assert_eq!(to_slug("Async Error Handling"), "async-error-handling");
        assert_eq!(to_slug("Web3 & Blockchain"), "web3-blockchain");
        assert_eq!(to_slug("  Multiple   Spaces  "), "multiple-spaces");
        assert_eq!(to_slug("Testing__123"), "testing-123");
    }

    #[test]
    fn test_diacritics_removal() {
        assert_eq!(to_slug("Configuración Avanzada"), "configuracion-avanzada");
        assert_eq!(to_slug("Programación Rust"), "programacion-rust");
        assert_eq!(to_slug("Español"), "espanol");
        assert_eq!(to_slug("naïve résumé"), "naive-resume");
    }

    #[test]
    fn test_normalize_category() {
        assert_eq!(normalize_category("Rust Programming"), "rust-programming");
        assert_eq!(normalize_category("Docker"), "docker");
        assert_eq!(normalize_category("CI/CD"), "ci-cd");
    }

    #[test]
    fn test_title_to_slug() {
        assert_eq!(
            title_to_slug("Modern Rust Web Development in 2024"),
            "modern-rust-web-development-2024"
        );
        assert_eq!(title_to_slug("A Guide to Docker"), "guide-docker");
        // Short titles should keep all words
        assert_eq!(title_to_slug("The Test"), "the-test");
    }

    #[test]
    fn test_truncate_text() {
        assert_eq!(truncate_text("Short text", 20), "Short text");
        assert_eq!(
            truncate_text("This is a very long text that should be truncated", 20),
            "This is a very long..."
        );
        assert_eq!(truncate_text("NoSpacesHere", 8), "NoSpa...");
    }

    #[test]
    fn test_extract_summary() {
        let text = "First paragraph here.\n\nSecond paragraph.\n\nThird paragraph.";
        assert_eq!(extract_summary(text, 50), "First paragraph here.");

        let long_paragraph = "This is a very long first paragraph that goes on and on. It \
                              contains multiple sentences. This should be truncated.";
        assert_eq!(
            extract_summary(long_paragraph, 50),
            "This is a very long first paragraph that goes..."
        );
    }
}
