//! Markdown processing and conversion utilities.
//!
//! This module provides convenience functions for working with Markdown content.
//! It uses `pulldown-cmark` for robust Markdown parsing and HTML conversion.

use crate::error::{WebError, WebResult};
use pulldown_cmark::{html, Parser, Event, Tag};

/// Struct for building Markdown content.
#[derive(Debug, Clone, Default)]
pub struct MarkdownBuilder {
    content: String,
}

impl MarkdownBuilder {
    /// Create a new Markdown builder.
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a heading to the Markdown content.
    ///
    /// # Arguments
    ///
    /// * `level` - The heading level (1-6)
    /// * `text` - The heading text
    ///
    /// # Example
    ///
    /// ```ignore
    /// let md = MarkdownBuilder::new()
    ///     .heading(1, "Title")?
    ///     .build();
    /// ```
    pub fn heading(mut self, level: usize, text: &str) -> WebResult<Self> {
        if !(1..=6).contains(&level) {
            return Err(WebError::markdown(
                "Heading level must be between 1 and 6",
            ));
        }
        let hashes = "#".repeat(level);
        self.content.push_str(&format!("{} {}\n\n", hashes, text));
        Ok(self)
    }

    /// Add a paragraph to the Markdown content.
    pub fn paragraph(mut self, text: &str) -> Self {
        self.content.push_str(&format!("{}\n\n", text));
        self
    }

    /// Add bold text.
    pub fn bold(mut self, text: &str) -> Self {
        self.content.push_str(&format!("**{}**", text));
        self
    }

    /// Add italic text.
    pub fn italic(mut self, text: &str) -> Self {
        self.content.push_str(&format!("*{}*", text));
        self
    }

    /// Add a code block.
    ///
    /// # Arguments
    ///
    /// * `language` - The programming language for syntax highlighting
    /// * `code` - The code content
    pub fn code_block(mut self, language: &str, code: &str) -> Self {
        self.content
            .push_str(&format!("```{}\n{}\n```\n\n", language, code));
        self
    }

    /// Add an unordered list.
    pub fn list(mut self, items: &[&str]) -> Self {
        for item in items {
            self.content.push_str(&format!("- {}\n", item));
        }
        self.content.push('\n');
        self
    }

    /// Add an ordered list.
    pub fn ordered_list(mut self, items: &[&str]) -> Self {
        for (i, item) in items.iter().enumerate() {
            self.content.push_str(&format!("{}. {}\n", i + 1, item));
        }
        self.content.push('\n');
        self
    }

    /// Add a link.
    pub fn link(mut self, text: &str, url: &str) -> Self {
        self.content.push_str(&format!("[{}]({})", text, url));
        self
    }

    /// Add a blockquote.
    pub fn blockquote(mut self, text: &str) -> Self {
        self.content.push_str(&format!("> {}\n\n", text));
        self
    }

    /// Add raw Markdown content.
    pub fn add_raw(mut self, content: &str) -> Self {
        self.content.push_str(content);
        self
    }

    /// Build and return the Markdown content.
    pub fn build(self) -> String {
        self.content
    }
}

/// Convert Markdown to HTML using pulldown-cmark parser.
///
/// This function uses the robust `pulldown-cmark` library to parse
/// Markdown and convert it to proper HTML.
///
/// # Arguments
///
/// * `markdown` - The Markdown content
///
/// # Returns
///
/// HTML representation of the Markdown
pub fn markdown_to_html(markdown: &str) -> String {
    let parser = Parser::new(markdown);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    html_output
}

