//! Markdown to Slack mrkdwn conversion.

/// Convert Markdown text to Slack mrkdwn format.
///
/// Slack uses its own "mrkdwn" syntax which differs from standard Markdown.
/// This function converts common Markdown constructs so messages render
/// nicely in Slack instead of showing raw Markdown syntax.
pub fn markdown_to_slack(text: &str) -> String {
    use crate::chat::util::normalize_line_breaks;
    use regex::Regex;
    use std::sync::LazyLock;

    let normalized = normalize_line_breaks(text);
    let text = normalized.as_str();

    // Regexes are compiled once and reused across calls.
    static RE_FENCED_BLOCK: LazyLock<Regex> =
        LazyLock::new(|| Regex::new(r"(?ms)^```.*?\n(.*?)^```").unwrap());
    static RE_HEADER: LazyLock<Regex> =
        LazyLock::new(|| Regex::new(r"(?m)^#{1,6}\s+(.+)$").unwrap());
    static RE_BOLD_ITALIC: LazyLock<Regex> =
        LazyLock::new(|| Regex::new(r"\*\*\*(.+?)\*\*\*").unwrap());
    static RE_BOLD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\*\*(.+?)\*\*").unwrap());
    static RE_STRIKETHROUGH: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"~~(.+?)~~").unwrap());
    static RE_LINK: LazyLock<Regex> =
        LazyLock::new(|| Regex::new(r"\[([^\]]+)\]\(([^)]+)\)").unwrap());

    // 1. Protect fenced code blocks by replacing them with placeholders.
    let mut code_blocks: Vec<String> = Vec::new();
    let protected = RE_FENCED_BLOCK.replace_all(text, |caps: &regex::Captures| {
        let idx = code_blocks.len();
        code_blocks.push(caps[0].to_string());
        format!("\x00CODEBLOCK{idx}\x00")
    });
    let mut out = protected.into_owned();

    // 2. Headers → bold text.
    out = RE_HEADER.replace_all(&out, "*$1*").into_owned();

    // 3. Bold+italic (***text***) → bold italic (*_text_*).
    out = RE_BOLD_ITALIC.replace_all(&out, "*_${1}_*").into_owned();

    // 4. Bold (**text**) → Slack bold (*text*).
    out = RE_BOLD.replace_all(&out, "*$1*").into_owned();

    // 5. Strikethrough (~~text~~) → Slack strikethrough (~text~).
    out = RE_STRIKETHROUGH.replace_all(&out, "~$1~").into_owned();

    // 6. Links [text](url) → Slack mrkdwn format <url|text>.
    out = RE_LINK.replace_all(&out, "<$2|$1>").into_owned();

    // 7. Restore code blocks.
    for (idx, block) in code_blocks.iter().enumerate() {
        out = out.replace(&format!("\x00CODEBLOCK{idx}\x00"), block);
    }

    out
}

// ── Tests ───────────────────────────────────────────────────────────────

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

    #[test]
    fn slack_headers_become_bold() {
        assert_eq!(markdown_to_slack("# Title"), "*Title*");
        assert_eq!(markdown_to_slack("## Subtitle"), "*Subtitle*");
        assert_eq!(markdown_to_slack("### Section"), "*Section*");
        assert_eq!(markdown_to_slack("###### Deep"), "*Deep*");
    }

    #[test]
    fn slack_bold_converted() {
        assert_eq!(markdown_to_slack("**bold text**"), "*bold text*");
    }

    #[test]
    fn slack_bold_italic_converted() {
        assert_eq!(markdown_to_slack("***emphasis***"), "*_emphasis_*");
    }

    #[test]
    fn slack_strikethrough_converted() {
        assert_eq!(markdown_to_slack("~~removed~~"), "~removed~");
    }

    #[test]
    fn slack_links_converted_to_mrkdwn() {
        assert_eq!(
            markdown_to_slack("[click here](https://example.com)"),
            "<https://example.com|click here>"
        );
    }

    #[test]
    fn slack_inline_code_preserved() {
        assert_eq!(markdown_to_slack("use `foo()` here"), "use `foo()` here");
    }

    #[test]
    fn slack_fenced_code_block_preserved() {
        let input = "```rust\nlet x = 1;\n```";
        let output = markdown_to_slack(input);
        assert!(
            output.contains("let x = 1;"),
            "code block content must be preserved"
        );
        assert!(
            output.contains("```"),
            "fenced code delimiters must be preserved"
        );
    }

    #[test]
    fn slack_code_block_content_not_transformed() {
        let input = "```\n**not bold** # not header\n```";
        let output = markdown_to_slack(input);
        assert!(
            output.contains("**not bold**"),
            "markdown inside code blocks must not be transformed"
        );
    }

    #[test]
    fn slack_plain_text_unchanged() {
        let plain = "Hello, this is a plain message with no formatting.";
        assert_eq!(markdown_to_slack(plain), plain);
    }

    #[test]
    fn slack_empty_string_unchanged() {
        assert_eq!(markdown_to_slack(""), "");
    }
}