/// Extract headings from Markdown.
///
/// Returns a vector of (level, text) tuples by parsing
/// the Markdown structure.
pub fn extract_headings(markdown: &str) -> Vec<(usize, String)> {
    let parser = Parser::new(markdown);
    let mut headings = Vec::new();
    let mut current_heading_level: usize = 0;
    let mut current_text = String::new();

    for event in parser {
        match event {
            Event::Start(Tag::Heading {
                level,
                id: _,
                classes: _,
                attrs: _,
            }) => {
                current_heading_level = level as usize;
            }
            Event::Text(text) => {
                if current_heading_level > 0 {
                    current_text.push_str(&text);
                }
            }
            Event::End(_) => {
                if current_heading_level > 0 {
                    headings.push((current_heading_level, current_text.clone()));
                    current_heading_level = 0;
                    current_text.clear();
                }
            }
            _ => {}
        }
    }

    headings
}

/// Count words in Markdown content.
///
/// Counts words by parsing the Markdown and extracting text events.
pub fn count_words(markdown: &str) -> usize {
    let parser = Parser::new(markdown);
    let mut word_count = 0;

    for event in parser {
        if let Event::Text(text) = event {
            word_count += text.split_whitespace().count();
        }
    }

    word_count
}

/// Count lines in Markdown content.
pub fn count_lines(markdown: &str) -> usize {
    markdown.lines().count()
}

/// Convert Markdown to a complete HTML page with Bootstrap styling.
///
/// This function creates a fully-formed HTML5 document with:
/// - Bootstrap 5.3.8 CSS and JavaScript
/// - Proper semantic HTML structure
/// - Mobile-responsive meta tags
/// - UTF-8 charset
///
/// # Arguments
///
/// * `markdown` - The Markdown content
/// * `title` - The HTML page title
///
/// # Returns
///
/// Complete HTML5 document with Bootstrap-styled Markdown content
///
/// # Example
///
/// ```ignore
/// let html = markdown_to_html_page("# Hello\n\nWorld", "My Page");
/// // Returns complete HTML document ready to save or serve
/// ```
pub fn markdown_to_html_page(markdown: &str, title: &str) -> String {
    let body_html = markdown_to_html(markdown);
    
    let html = format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
    <style>
        body {{
            padding-top: 2rem;
            padding-bottom: 2rem;
            color: #555;
        }}
        .container {{
            max-width: 900px;
        }}
        h1 {{
            margin-top: 2rem;
            margin-bottom: 1rem;
            color: #212529;
            border-bottom: 2px solid #0d6efd;
            padding-bottom: 0.5rem;
        }}
        h2 {{
            margin-top: 1.5rem;
            margin-bottom: 0.75rem;
            color: #495057;
        }}
        h3, h4, h5, h6 {{
            margin-top: 1rem;
            margin-bottom: 0.5rem;
            color: #6c757d;
        }}
        p {{
            margin-bottom: 1rem;
            line-height: 1.6;
        }}
        code {{
            background-color: #f8f9fa;
            padding: 0.2rem 0.4rem;
            border-radius: 0.25rem;
            color: #d63384;
        }}
        pre {{
            background-color: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 0.5rem;
            padding: 1rem;
            overflow-x: auto;
        }}
        pre code {{
            color: inherit;
            background-color: transparent;
            padding: 0;
        }}
        blockquote {{
            border-left: 4px solid #0d6efd;
            padding-left: 1rem;
            margin-left: 0;
            color: #6c757d;
            font-style: italic;
        }}
        ul, ol {{
            margin-bottom: 1rem;
            padding-left: 2rem;
        }}
        li {{
            margin-bottom: 0.5rem;
        }}
        a {{
            color: #0d6efd;
            text-decoration: none;
        }}
        a:hover {{
            color: #0b5ed7;
            text-decoration: underline;
        }}
        table {{
            margin-bottom: 1rem;
            border-collapse: collapse;
        }}
        th, td {{
            border: 1px solid #dee2e6;
            padding: 0.75rem;
            text-align: left;
        }}
        th {{
            background-color: #f8f9fa;
            font-weight: bold;
        }}
        hr {{
            margin: 2rem 0;
            border: 0;
            border-top: 1px solid #dee2e6;
        }}
        img {{
            max-width: 100%;
            height: auto;
            border-radius: 0.5rem;
        }}
    </style>
</head>
<body>
    <div class="container">
        {}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
</body>
</html>"#,
        escape_html_title(title),
        body_html
    );
    
    html
}

/// Convert Markdown to HTML with Bootstrap container styling.
///
/// Similar to `markdown_to_html_page` but returns just the body content
/// wrapped in a Bootstrap container div.
///
/// Useful for embedding in existing HTML pages.
///
/// # Arguments
///
/// * `markdown` - The Markdown content
///
/// # Returns
///
/// HTML fragment with Bootstrap container
pub fn markdown_to_html_bootstrap(markdown: &str) -> String {
    let body_html = markdown_to_html(markdown);
    format!(
        "<div class=\"container mt-5 mb-5\">\n{}\n</div>",
        body_html
    )
}

/// Generate a table of contents from Markdown headings with Bootstrap styling.
///
/// Creates a Bootstrap-styled navigation list from headings found in the Markdown.
///
/// # Arguments
///
/// * `markdown` - The Markdown content
///
/// # Returns
///
/// Bootstrap-styled HTML table of contents
pub fn markdown_to_toc_bootstrap(markdown: &str) -> String {
    let headings = extract_headings(markdown);
    
    if headings.is_empty() {
        return String::new();
    }
    
    let mut toc = String::from("<nav class=\"navbar navbar-light bg-light\">\n");
    toc.push_str("  <div class=\"container-fluid\">\n");
    toc.push_str("    <span class=\"navbar-brand mb-0 h1\">Contents</span>\n");
    toc.push_str("    <ul class=\"nav flex-column\">\n");
    
    for (level, text) in headings {
        // Calculate indentation based on heading level (level 1 has no indent, level 2+ gets nested)
        let indent_str = if level > 1 {
            "  ".repeat(level - 1)
        } else {
            String::new()
        };
        let slug = text_to_slug(&text);
        toc.push_str(&format!(
            "{}      <li class=\"nav-item\"><a class=\"nav-link\" href=\"#{}\">{}</a></li>\n",
            indent_str,
            slug,
            text
        ));
    }
    
    toc.push_str("    </ul>\n");
    toc.push_str("  </div>\n");
    toc.push_str("</nav>\n");
    
    toc
}

/// Escape HTML special characters in text.
///
/// Prevents XSS attacks by escaping dangerous HTML characters.
fn escape_html(text: &str) -> String {
    text.chars()
        .map(|c| match c {
            '&' => "&amp;".to_string(),
            '<' => "&lt;".to_string(),
            '>' => "&gt;".to_string(),
            '"' => "&quot;".to_string(),
            '\'' => "&#39;".to_string(),
            c => c.to_string(),
        })
        .collect()
}

/// Escape HTML special characters in title text.
///
/// This is a convenience wrapper around escape_html for backward compatibility.
fn escape_html_title(text: &str) -> String {
    escape_html(text)
}

/// Convert text to URL-friendly slug.
fn text_to_slug(text: &str) -> String {
    text.to_lowercase()
        .chars()
        .map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                c
            } else if c.is_whitespace() {
                '-'
            } else {
                '_'
            }
        })
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<_>>()
        .join("-")
}


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

    #[test]
    fn test_markdown_builder_heading() {
        let md = MarkdownBuilder::new()
            .heading(1, "Title")
            .unwrap()
            .build();
        assert!(md.contains("# Title"));
    }

    #[test]
    fn test_markdown_builder_invalid_heading() {
        let result = MarkdownBuilder::new().heading(7, "Title");
        assert!(result.is_err());
    }

    #[test]
    fn test_markdown_builder_paragraph() {
        let md = MarkdownBuilder::new().paragraph("Hello World").build();
        assert!(md.contains("Hello World"));
    }

    #[test]
    fn test_count_words() {
        let count = count_words("Hello world this is markdown");
        assert_eq!(count, 5);
    }

    #[test]
    fn test_count_lines() {
        let count = count_lines("Line 1\nLine 2\nLine 3");
        assert_eq!(count, 3);
    }

    #[test]
    fn test_extract_headings() {
        let md = "# Title\nSome text\n## Subtitle\nMore text";
        let headings = extract_headings(md);
        assert_eq!(headings.len(), 2);
        assert_eq!(headings[0].0, 1);
        assert_eq!(headings[0].1, "Title");
    }

    #[test]
    fn test_markdown_builder_list() {
        let md = MarkdownBuilder::new()
            .list(&["Item 1", "Item 2"])
            .build();
        assert!(md.contains("- Item 1"));
        assert!(md.contains("- Item 2"));
    }

    #[test]
    fn test_markdown_builder_code_block() {
        let md = MarkdownBuilder::new()
            .code_block("rust", "fn main() {}")
            .build();
        assert!(md.contains("```rust"));
        assert!(md.contains("fn main() {}"));
    }

    #[test]
    fn test_markdown_to_html_page() {
        let html = markdown_to_html_page("# Hello\n\nWorld", "Test Page");
        assert!(html.contains("<!DOCTYPE html>"));
        assert!(html.contains("<title>Test Page</title>"));
        assert!(html.contains("bootstrap@5.3.8"));
        assert!(html.contains("<h1>Hello</h1>"));
        assert!(html.contains("<p>World</p>"));
    }

    #[test]
    fn test_markdown_to_html_bootstrap() {
        let html = markdown_to_html_bootstrap("# Test\n\nContent");
        assert!(html.contains("container"));
        assert!(html.contains("<h1>Test</h1>"));
        assert!(html.contains("<p>Content</p>"));
        assert!(!html.contains("<!DOCTYPE"));
    }

    #[test]
    fn test_markdown_to_toc_bootstrap() {
        let md = "# Chapter 1\n\nText\n## Section 1.1\n\nMore text\n# Chapter 2";
        let toc = markdown_to_toc_bootstrap(md);
        assert!(toc.contains("navbar"));
        assert!(toc.contains("Contents"));
        assert!(toc.contains("Chapter 1"));
        assert!(toc.contains("Section 1.1"));
        assert!(toc.contains("Chapter 2"));
    }

    #[test]
    fn test_text_to_slug() {
        assert_eq!(text_to_slug("Hello World"), "hello-world");
        assert_eq!(text_to_slug("Test  Multiple  Spaces"), "test-multiple-spaces");
        assert_eq!(text_to_slug("CamelCase"), "camelcase");
    }

    #[test]
    fn test_markdown_to_html_page_title_escaping() {
        let html = markdown_to_html_page("# Test", "Title with <script>");
        assert!(html.contains("&lt;script&gt;"));
        assert!(!html.contains("<script>"));
    }

    #[test]
    fn test_markdown_to_toc_bootstrap_with_special_chars() {
        let md = "# Chapter & Test\n\nText\n## Section <2>\n\nMore text";
        let toc = markdown_to_toc_bootstrap(md);
        assert!(toc.contains("Contents"));
        assert!(toc.contains("Chapter & Test"));
        assert!(toc.contains("Section <2>"));
    }

    #[test]
    fn test_markdown_to_toc_bootstrap_deep_nesting() {
        let md = "# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6";
        let toc = markdown_to_toc_bootstrap(md);
        assert!(toc.contains("H1"));
        assert!(toc.contains("H2"));
        assert!(toc.contains("H3"));
        assert!(toc.contains("H4"));
        assert!(toc.contains("H5"));
        assert!(toc.contains("H6"));
    }

    #[test]
    fn test_escape_html() {
        assert_eq!(escape_html("Hello & <World>"), "Hello &amp; &lt;World&gt;");
        assert_eq!(escape_html("Quote: \"test\""), "Quote: &quot;test&quot;");
        assert_eq!(escape_html("Apostrophe: 'test'"), "Apostrophe: &#39;test&#39;");
    }
}
