//! Comprehensive regression tests for MkDocs flavor extension support
//!
//! These tests verify that rumdl correctly handles the syntax from:
//! - Python-Markdown extensions
//! - PyMdown Extensions
//! - mkdocstrings
//!
//! Test categories:
//! 1. Basic recognition - Extensions don't produce false positives
//! 2. Edge cases - Malformed syntax, boundary conditions, unusual nesting
//! 3. Negative tests - Violations ARE still detected in extension contexts
//! 4. Rule interactions - Specific rules that might conflict with extensions
//! 5. Fix preservation - Auto-fix doesn't break extension syntax
//! 6. Stress tests - Complex documents with multiple nested extensions

use rumdl_lib::config::{Config, MarkdownFlavor};
use rumdl_lib::lint;
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::rules::*;

fn create_mkdocs_config() -> Config {
    let mut config = Config::default();
    config.global.flavor = MarkdownFlavor::MkDocs;
    config
}

fn lint_mkdocs(content: &str) -> Vec<rumdl_lib::rule::LintWarning> {
    let config = create_mkdocs_config();
    let rules = filter_rules(&all_rules(&config), &config.global);
    lint(content, &rules, false, MarkdownFlavor::MkDocs, None, None).unwrap()
}

fn lint_standard(content: &str) -> Vec<rumdl_lib::rule::LintWarning> {
    let config = Config::default();
    let rules = filter_rules(&all_rules(&config), &config.global);
    lint(content, &rules, false, MarkdownFlavor::Standard, None, None).unwrap()
}

// =============================================================================
// PART 1: BASIC EXTENSION RECOGNITION
// These tests verify that each extension syntax is recognized without false positives
// =============================================================================

mod basic_recognition {
    use super::*;

    #[test]
    fn test_admonitions_all_types() {
        // Test all admonition types recognized by Material for MkDocs
        let content = r#"# Admonitions

!!! note
    Note content.

!!! abstract
    Abstract content.

!!! info
    Info content.

!!! tip
    Tip content.

!!! success
    Success content.

!!! question
    Question content.

!!! warning
    Warning content.

!!! failure
    Failure content.

!!! danger
    Danger content.

!!! bug
    Bug content.

!!! example
    Example content.

!!! quote
    Quote content.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "All admonition types should be recognized: {warnings:?}"
        );
    }

    #[test]
    fn test_collapsible_admonitions() {
        let content = r#"# Collapsible

??? note "Collapsed"
    Hidden by default.

???+ warning "Expanded"
    Visible by default.

??? abstract
    No title, collapsed.

???+ tip
    No title, expanded.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Collapsible admonitions should work: {warnings:?}");
    }

    #[test]
    fn test_content_tabs_variations() {
        let content = r#"# Tabs

=== "Tab 1"
    Content 1.

=== "Tab 2"
    Content 2.

=== "Tab with 'quotes'"
    Quoted title.

=== "Tab with \"double quotes\""
    Double quoted.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Content tabs should work: {warnings:?}");
    }

    #[test]
    fn test_mkdocstrings_variations() {
        let content = r#"# API Docs

::: module
    handler: python

::: package.module.Class
    options:
        show_source: true
        heading_level: 2

::: function

:::module.without.space
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "mkdocstrings blocks should work: {warnings:?}");
    }

    #[test]
    fn test_pymdown_inline_extensions() {
        let content = r#"# Inline Extensions

Keys: ++ctrl+c++ and ++ctrl+alt+del++

Caret: ^superscript^ and ^^insert^^

Tilde: ~subscript~ and ~~strikethrough~~

Mark: ==highlighted==

Critic: {++added++} {--deleted--} {~~old~>new~~} {==marked==} {>>comment<<}
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "PyMdown inline extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_snippets_syntax() {
        let content = r#"# Snippets

--8<-- "file.md"

--8<-- "path/to/file.py"

--8<-- "file.md:10:20"

;--8<--
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Snippets should work: {warnings:?}");
    }

    #[test]
    fn test_abbreviations() {
        let content = r#"# Abbreviations

The HTML and CSS specifications.

*[HTML]: Hypertext Markup Language
*[CSS]: Cascading Style Sheets
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Abbreviations should work: {warnings:?}");
    }

    #[test]
    fn test_definition_lists() {
        let content = r#"# Definitions

Term 1
:   Definition 1

Term 2
:   Definition 2a
:   Definition 2b

Term 3
:   Multi-line
    definition.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Definition lists should work: {warnings:?}");
    }

    #[test]
    fn test_footnotes() {
        let content = r#"# Footnotes

Text with footnote.[^1] Another.[^named]

[^1]: Simple footnote.

[^named]:
    Multi-paragraph
    footnote content.

    Second paragraph.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Footnotes should work: {warnings:?}");
    }

    #[test]
    fn test_attribute_lists() {
        let content = r#"# Attributes

## Heading {#custom-id}

## Another {.class-name}

## Multiple {#id .class data-value="test"}

Paragraph.
{.centered}
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Attribute lists should work: {warnings:?}");
    }

    #[test]
    fn test_superfences_with_attributes() {
        let content = r#"# Code Blocks

```python title="example.py"
print("hello")
```

```javascript linenums="1" hl_lines="2-3"
const a = 1;
const b = 2;
const c = 3;
```

```mermaid
graph TD
    A --> B
```
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Superfences should work: {warnings:?}");
    }

    #[test]
    fn test_math_blocks() {
        let content = r#"# Math

Inline: $E = mc^2$ and $\int_0^1 x dx$

Block:

$$
\frac{n!}{k!(n-k)!} = \binom{n}{k}
$$

$$
\sum_{i=1}^{n} i = \frac{n(n+1)}{2}
$$
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Math blocks should work: {warnings:?}");
    }

    #[test]
    fn test_emoji_shortcodes() {
        let content = r#"# Emoji

Material: :material-check: :material-close: :material-github:

FontAwesome: :fontawesome-brands-github: :fontawesome-solid-heart:

Octicons: :octicons-mark-github-16: :octicons-alert-24:

Twemoji: :smile: :heart: :rocket:
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Emoji shortcodes should work: {warnings:?}");
    }

    #[test]
    fn test_inline_code_highlighting() {
        let content = r#"# InlineHilite

Use `#!python print("hello")` for Python.

Or `#!javascript console.log("hi")` for JS.

Generic: `#!bash echo $PATH`
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "InlineHilite should work: {warnings:?}");
    }

    #[test]
    fn test_md_in_html() {
        let content = r#"# MD in HTML

<div markdown="1">

**Bold** and *italic* work here.

- List item
- Another

</div>

<div markdown="block">
More content.
</div>
"#;
        let warnings = lint_mkdocs(content);
        // Should not flag the div tags in MkDocs flavor
        let md033 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD033"))
            .count();
        assert_eq!(md033, 0, "md_in_html should not trigger MD033");
    }

    #[test]
    fn test_toc_marker() {
        let content = r#"# Document

[TOC]

## Section 1

## Section 2
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "TOC marker should work: {warnings:?}");
    }

    #[test]
    fn test_tasklists() {
        let content = r#"# Tasks

- [x] Completed
- [ ] Incomplete
- [X] Also completed
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Tasklists should work: {warnings:?}");
    }

    #[test]
    fn test_smartsymbols() {
        let content = r#"# Symbols

Copyright (c) and trademark (tm) and registered (r).

Arrows: --> <-- <-->

Fractions: 1/4 1/2 3/4

Dashes: -- and ---
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "SmartSymbols should work: {warnings:?}");
    }
}

// =============================================================================
// PART 2: EDGE CASES
// Test malformed syntax, boundary conditions, and unusual nesting
// =============================================================================

mod edge_cases {
    use super::*;

    #[test]
    fn test_admonition_without_content() {
        let content = r#"# Empty Admonitions

!!! note

!!! warning "Title Only"

Text after.
"#;
        let warnings = lint_mkdocs(content);
        // Should handle gracefully, even if unusual
        assert!(warnings.is_empty(), "Empty admonitions should be handled: {warnings:?}");
    }

    #[test]
    fn test_admonition_single_line() {
        let content = r#"# Single Line

!!! note "Title" Content on same line is not standard but should not crash.

Regular text.
"#;
        // This is non-standard but should not cause false positives
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_deeply_nested_admonitions() {
        let content = r#"# Deep Nesting

!!! note "Level 1"
    Content level 1.

    !!! warning "Level 2"
        Content level 2.

        !!! danger "Level 3"
            Content level 3.

            !!! tip "Level 4"
                Maximum reasonable nesting.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Deeply nested admonitions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_tabs_with_code_blocks() {
        let content = r#"# Tabs with Code

=== "Python"

    ```python
    def hello():
        print("Hello")
    ```

=== "Rust"

    ```rust
    fn main() {
        println!("Hello");
    }
    ```
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Tabs with code should work: {warnings:?}");
    }

    #[test]
    fn test_tabs_inside_admonition() {
        let content = r#"# Nested Tabs

!!! example "Code Examples"

    === "Python"

        ```python
        print("hello")
        ```

    === "JavaScript"

        ```javascript
        console.log("hello");
        ```
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Tabs inside admonitions should work: {warnings:?}");
    }

    #[test]
    fn test_mkdocstrings_with_complex_paths() {
        let content = r#"# Complex Paths

::: package.subpackage.module.Class.method

::: _private_module._PrivateClass

::: module.Class.__init__

::: package.module.CONSTANT
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Complex mkdocstrings paths should work: {warnings:?}"
        );
    }

    #[test]
    fn test_keys_with_special_characters() {
        let content = r#"# Special Keys

Simple: ++enter++ ++escape++ ++space++

Modifiers: ++ctrl+shift+alt+del++

Function keys: ++f1++ ++f12++

Arrows: ++arrow-up++ ++arrow-down++ ++arrow-left++ ++arrow-right++

Numpad: ++num0++ ++num-lock++
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Special keys should work: {warnings:?}");
    }

    #[test]
    fn test_inline_extensions_adjacent() {
        // Multiple inline extensions next to each other
        let content = r#"# Adjacent

Text ==highlight==^super^~sub~**bold** end.

Keys ++ctrl+c++++ctrl+v++ adjacent.

Mixed {++add++}{--del--} together.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Adjacent inline extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_inline_extensions_in_emphasis() {
        // Use inline emphasis within paragraphs to avoid MD036
        let content = r#"# In Emphasis

This is **bold with ==highlight== inside** text.

This is *italic with ^super^ inside* text.

This is ***bold italic with ~sub~ inside*** text.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Extensions in emphasis should work: {warnings:?}");
    }

    #[test]
    fn test_math_with_special_characters() {
        let content = r#"# Complex Math

Inline: $\alpha + \beta = \gamma$ and $x_{i,j}^{2}$

Block with alignment:

$$
\begin{aligned}
a &= b + c \\
d &= e + f
\end{aligned}
$$

Matrices:

$$
\begin{pmatrix}
a & b \\
c & d
\end{pmatrix}
$$
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Complex math should work: {warnings:?}");
    }

    #[test]
    fn test_footnotes_complex_content() {
        let content = r#"# Complex Footnotes

Text[^complex] here.

[^complex]:
    This footnote has:

    - A list
    - With items

    ```python
    # And code
    print("hello")
    ```

    And more text.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Complex footnotes should work: {warnings:?}");
    }

    #[test]
    fn test_abbreviation_with_special_chars() {
        let content = r#"# Special Abbreviations

Using HTML5 and CSS3 and ES6+ features.

*[HTML5]: Hypertext Markup Language version 5
*[CSS3]: Cascading Style Sheets version 3
*[ES6+]: ECMAScript 6 and later
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Abbreviations with special chars should work: {warnings:?}"
        );
    }

    #[test]
    fn test_snippet_paths_with_special_chars() {
        let content = r#"# Special Paths

--8<-- "path/to/file-name.md"

--8<-- "path/to/file_name.py"

--8<-- "../relative/path.txt"

--8<-- "./same-dir/file.md"
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Snippet paths should work: {warnings:?}");
    }

    #[test]
    fn test_attribute_list_complex() {
        let content = r#"# Complex Attributes

## Heading {#my-id .class1 .class2 data-foo="bar" data-baz='qux'}

Paragraph with many attributes.
{#para-id .styled .centered style="color: red" data-toggle="tooltip"}
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Complex attributes should work: {warnings:?}");
    }

    #[test]
    fn test_definition_list_with_markdown() {
        let content = r#"# Rich Definitions

Term with **bold**
:   Definition with *italic* and `code`.

    Second paragraph with [definition list documentation](https://example.com).

Another Term
:   - List item 1
    - List item 2
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Rich definitions should work: {warnings:?}");
    }

    #[test]
    fn test_critic_markup_edge_cases() {
        let content = r#"# Critic Edge Cases

Empty: {++++} {----}

Nested braces: {++text with {braces}++}

Multi-word: {++multiple words added here++}

With punctuation: {--removed, with punctuation!--}

Complex substitution: {~~old text with *emphasis*~>new text with **bold**~~}
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Critic edge cases should work: {warnings:?}");
    }

    #[test]
    fn test_unclosed_inline_extensions() {
        // Malformed syntax should not crash
        let content = r#"# Unclosed

Unclosed key ++ctrl

Unclosed mark ==highlight

Unclosed caret ^super

Regular text after.
"#;
        // Should not panic, may or may not produce warnings
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_empty_content() {
        let content = "";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Empty content should work: {warnings:?}");
    }

    #[test]
    fn test_only_extension_markers() {
        let content = r#"!!! note
    Only admonition.
"#;
        let warnings = lint_mkdocs(content);
        // May trigger MD041 (first line should be heading) but extension should be recognized
        let non_md041 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() != Some("MD041"))
            .count();
        assert_eq!(
            non_md041, 0,
            "Only extension markers should work except MD041: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 3: NEGATIVE TESTS
// Verify that actual violations ARE still detected in extension contexts
// =============================================================================

mod negative_tests {
    use super::*;

    #[test]
    fn test_violations_in_admonition_content() {
        // Test that regular markdown violations are still detected in admonitions
        // Use multiple blank lines which MD012 should detect
        let content = "# Test\n\n!!! note\n    Content here.\n\n\n    More content.\n";
        let warnings = lint_mkdocs(content);
        // MD012 should still detect multiple blank lines
        let md012 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD012"))
            .count();
        assert!(
            md012 > 0,
            "MD012 should detect multiple blanks in admonition: {warnings:?}"
        );
    }

    #[test]
    fn test_trailing_spaces_in_admonition() {
        let content = "# Test\n\n!!! note\n    Line with trailing spaces   \n";
        let warnings = lint_mkdocs(content);
        let md009 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD009"))
            .count();
        assert!(
            md009 > 0,
            "MD009 should detect trailing spaces in admonition: {warnings:?}"
        );
    }

    #[test]
    fn test_long_lines_detected() {
        let content = "# Test\n\nThis is a very long line that exceeds the default 80 character limit and should definitely trigger MD013 in the linter.\n";
        let warnings = lint_mkdocs(content);
        let md013 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD013"))
            .count();
        assert!(md013 > 0, "MD013 should detect long lines: {warnings:?}");
    }

    #[test]
    fn test_multiple_blank_lines_detected() {
        // Use non-heading content so blanks are not heading-adjacent (MD022's domain)
        let content = "# Test\n\nParagraph 1.\n\n\n\nParagraph 2.\n";
        let warnings = lint_mkdocs(content);
        let md012 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD012"))
            .count();
        assert!(md012 > 0, "MD012 should detect multiple blank lines: {warnings:?}");
    }

    #[test]
    fn test_heading_increment_detected() {
        let content = "# H1\n\n### H3 skipping H2\n";
        let warnings = lint_mkdocs(content);
        let md001 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD001"))
            .count();
        assert!(md001 > 0, "MD001 should detect heading increment: {warnings:?}");
    }

    #[test]
    fn test_bare_url_detected() {
        let content = "# Test\n\nVisit https://example.com for more.\n";
        let warnings = lint_mkdocs(content);
        let md034 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .count();
        assert!(md034 > 0, "MD034 should detect bare URLs: {warnings:?}");
    }

    #[test]
    fn test_hard_tabs_detected() {
        let content = "# Test\n\n\tIndented with tab.\n";
        let warnings = lint_mkdocs(content);
        let md010 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD010"))
            .count();
        assert!(md010 > 0, "MD010 should detect hard tabs: {warnings:?}");
    }

    #[test]
    fn test_emphasis_used_as_heading_detected() {
        let content = "# Test\n\n**This looks like a heading**\n\nBut it's just bold text.\n";
        let warnings = lint_mkdocs(content);
        let md036 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD036"))
            .count();
        assert!(md036 > 0, "MD036 should detect emphasis as heading: {warnings:?}");
    }

    #[test]
    fn test_standard_flavor_flags_mkdocs_syntax() {
        // In standard flavor, MkDocs-specific syntax should be flagged
        let content = r#"# Test

!!! note
    This is not an admonition in standard markdown.
"#;
        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should have fewer or different warnings
        // The exact behavior depends on what rules flag the !!! syntax
        // At minimum, standard should not treat it as a special block
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len()
                || mkdocs_warnings.iter().map(|w| &w.rule_name).collect::<Vec<_>>()
                    != standard_warnings.iter().map(|w| &w.rule_name).collect::<Vec<_>>(),
            "Standard and MkDocs should handle admonitions differently"
        );
    }

    #[test]
    fn test_unreferenced_footnote_detected() {
        let content = "# Test\n\n[^orphan]: This footnote is never used.\n";
        let warnings = lint_mkdocs(content);
        let md066 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD066"))
            .count();
        assert!(md066 > 0, "MD066 should detect unreferenced footnotes: {warnings:?}");
    }

    #[test]
    fn test_undefined_reference_detected() {
        let content = "# Test\n\nSee [undefined reference][nowhere] here.\n";
        let warnings = lint_mkdocs(content);
        let md052 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD052"))
            .count();
        assert!(md052 > 0, "MD052 should detect undefined references: {warnings:?}");
    }
}

// =============================================================================
// PART 4: RULE INTERACTION TESTS
// Test specific rules that might conflict with extension syntax
// =============================================================================

mod rule_interactions {
    use super::*;

    #[test]
    fn test_md031_blanks_around_admonitions() {
        // MD031 should recognize admonitions as fence-like blocks
        let content = r#"# Test

Text before.

!!! note
    Admonition content.

Text after.
"#;
        let warnings = lint_mkdocs(content);
        let md031 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD031"))
            .count();
        assert_eq!(
            md031, 0,
            "MD031 should not flag properly spaced admonitions: {warnings:?}"
        );
    }

    #[test]
    fn test_md038_with_inlinehilite() {
        // MD038 checks for spaces in code spans
        // InlineHilite uses `#!lang code` which should not be flagged
        let content = "# Test\n\nUse `#!python print()` here.\n";
        let warnings = lint_mkdocs(content);
        let md038 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .count();
        assert_eq!(md038, 0, "MD038 should not flag InlineHilite: {warnings:?}");
    }

    #[test]
    fn test_md040_with_superfences() {
        // MD040 requires language on fenced code blocks
        // Superfences with mermaid/other should be recognized
        let content = "# Test\n\n```mermaid\ngraph TD\n    A --> B\n```\n";
        let warnings = lint_mkdocs(content);
        let md040 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD040"))
            .count();
        assert_eq!(md040, 0, "MD040 should recognize mermaid as language: {warnings:?}");
    }

    #[test]
    fn test_md042_with_auto_references() {
        // MD042 flags empty links []()
        // mkdocstrings uses [Class][] for auto-references
        let content = "# Test\n\nSee [module.Class][] for details.\n";
        let warnings = lint_mkdocs(content);
        let md042 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD042"))
            .count();
        assert_eq!(md042, 0, "MD042 should allow auto-references: {warnings:?}");
    }

    #[test]
    fn test_md046_with_tabs_and_admonitions() {
        // MD046 checks code block style consistency
        let content = r#"# Test

=== "Tab 1"

    ```python
    code()
    ```

=== "Tab 2"

    ```python
    more_code()
    ```
"#;
        let warnings = lint_mkdocs(content);
        let md046 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD046"))
            .count();
        assert_eq!(md046, 0, "MD046 should handle code in tabs: {warnings:?}");
    }

    #[test]
    fn test_md033_with_md_in_html() {
        // MD033 flags inline HTML
        // But markdown="1" attribute should be allowed in MkDocs
        let content = "# Test\n\n<div markdown=\"1\">\nContent.\n</div>\n";
        let warnings = lint_mkdocs(content);
        let md033 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD033"))
            .count();
        assert_eq!(md033, 0, "MD033 should allow markdown attribute: {warnings:?}");
    }

    #[test]
    fn test_md049_md050_with_pymdown() {
        // MD049/MD050 check emphasis style consistency
        // PyMdown extensions like ==mark== use similar syntax
        let content = "# Test\n\nThis is ==marked== and *italic* and **bold**.\n";
        let warnings = lint_mkdocs(content);
        let md049 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD049"))
            .count();
        let md050 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD050"))
            .count();
        assert_eq!(
            md049 + md050,
            0,
            "MD049/MD050 should not flag mark syntax: {warnings:?}"
        );
    }

    #[test]
    fn test_md032_with_lists_in_admonitions() {
        // MD032 requires blanks around lists
        // Lists inside admonitions might have different rules
        let content = r#"# Test

!!! note
    Text before list.

    - Item 1
    - Item 2

    Text after list.
"#;
        let warnings = lint_mkdocs(content);
        let md032 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD032"))
            .count();
        assert_eq!(md032, 0, "MD032 should handle lists in admonitions: {warnings:?}");
    }

    #[test]
    fn test_md022_md023_with_attr_list() {
        // MD022/MD023 check blanks around headings
        // Headings with attribute lists like {#id} should work
        let content = "# Test\n\n## Heading {#custom-id}\n\nText.\n";
        let warnings = lint_mkdocs(content);
        let md022 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD022"))
            .count();
        let md023 = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD023"))
            .count();
        assert_eq!(md022 + md023, 0, "MD022/MD023 should handle attr_list: {warnings:?}");
    }

    #[test]
    fn test_md024_with_tabs() {
        // MD024 flags duplicate headings
        // Same heading in different tabs should be allowed
        let content = r#"# Guide

=== "Python"

    ## Installation

    Install with pip.

=== "JavaScript"

    ## Installation

    Install with npm.
"#;
        let warnings = lint_mkdocs(content);
        // The duplicate "Installation" headings are in different tab contexts
        // This is a nuanced case - behavior may vary
        let _ = warnings; // Just verify no panic
    }
}

// =============================================================================
// PART 5: FIX PRESERVATION TESTS
// Verify that auto-fix doesn't break extension syntax
// =============================================================================

mod fix_preservation {
    use super::*;

    fn assert_fix_preserves(content: &str, rule: &dyn Rule, rule_name: &str) {
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();

        // Key extension markers should be preserved
        if content.contains("!!!") {
            assert!(fixed.contains("!!!"), "{rule_name} should preserve admonitions");
        }
        if content.contains("===") {
            assert!(fixed.contains("==="), "{rule_name} should preserve tabs");
        }
        if content.contains(":::") {
            assert!(fixed.contains(":::"), "{rule_name} should preserve mkdocstrings");
        }
        if content.contains("++") && content.contains("++") {
            // Keys extension
            assert!(fixed.matches("++").count() >= 2, "{rule_name} should preserve keys");
        }
        if content.contains("--8<--") {
            assert!(fixed.contains("--8<--"), "{rule_name} should preserve snippets");
        }
    }

    #[test]
    fn test_md009_preserves_extensions() {
        let content = "# Test\n\n!!! note\n    Content here.   \n\n=== \"Tab\"\n\n    Tab content.\n";
        let rule = MD009TrailingSpaces::default();
        assert_fix_preserves(content, &rule, "MD009");
    }

    #[test]
    fn test_md010_preserves_extensions() {
        let content = "# Test\n\n!!! note\n\tTabbed content.\n";
        let rule = MD010NoHardTabs::default();
        assert_fix_preserves(content, &rule, "MD010");
    }

    #[test]
    fn test_md012_preserves_extensions() {
        let content = "# Test\n\n\n!!! note\n    Content.\n\n\n=== \"Tab\"\n\n    More.\n";
        let rule = MD012NoMultipleBlanks::default();
        assert_fix_preserves(content, &rule, "MD012");
    }

    #[test]
    fn test_md013_preserves_extensions() {
        let content = r#"# Test

!!! note "A very long admonition title that might exceed line length limits"
    Content inside the admonition that is also quite long and might be wrapped.

=== "Tab with a somewhat long title"

    Tab content here.

::: module.path.to.a.deeply.nested.Class
    options:
      show_source: true
"#;
        let config = create_mkdocs_config();
        let rule = MD013LineLength::from_config(&config);
        assert_fix_preserves(content, rule.as_ref(), "MD013");
    }

    #[test]
    fn test_md022_preserves_extensions() {
        let content = "# Test\n## Heading {#custom-id}\nText.\n";
        let config = create_mkdocs_config();
        let rule = MD022BlanksAroundHeadings::from_config(&config);
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(fixed.contains("{#custom-id}"), "MD022 should preserve attribute lists");
    }

    #[test]
    fn test_md023_preserves_extensions() {
        let content = "# Test\n\n  ## Indented {.class}\n\nText.\n";
        let config = create_mkdocs_config();
        let rule = MD023HeadingStartLeft::from_config(&config);
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(fixed.contains("{.class}"), "MD023 should preserve attribute lists");
    }

    #[test]
    fn test_md031_preserves_extensions() {
        let content = "# Test\n!!! note\n    Content.\nText after.\n";
        let rule = MD031BlanksAroundFences::default();
        assert_fix_preserves(content, &rule, "MD031");
    }

    #[test]
    fn test_md032_preserves_extensions() {
        let content = "# Test\n!!! note\n    - Item 1\n    - Item 2\nText.\n";
        let rule = MD032BlanksAroundLists::default();
        assert_fix_preserves(content, &rule, "MD032");
    }

    #[test]
    fn test_md047_preserves_extensions() {
        let content = "# Test\n\n!!! note\n    Content.\n";
        let rule = MD047SingleTrailingNewline;
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(fixed.contains("!!!"), "MD047 should preserve admonitions");
    }

    #[test]
    fn test_multiple_fixes_preserve_extensions() {
        // Apply multiple fixes in sequence
        let content = r#"# Test

!!! note
    Content with trailing spaces.

=== "Tab"

    Tab content.


Extra blank lines above.

::: module.Class
    options:
      show: true
"#;
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        // Apply fixes in sequence
        let md009 = MD009TrailingSpaces::default();
        let fixed1 = md009.fix(&ctx).unwrap();

        let ctx2 = LintContext::new(&fixed1, MarkdownFlavor::MkDocs, None);
        let md012 = MD012NoMultipleBlanks::default();
        let fixed2 = md012.fix(&ctx2).unwrap();

        assert!(fixed2.contains("!!!"), "Multiple fixes should preserve admonitions");
        assert!(fixed2.contains("==="), "Multiple fixes should preserve tabs");
        assert!(fixed2.contains(":::"), "Multiple fixes should preserve mkdocstrings");
    }
}

// =============================================================================
// PART 6: STRESS TESTS
// Complex documents with multiple nested extensions and edge cases
// =============================================================================

mod stress_tests {
    use super::*;

    #[test]
    fn test_comprehensive_document() {
        let content = r#"# Comprehensive MkDocs Document

[TOC]

## Introduction

This document tests all MkDocs extensions. The HTML[^1] works.

*[HTML]: Hypertext Markup Language
*[CSS]: Cascading Style Sheets

## Admonitions with Everything

!!! note "Complex Admonition"
    This admonition contains:

    - Task lists:
        - [x] Completed
        - [ ] Pending

    - Keys: Press ++ctrl+c++ to copy.

    - Code:

        ```python title="example.py"
        print("Hello")
        ```

    - Math: $E = mc^2$

    - Formatting: ==highlighted== and ^^inserted^^

??? tip "Collapsible with Tabs"

    === "Python"

        ```python
        def greet():
            print("Hello")
        ```

    === "Rust"

        ```rust
        fn greet() {
            println!("Hello");
        }
        ```

## API Reference

::: mypackage.core.MainClass
    handler: python
    options:
        show_source: true
        heading_level: 3
        members:
            - __init__
            - process
            - cleanup

See [mypackage.core.MainClass][] for details.

## Definition Lists

API
:   Application Programming Interface.

    Used for:

    - Communication
    - Integration

SDK
:   Software Development Kit.

## Complex Math

Inline: $\sum_{i=1}^{n} x_i$ and $\int_0^\infty e^{-x} dx$

Block:

$$
\mathbf{V}_1 \times \mathbf{V}_2 =
\begin{vmatrix}
\mathbf{i} & \mathbf{j} & \mathbf{k} \\
\frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
\frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0
\end{vmatrix}
$$

## Critic Markup

Has {++additions++}, {--deletions--}, and {~~old~>new~~}.

It also has {==highlights==} and {>>author comments<<}.

## Snippets

--8<-- "examples/header.md"

## Formatting Summary

| Feature | Syntax | Example |
|---------|--------|---------|
| Keys | `++key++` | ++enter++ |
| Mark | `==text==` | ==marked== |
| Super | `^text^` | ^super^ |
| Sub | `~text~` | ~sub~ |

---

[^1]: Footnote with complex content:

    Including code:

    ```python
    print("footnote code")
    ```

    And lists:

    - Item 1
    - Item 2
"#;

        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Comprehensive document should have no warnings: {warnings:?}"
        );
    }

    #[test]
    fn test_maximum_nesting_depth() {
        let content = r#"# Maximum Nesting

!!! note "Level 1"

    !!! warning "Level 2"

        !!! danger "Level 3"

            === "Tab A"

                ```python title="nested.py"
                def deeply_nested():
                    """
                    Docstring with math: $x^2$
                    """
                    pass
                ```

            === "Tab B"

                ::: module.Class
                    options:
                        show: true
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Maximum nesting should work: {warnings:?}");
    }

    #[test]
    fn test_all_inline_extensions_together() {
        let content = r#"# All Inline Extensions

This has ==highlights==, ^super^, ~sub~, ~~strike~~, ^^insert^^.

Also ++keys++ and `#!python code()`.

Math inline: $\alpha + \beta = \gamma$

Emoji: :material-check: :fontawesome-solid-heart:

Critic: {++add++} {--del--} {~~old~>new~~} {==mark==} {>>note<<}

Combined: ==mark== ^sup^ ~sub~ ^^ins^^ ++key++ `#!py x` $x$ :smile:
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "All inline extensions should work: {warnings:?}");
    }

    #[test]
    fn test_rapid_context_switching() {
        // Rapidly switch between different extension contexts
        let content = r#"# Rapid Switching

!!! note
    Note.

=== "Tab"
    Tab.

::: mod
    opt: val

!!! warning
    Warning.

=== "Another"
    Another.

::: other
    more: opts

Regular paragraph.

!!! tip
    Final.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Rapid context switching should work: {warnings:?}");
    }

    #[test]
    fn test_large_document_performance() {
        // Generate a large document with many extensions
        let mut content = String::from("# Large Document\n\n");

        for i in 0..50 {
            content.push_str(&format!("## Section {i}\n\n"));
            content.push_str(&format!("!!! note \"Note {i}\"\n    Content for note {i}.\n\n"));
            content.push_str(&format!("=== \"Tab A{i}\"\n\n    Tab A content.\n\n"));
            content.push_str(&format!("=== \"Tab B{i}\"\n\n    Tab B content.\n\n"));
            content.push_str(&format!("::: module{i}.Class\n\n"));
            content.push_str(&format!("Text with ==hi{i}== and ++key{i}++.\n\n"));
        }

        // Trim trailing newlines to avoid MD012 (multiple blank lines at end)
        let content = content.trim_end().to_string() + "\n";

        let start = std::time::Instant::now();
        let warnings = lint_mkdocs(&content);
        let duration = start.elapsed();

        assert!(
            warnings.is_empty(),
            "Large document should have no warnings: {warnings:?}"
        );
        assert!(
            duration.as_secs() < 10,
            "Large document should lint in reasonable time: {duration:?}"
        );
    }

    #[test]
    fn test_unicode_in_extensions() {
        let content = r#"# Unicode Extensions

!!! note "日本語タイトル"
    Japanese content: こんにちは

=== "Français"
    Contenu français avec accents: é, è, ê, ë

=== "中文"
    中文内容

::: module.Ελληνικά

Text with ==强调== and ^^挿入^^ and ~下付き~.

*[API]: アプリケーションプログラミングインターフェース

Press ++ctrl+日++ for Japanese.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Unicode in extensions should work: {warnings:?}");
    }

    #[test]
    fn test_special_characters_in_extensions() {
        let content = r#"# Special Characters

!!! note "Title with <angle> & 'quotes' \"doubles\""
    Content with special chars: < > & ' " ` ~

=== "Tab with `backticks`"
    Content.

=== "Tab with *asterisks*"
    More content.

::: module.Class_with_underscores

*[C++]: C Plus Plus
*[C#]: C Sharp

Keys: ++ctrl+<++ and ++>+shift++
"#;
        let warnings = lint_mkdocs(content);
        // May have some warnings but should not crash
        let _ = warnings;
    }

    #[test]
    fn test_empty_extension_blocks() {
        let content = r#"# Empty Blocks

!!! note

!!! warning ""

=== ""

=== "Empty Tab"

:::

::: module

Text after empty blocks.
"#;
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_extensions_at_document_boundaries() {
        // Extension at very start
        let content1 = "!!! note\n    Start with admonition.\n";
        let _ = lint_mkdocs(content1);

        // Extension at very end
        let content2 = "# Title\n\n!!! note\n    End with admonition.";
        let _ = lint_mkdocs(content2);

        // Only extensions, no regular content
        let content3 = "!!! note\n    Only.\n\n=== \"Tab\"\n    Tab.\n\n::: mod\n";
        let _ = lint_mkdocs(content3);
    }
}

// =============================================================================
// PART 7: TABLE EXTENSION TESTS
// Verify table handling with MkDocs extensions (MD056)
// =============================================================================

mod table_extensions {
    use super::*;

    #[test]
    fn test_basic_table_in_mkdocs() {
        let content = r#"# Tables

| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1   | Cell 2   | Cell 3   |
| Cell 4   | Cell 5   | Cell 6   |
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Basic table should work: {warnings:?}");
    }

    #[test]
    fn test_table_in_admonition() {
        let content = r#"# Table in Admonition

!!! note "Table Example"

    | Header | Value |
    |--------|-------|
    | Key    | Val   |
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Table in admonition should work: {warnings:?}");
    }

    #[test]
    fn test_table_in_content_tab() {
        let content = r#"# Table in Tab

=== "Data Table"

    | ID | Name    | Status |
    |----|---------|--------|
    | 1  | Alice   | Active |
    | 2  | Bob     | Active |

=== "Summary Table"

    | Metric | Value |
    |--------|-------|
    | Total  | 100   |
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Table in content tab should work: {warnings:?}");
    }

    #[test]
    fn test_table_with_inline_extensions() {
        let content = r#"# Tables with Extensions

| Feature | Syntax | Result |
|---------|--------|--------|
| Keys | `++ctrl++` | ++ctrl++ |
| Mark | `==text==` | ==text== |
| Math | `$x^2$` | $x^2$ |
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Table with inline extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_table_alignment_variations() {
        let content = r#"# Aligned Tables

| Left | Center | Right |
|:-----|:------:|------:|
| L    | C      | R     |
| L    | C      | R     |
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Table alignment should work: {warnings:?}");
    }

    #[test]
    fn test_table_after_admonition() {
        let content = r#"# Table After Admonition

!!! info
    Some info here.

| After | Admonition |
|-------|------------|
| Data  | Here       |
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Table after admonition should work: {warnings:?}");
    }

    #[test]
    fn test_multiple_tables_with_extensions() {
        let content = r#"# Multiple Tables

!!! note "First Table"

    | A | B |
    |---|---|
    | 1 | 2 |

!!! warning "Second Table"

    | C | D |
    |---|---|
    | 3 | 4 |

Regular table:

| E | F |
|---|---|
| 5 | 6 |
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Multiple tables with extensions should work: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 8: FRONTMATTER/META EXTENSION TESTS
// Verify YAML frontmatter handling with MkDocs
// =============================================================================

mod frontmatter_tests {
    use super::*;

    #[test]
    fn test_yaml_frontmatter_basic() {
        let content = r#"---
title: Test Document
description: A test document for MkDocs
---

## Overview

Content here.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "YAML frontmatter should work: {warnings:?}");
    }

    #[test]
    fn test_frontmatter_with_extensions() {
        let content = r#"---
title: Extensions Test
tags:
  - mkdocs
  - testing
---

## Extensions

!!! note
    Content after frontmatter.

=== "Tab"
    Tab content.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Frontmatter with extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_frontmatter_with_special_yaml() {
        let content = r#"---
title: "Title with: colon"
description: |
  Multi-line
  description
list:
  - item1
  - item2
---

## Content

Text here.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Complex YAML frontmatter should work: {warnings:?}"
        );
    }

    #[test]
    fn test_frontmatter_not_confused_with_hr() {
        let content = r#"---
title: Test
---

## Heading

Content.

---

More content after horizontal rule.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "HR after frontmatter should work: {warnings:?}");
    }

    #[test]
    fn test_frontmatter_with_mkdocstrings_config() {
        let content = r#"---
title: API Reference
plugins:
  - mkdocstrings:
      handlers:
        python:
          options:
            show_source: true
---

## API Reference

::: mymodule.MyClass
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Frontmatter with mkdocstrings config should work: {warnings:?}"
        );
    }

    #[test]
    fn test_toml_frontmatter() {
        // TOML frontmatter uses +++ delimiters (Hugo-style)
        // Note: rumdl may or may not support this - test for graceful handling
        let content = r#"+++
title = "TOML Frontmatter"
date = 2024-01-01
+++

# TOML Test

Content.
"#;
        // Should not panic, warnings may vary based on implementation
        let _ = lint_mkdocs(content);
    }
}

// =============================================================================
// PART 9: EXTENSION-INSIDE-EXTENSION INTERACTION TESTS
// Verify deeply nested and interacting extensions
// =============================================================================

mod extension_interactions {
    use super::*;

    #[test]
    fn test_tabs_inside_admonition() {
        let content = r#"# Nested Extensions

!!! example "Code Examples"

    === "Python"

        ```python
        def hello():
            print("Hello")
        ```

    === "Rust"

        ```rust
        fn hello() {
            println!("Hello");
        }
        ```

    === "Go"

        ```go
        func hello() {
            fmt.Println("Hello")
        }
        ```
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Tabs inside admonition should work: {warnings:?}");
    }

    #[test]
    fn test_admonition_inside_tabs() {
        let content = r#"# Admonitions in Tabs

=== "Notes"

    !!! note
        A note inside a tab.

    !!! warning
        A warning inside the same tab.

=== "Tips"

    !!! tip
        A tip in another tab.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Admonitions inside tabs should work: {warnings:?}");
    }

    #[test]
    fn test_code_blocks_inside_nested_extensions() {
        let content = r#"# Deep Code Nesting

!!! example

    === "With Highlighting"

        ```python title="example.py" hl_lines="2 3"
        def process():
            data = load()
            result = transform(data)
            return result
        ```

    === "With Line Numbers"

        ```python linenums="1"
        def other():
            pass
        ```
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Code blocks in nested extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_mkdocstrings_inside_admonition() {
        let content = r#"# API in Admonition

!!! info "Quick Reference"

    ::: mymodule.quick_function
        options:
            show_source: false

!!! example "Full Reference"

    ::: mymodule.detailed_function
        options:
            show_source: true
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "mkdocstrings inside admonition should work: {warnings:?}"
        );
    }

    #[test]
    fn test_definition_list_inside_admonition() {
        let content = r#"# Definitions in Admonition

!!! note "Terminology"

    Term 1
    :   Definition of term 1.

    Term 2
    :   Definition of term 2.
        With additional detail.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Definition list in admonition should work: {warnings:?}"
        );
    }

    #[test]
    fn test_footnotes_with_extensions() {
        let content = r#"# Footnotes and Extensions

Text with footnote[^1] and ==highlighting==.

!!! note
    Content with another footnote[^2].

[^1]: Regular footnote.

[^2]: Footnote from admonition.

    With code:

    ```python
    print("footnote code")
    ```
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Footnotes with extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_math_inside_all_contexts() {
        let content = r#"# Math Everywhere

Inline: $E = mc^2$

!!! note "Math Note"
    Block math in admonition:

    $$
    \int_0^\infty e^{-x} dx = 1
    $$

=== "Equations"

    More math: $\sum_{i=1}^n x_i$

    $$
    \frac{d}{dx} \sin(x) = \cos(x)
    $$
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Math inside all contexts should work: {warnings:?}"
        );
    }

    #[test]
    fn test_inline_extensions_inside_all_block_extensions() {
        let content = r#"# Inline in Blocks

!!! note
    Keys: ++ctrl+s++ to save.
    Mark: ==important== text.
    Super: E=mc^2^

=== "Inline Tab"
    Subscript: H~2~O
    Emoji: :material-check:
    Critic: {++added++}

::: module.Class
    options:
        show_root_heading: true
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Inline extensions in block extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_collapsible_with_everything() {
        let content = r#"# Collapsible Complex

??? example "Click to expand"

    === "Overview"

        !!! tip
            Nested tip.

        Regular paragraph with ==marks==.

    === "Details"

        ```python title="code.py"
        print("nested code")
        ```

        Math: $x^2 + y^2 = z^2$

???+ warning "Open by default"

    - List with ++keys++
    - And ^superscript^
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Collapsible with everything should work: {warnings:?}"
        );
    }

    #[test]
    fn test_triple_nesting_depth() {
        let content = r#"# Triple Nesting

!!! note "Level 1"

    === "Level 2A"

        !!! warning "Level 3"
            Deep content with ==mark== and ++key++.

    === "Level 2B"

        !!! tip "Level 3 Alt"
            Alternative deep content.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Triple nesting should work: {warnings:?}");
    }
}

// =============================================================================
// PART 10: FIX OUTPUT VALIDATION TESTS
// Verify that fixed output is still valid MkDocs markdown
// =============================================================================

mod fix_validation {
    use super::*;

    fn fix_and_validate(content: &str, rule: &dyn Rule, rule_name: &str) {
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();

        // The fixed content should have no warnings from ANY rule
        let post_fix_warnings = lint_mkdocs(&fixed);

        // Filter to warnings that indicate broken extension syntax
        let extension_breaks: Vec<_> = post_fix_warnings
            .iter()
            .filter(|w| {
                // These would indicate the fix broke extension syntax
                let msg = &w.message;
                msg.contains("!!!") || msg.contains("===") || msg.contains(":::")
            })
            .collect();

        assert!(
            extension_breaks.is_empty(),
            "{rule_name} fix should not break extension syntax. \
             Original:\n{content}\nFixed:\n{fixed}\nBroken: {extension_breaks:?}"
        );
    }

    #[test]
    fn test_md009_fix_validates() {
        let content = "# Test\n\n!!! note\n    Content here.   \n\n=== \"Tab\"  \n    Tab.   \n";
        let rule = MD009TrailingSpaces::default();
        fix_and_validate(content, &rule, "MD009");

        // Also verify the fix removed trailing spaces
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(!fixed.contains("   \n"), "MD009 should remove trailing spaces");
    }

    #[test]
    fn test_md010_fix_validates() {
        let content = "# Test\n\n!!! note\n\tTabbed content.\n";
        let rule = MD010NoHardTabs::default();
        fix_and_validate(content, &rule, "MD010");

        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(!fixed.contains('\t'), "MD010 should remove hard tabs");
    }

    #[test]
    fn test_md012_fix_validates() {
        let content = "# Test\n\n\n\n!!! note\n    Content.\n\n\n\n=== \"Tab\"\n    More.\n";
        let rule = MD012NoMultipleBlanks::default();
        fix_and_validate(content, &rule, "MD012");

        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        // Heading-adjacent blanks are preserved (heading spacing is MD022's domain)
        // Non-heading blanks (before === "Tab") are still reduced
        assert!(
            fixed.contains("Content.\n\n=== \"Tab\""),
            "MD012 should reduce non-heading-adjacent blanks"
        );
    }

    #[test]
    fn test_md047_fix_validates() {
        let content = "# Test\n\n!!! note\n    Content.\n\n=== \"Tab\"\n    Tab.";
        let rule = MD047SingleTrailingNewline;
        fix_and_validate(content, &rule, "MD047");

        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();
        assert!(fixed.ends_with('\n'), "MD047 should add trailing newline");
        assert!(!fixed.ends_with("\n\n"), "MD047 should not add multiple newlines");
    }

    #[test]
    fn test_fix_preserves_extension_markers_precisely() {
        let content = "# Test\n\n!!! note \"Title\"   \n    Content.   \n\n???+ tip\n    Tip.   \n";
        let rule = MD009TrailingSpaces::default();
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();

        // Verify specific markers are preserved exactly
        assert!(fixed.contains("!!! note \"Title\""), "Admonition marker preserved");
        assert!(fixed.contains("???+ tip"), "Collapsible marker preserved");
    }

    #[test]
    fn test_fix_chain_validates() {
        // Apply multiple fixes in sequence and validate result
        let content = "# Test   \n\n\n!!! note\n    Content.   \n\n\n=== \"Tab\"\n    Tab.   ";

        let mut current = content.to_string();

        // Fix trailing spaces
        let ctx = LintContext::new(&current, MarkdownFlavor::MkDocs, None);
        current = MD009TrailingSpaces::default().fix(&ctx).unwrap();

        // Fix multiple blanks
        let ctx = LintContext::new(&current, MarkdownFlavor::MkDocs, None);
        current = MD012NoMultipleBlanks::default().fix(&ctx).unwrap();

        // Fix trailing newline
        let ctx = LintContext::new(&current, MarkdownFlavor::MkDocs, None);
        current = MD047SingleTrailingNewline.fix(&ctx).unwrap();

        // Final result should be clean
        let final_warnings = lint_mkdocs(&current);
        let critical: Vec<_> = final_warnings
            .iter()
            .filter(|w| matches!(w.rule_name.as_deref(), Some("MD009") | Some("MD012") | Some("MD047")))
            .collect();

        assert!(
            critical.is_empty(),
            "Fix chain should resolve all targeted issues: {critical:?}"
        );

        // Extensions still intact
        assert!(current.contains("!!! note"), "Admonition preserved");
        assert!(current.contains("=== \"Tab\""), "Tab preserved");
    }

    #[test]
    fn test_fix_with_deeply_nested_content() {
        let content = r#"# Deep Nesting

!!! note "Outer"

    === "Tab A"

        !!! warning "Inner"
            Content.

    === "Tab B"

        More content.
"#;
        let rule = MD009TrailingSpaces::default();
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fixed = rule.fix(&ctx).unwrap();

        // All nesting levels preserved
        assert!(fixed.contains("!!! note \"Outer\""), "Outer admonition");
        assert!(fixed.contains("=== \"Tab A\""), "Tab A");
        assert!(fixed.contains("!!! warning \"Inner\""), "Inner admonition");
        assert!(fixed.contains("=== \"Tab B\""), "Tab B");

        // No trailing spaces remain
        for (i, line) in fixed.lines().enumerate() {
            assert!(
                !line.ends_with(' '),
                "Line {} should not have trailing space: {:?}",
                i + 1,
                line
            );
        }
    }
}

// =============================================================================
// PART 11: SYSTEMATIC BOUNDARY TESTS
// Property-based style tests for edge cases
// =============================================================================

mod boundary_tests {
    use super::*;

    #[test]
    fn test_extension_marker_variations() {
        // Test all valid admonition marker formats
        let markers = [
            "!!! note",
            "!!! note \"Title\"",
            "!!! note \"\"",
            "??? note",
            "??? note \"Title\"",
            "???+ note",
            "???+ note \"Title\"",
        ];

        for marker in markers {
            let content = format!("# Test\n\n{marker}\n    Content.\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Marker '{marker}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_tab_marker_variations() {
        let markers = [
            "=== \"Tab\"",
            "=== \"\"",
            "=== \"Tab with spaces\"",
            "=== \"Tab's apostrophe\"",
        ];

        for marker in markers {
            let content = format!("# Test\n\n{marker}\n    Content.\n");
            let warnings = lint_mkdocs(&content);
            // May have warnings but should not panic
            let _ = warnings;
        }
    }

    #[test]
    fn test_mkdocstrings_marker_variations() {
        let markers = [
            "::: module",
            "::: module.submodule",
            "::: module.submodule.Class",
            "::: module.Class.method",
            "::: package.module:function",
        ];

        for marker in markers {
            let content = format!("# Test\n\n{marker}\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "mkdocstrings '{marker}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_inline_extension_boundary_positions() {
        // Extensions at various positions in text
        let cases = [
            "==start== of line",
            "at ==middle== of line",
            "at end ==here==",
            "==only==",
            "a==tight==b",
            "==one== and ==two==",
        ];

        for case in cases {
            let content = format!("# Test\n\n{case}\n");
            let warnings = lint_mkdocs(&content);
            assert!(
                warnings.is_empty(),
                "Inline extension '{case}' should work: {warnings:?}"
            );
        }
    }

    #[test]
    fn test_keys_extension_variations() {
        let keys = [
            "++ctrl++",
            "++ctrl+c++",
            "++ctrl+alt+del++",
            "++ctrl+shift+alt+f12++",
            "++enter++",
            "++backspace++",
            "++arrow-up++",
            "++arrow-down++",
            "++arrow-left++",
            "++arrow-right++",
        ];

        for key in keys {
            let content = format!("# Test\n\nPress {key} to continue.\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Key '{key}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_snippet_syntax_variations() {
        let snippets = [
            "--8<-- \"file.md\"",
            "--8<-- \"path/to/file.md\"",
            "--8<-- \"../relative/file.md\"",
            ";--8<--",
        ];

        for snippet in snippets {
            let content = format!("# Test\n\n{snippet}\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Snippet '{snippet}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_math_boundary_positions() {
        let cases = [
            "$x$ at start",
            "at end $x$",
            "in $middle$ here",
            "$a$ and $b$ and $c$",
            "tight$x$bound",
        ];

        for case in cases {
            let content = format!("# Test\n\n{case}\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Math '{case}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_critic_markup_variations() {
        let critics = [
            "{++addition++}",
            "{--deletion--}",
            "{~~old~>new~~}",
            "{==highlight==}",
            "{>>comment<<}",
        ];

        for critic in critics {
            let content = format!("# Test\n\nText with {critic} here.\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Critic '{critic}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_abbreviation_variations() {
        let abbrs = [
            "*[HTML]: Hypertext Markup Language",
            "*[CSS]: Cascading Style Sheets",
            "*[API]: Application Programming Interface",
            "*[URL]: Uniform Resource Locator",
        ];

        for abbr in abbrs {
            let content = format!("# Test\n\nThe HTML spec.\n\n{abbr}\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Abbreviation '{abbr}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_attribute_list_variations() {
        let attrs = [
            "{ #id }",
            "{ .class }",
            "{ #id .class }",
            "{ .class1 .class2 }",
            "{ data-attr=value }",
            "{ #id .class data-x=y }",
        ];

        for attr in attrs {
            let content = format!("# Heading {attr}\n\nText.\n");
            let warnings = lint_mkdocs(&content);
            assert!(warnings.is_empty(), "Attribute '{attr}' should work: {warnings:?}");
        }
    }

    #[test]
    fn test_empty_and_whitespace_only_content() {
        let cases = ["", "\n", "\n\n", "   ", "   \n", "\t", "\t\n"];

        for case in cases {
            // Should not panic
            let _ = lint_mkdocs(case);
        }
    }

    #[test]
    fn test_maximum_line_lengths_with_extensions() {
        // 79 chars (within limit)
        let short = format!(
            "# Test\n\nPress {} to continue.\n",
            "++ctrl+alt+shift+".to_string() + &"x".repeat(40) + "++"
        );
        // Should work or fail gracefully
        let _ = lint_mkdocs(&short);
    }
}

// =============================================================================
// PART 12: REGRESSION PREVENTION TESTS
// Tests for specific bugs that could occur
// =============================================================================

mod regression_tests {
    use super::*;

    #[test]
    fn test_admonition_not_confused_with_emphasis() {
        // The "!!!" should not trigger emphasis-related rules
        let content = "# Test\n\n!!! note\n    Content.\n";
        let warnings = lint_mkdocs(content);
        let emphasis_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| {
                matches!(
                    w.rule_name.as_deref(),
                    Some("MD036") | Some("MD037") | Some("MD049") | Some("MD050")
                )
            })
            .collect();
        assert!(
            emphasis_warnings.is_empty(),
            "Admonition should not trigger emphasis rules: {emphasis_warnings:?}"
        );
    }

    #[test]
    fn test_tabs_not_confused_with_code_fence() {
        // The "===" should not trigger code fence rules
        let content = "# Test\n\n=== \"Tab\"\n    Content.\n";
        let warnings = lint_mkdocs(content);
        let fence_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| matches!(w.rule_name.as_deref(), Some("MD031") | Some("MD040") | Some("MD046")))
            .collect();
        assert!(
            fence_warnings.is_empty(),
            "Tab should not trigger fence rules: {fence_warnings:?}"
        );
    }

    #[test]
    fn test_mkdocstrings_not_confused_with_definition_list() {
        // The ":::" should not trigger definition list warnings
        let content = "# Test\n\n::: module.Class\n";
        let warnings = lint_mkdocs(content);
        // Should have no warnings about definition lists
        assert!(
            warnings.is_empty(),
            "mkdocstrings should not trigger warnings: {warnings:?}"
        );
    }

    #[test]
    fn test_keys_not_confused_with_code_span() {
        // The "++...++" should not trigger MD038 (spaces in code spans)
        let content = "# Test\n\nPress ++ctrl+c++ to copy.\n";
        let warnings = lint_mkdocs(content);
        let code_span_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .collect();
        assert!(
            code_span_warnings.is_empty(),
            "Keys should not trigger MD038: {code_span_warnings:?}"
        );
    }

    #[test]
    fn test_mark_not_confused_with_emphasis() {
        // The "==...==" should not trigger emphasis rules
        let content = "# Test\n\nThis is ==highlighted== text.\n";
        let warnings = lint_mkdocs(content);
        let emphasis_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| matches!(w.rule_name.as_deref(), Some("MD049") | Some("MD050")))
            .collect();
        assert!(
            emphasis_warnings.is_empty(),
            "Mark should not trigger emphasis rules: {emphasis_warnings:?}"
        );
    }

    #[test]
    fn test_snippet_not_confused_with_html_comment() {
        // The "--8<--" should not trigger HTML comment handling
        let content = "# Test\n\n--8<-- \"file.md\"\n";
        let warnings = lint_mkdocs(content);
        let html_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD033"))
            .collect();
        assert!(
            html_warnings.is_empty(),
            "Snippet should not trigger MD033: {html_warnings:?}"
        );
    }

    #[test]
    fn test_indented_content_in_extensions_not_code_block() {
        // Indented content inside admonitions should not be treated as code
        let content = r#"# Test

!!! note
    This is indented but not code.

    Still not code.
"#;
        let warnings = lint_mkdocs(content);
        let code_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| matches!(w.rule_name.as_deref(), Some("MD040") | Some("MD046")))
            .collect();
        assert!(
            code_warnings.is_empty(),
            "Admonition content should not be code: {code_warnings:?}"
        );
    }

    #[test]
    fn test_auto_reference_handling() {
        // [Class][] is auto-reference syntax in MkDocs with mkdocstrings
        // This tests current behavior - may warn until auto-ref support is added
        let content = "# Test\n\nSee [MyClass][] for details.\n";
        let warnings = lint_mkdocs(content);
        // Document current behavior: MD052 may or may not fire
        // The important thing is it doesn't crash and handles gracefully
        let _ = warnings;
    }

    #[test]
    fn test_smartsymbols_not_flagged() {
        // Smart symbols like (c), (tm), --> should work
        let content = "# Test\n\nCopyright (c) 2024. Trademark (tm). Arrow -->.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Smart symbols should work: {warnings:?}");
    }

    #[test]
    fn test_emoji_shortcodes_not_flagged() {
        // Emoji shortcodes :name: should work
        let content = "# Test\n\nCheck :material-check: and :fontawesome-solid-star:.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Emoji shortcodes should work: {warnings:?}");
    }

    // =========================================================================
    // Real-world validation regression tests
    // Found during multi-project validation against mkdocs-material, FastAPI,
    // Pydantic, MkDocs, mkdocs-macros-plugin, and mkdocstrings
    // =========================================================================

    #[test]
    fn test_md038_indented_fenced_code_in_admonition() {
        // Indented fenced code blocks inside admonitions are misinterpreted by
        // pulldown-cmark as multi-line code spans. MD038 should not flag these.
        // Found in: mkdocstrings docs/usage/index.md
        let content = concat!(
            "# Test\n\n",
            "!!! example\n",
            "    ```yaml title=\"mkdocs.yml\"\n",
            "    plugins:\n",
            "    - mkdocstrings:\n",
            "        enabled: true\n",
            "    ```\n",
        );
        let warnings = lint_mkdocs(content);
        let md038: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .collect();
        assert!(
            md038.is_empty(),
            "Indented fenced code in admonition should not trigger MD038: {md038:?}"
        );
    }

    #[test]
    fn test_md038_indented_fenced_code_in_tabs() {
        // Tabbed content with indented fenced code blocks
        // Found in: mkdocstrings docs/usage/index.md
        let content = concat!(
            "# Test\n\n",
            "=== \"Markdown\"\n",
            "    ```md\n",
            "    See [installer.records][] to learn about records.\n",
            "    ```\n\n",
            "=== \"Result (HTML)\"\n",
            "    ```html\n",
            "    <p>See <a href=\"url\">installer.records</a></p>\n",
            "    ```\n",
        );
        let warnings = lint_mkdocs(content);
        let md038: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .collect();
        assert!(
            md038.is_empty(),
            "Indented fenced code in tabs should not trigger MD038: {md038:?}"
        );
    }

    #[test]
    fn test_md038_real_issue_still_caught_in_admonition() {
        // Real MD038 violations inside admonitions should still be detected
        let content = "# Test\n\n!!! note\n    Use `  code  ` in your config.\n";
        let warnings = lint_mkdocs(content);
        let md038: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .collect();
        assert!(
            !md038.is_empty(),
            "Real MD038 violations in admonitions should still be caught"
        );
    }

    #[test]
    fn test_md051_blockquote_headings_generate_anchors() {
        // Headings inside blockquotes should generate valid anchors
        // Found in: mkdocs docs/dev-guide/themes.md (> #### locale)
        // and mkdocs docs/user-guide/configuration.md
        let content = concat!(
            "# Main\n\n",
            "> #### locale\n",
            ">\n",
            "> A code representing the language.\n\n",
            "[link](#locale)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        let md051: Vec<_> = warnings.iter().filter(|w| w.message.contains("locale")).collect();
        assert!(
            md051.is_empty(),
            "Blockquote heading anchor '#locale' should be recognized: {md051:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_heading_with_custom_id() {
        // Blockquote headings with custom IDs should also work
        let content = concat!(
            "# Main\n\n",
            "> ## Settings {#my-settings}\n\n",
            "[link](#my-settings)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Blockquote heading custom anchor should be recognized: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_nested_blockquote_heading() {
        // Headings inside nested blockquotes
        let content = concat!("# Main\n\n", ">> ### deep-heading\n\n", "[link](#deep-heading)\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Nested blockquote heading anchor should be recognized: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_mkdocs_duplicate_heading_underscore_dedup() {
        // Python-Markdown uses _N suffix for duplicate headings, not -N
        // Found in: mkdocstrings docs/usage/handlers.md (#templates_1)
        let content = concat!(
            "# Main\n\n",
            "## Templates\n\n",
            "First section.\n\n",
            "## Templates\n\n",
            "Second section.\n\n",
            "[first](#templates)\n",
            "[second-github](#templates-1)\n",
            "[second-mkdocs](#templates_1)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD051LinkFragments::with_anchor_style(
            rumdl_lib::utils::anchor_styles::AnchorStyle::PythonMarkdown,
        );
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "MkDocs _1 dedup suffix should be accepted: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_standard_flavor_no_underscore_dedup() {
        // Without MkDocs flavor, _N suffix should NOT be accepted
        let content = concat!(
            "# Main\n\n",
            "## Templates\n\n",
            "First section.\n\n",
            "## Templates\n\n",
            "Second section.\n\n",
            "[second-mkdocs](#templates_1)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            !warnings.is_empty(),
            "Standard flavor should NOT accept _1 dedup suffix"
        );
    }

    #[test]
    fn test_md051_mkdocs_triple_duplicate_heading() {
        // Three duplicate headings: original, _1, _2
        let content = concat!(
            "# Main\n\n",
            "## API\n\n",
            "First.\n\n",
            "## API\n\n",
            "Second.\n\n",
            "## API\n\n",
            "Third.\n\n",
            "[first](#api)\n",
            "[second](#api_1)\n",
            "[third](#api_2)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD051LinkFragments::with_anchor_style(
            rumdl_lib::utils::anchor_styles::AnchorStyle::PythonMarkdown,
        );
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "MkDocs triple duplicate dedup should work: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_heading_with_closing_hashes() {
        // CommonMark allows closing hash sequences: > ## Heading ##
        // The trailing ## should be stripped when generating the anchor
        let content = concat!("# Main\n\n", "> ## Settings ##\n\n", "[link](#settings)\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Blockquote heading with closing hashes should generate correct anchor: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_heading_closing_hashes_different_count() {
        // Closing hash count doesn't need to match opening hash count
        let content = concat!("# Main\n\n", "> ### Info ###########\n\n", "[link](#info)\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Closing hashes with different count should still generate correct anchor: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_heading_hash_in_text_not_stripped() {
        // A hash that's part of the heading text (not preceded by space) should NOT be stripped
        let content = concat!("# Main\n\n", "> ## C# Language\n\n", "[link](#c-language)\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Hash in heading text (C#) should not be treated as closing sequence: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_heading_only_closing_hashes() {
        // Edge case: heading text is entirely closing hashes (should result in empty after strip)
        // This is degenerate but shouldn't crash
        let content = concat!("# Main\n\n", "> ## ##\n\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        // Should not crash
        let _warnings = rule.check(&ctx).unwrap();
    }

    #[test]
    fn test_md038_indented_fenced_code_in_pymdown_block() {
        // PyMdown blocks (///) with indented fenced code inside
        let content = concat!(
            "# Test\n\n",
            "/// details | Summary\n",
            "    ```python\n",
            "    def foo():\n",
            "        pass\n",
            "    ```\n",
            "///\n",
        );
        let warnings = lint_mkdocs(content);
        let md038: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD038"))
            .collect();
        assert!(
            md038.is_empty(),
            "Indented fenced code in PyMdown block should not trigger MD038: {md038:?}"
        );
    }

    #[test]
    fn test_md051_mkdocs_slash_in_heading_collapses_separators() {
        // Python-Markdown collapses consecutive separators caused by removed punctuation
        // Found in: mkdocstrings docs/usage/index.md
        // Heading: "### Cross-references to other projects / inventories"
        // The `/` is removed and the resulting double space collapsed to single `-`
        let content = concat!(
            "# Main\n\n",
            "### Cross-references to other projects / inventories\n\n",
            "[link](#cross-references-to-other-projects-inventories)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD051LinkFragments::with_anchor_style(
            rumdl_lib::utils::anchor_styles::AnchorStyle::PythonMarkdown,
        );
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "MkDocs slash-in-heading should collapse separators: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_mkdocs_via_lint_mkdocs_auto_anchor_style() {
        // Verify that lint_mkdocs() automatically uses PythonMarkdown anchor style
        let content = concat!(
            "# Main\n\n",
            "### Cross-references to other projects / inventories\n\n",
            "[link](#cross-references-to-other-projects-inventories)\n",
        );
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "lint_mkdocs should auto-use PythonMarkdown anchor style: {md051:?}"
        );
    }

    #[test]
    fn test_md051_mkdocs_cjk_heading_generates_underscore_anchor() {
        // Python-Markdown: CJK-only headings produce empty slug → unique() gives _1, _2, _3
        let content = concat!(
            "# Main\n\n",
            "## 你好世界\n\n",
            "## こんにちは\n\n",
            "## 안녕하세요\n\n",
            "[first](#_1)\n",
            "[second](#_2)\n",
            "[third](#_3)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD051LinkFragments::with_anchor_style(
            rumdl_lib::utils::anchor_styles::AnchorStyle::PythonMarkdown,
        );
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "MkDocs CJK headings should generate _1, _2, _3 anchors: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_standard_cjk_heading_preserves_unicode() {
        // GitHub style preserves Unicode characters in anchors
        let content = concat!("# Main\n\n", "## 你好世界\n\n", "[link](#你好世界)\n",);
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let rule = rumdl_lib::MD051LinkFragments::new();
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "GitHub style should preserve CJK anchors: {warnings:?}"
        );
    }

    #[test]
    fn test_md051_blockquote_empty_heading_text() {
        // CommonMark: `> ## ` is a valid empty heading
        // Python-Markdown generates _1 for it
        let content = concat!(
            "# Main\n\n",
            "> ## \n\n",
            "> ## Real Heading\n\n",
            "[link](#real-heading)\n",
        );
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD051LinkFragments::with_anchor_style(
            rumdl_lib::utils::anchor_styles::AnchorStyle::PythonMarkdown,
        );
        let warnings = rule.check(&ctx).unwrap();
        assert!(
            warnings.is_empty(),
            "Empty blockquote heading should not break subsequent anchor generation: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 13: CROSS-FLAVOR COMPARISON TESTS
// Verify MkDocs flavor behaves differently from Standard where expected
// =============================================================================

mod cross_flavor_tests {
    use super::*;

    #[test]
    fn test_admonition_standard_vs_mkdocs() {
        let content = "# Test\n\n!!! note\n    Content.\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should recognize this, Standard may flag it
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len(),
            "MkDocs should be more lenient with admonitions.\n\
             MkDocs: {mkdocs_warnings:?}\nStandard: {standard_warnings:?}"
        );
    }

    #[test]
    fn test_content_tabs_standard_vs_mkdocs() {
        let content = "# Test\n\n=== \"Tab\"\n    Content.\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should recognize tabs, Standard may see issues
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len(),
            "MkDocs should be more lenient with content tabs.\n\
             MkDocs: {mkdocs_warnings:?}\nStandard: {standard_warnings:?}"
        );
    }

    #[test]
    fn test_mkdocstrings_standard_vs_mkdocs() {
        let content = "# Test\n\n::: module.Class\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should recognize mkdocstrings
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len(),
            "MkDocs should be more lenient with mkdocstrings.\n\
             MkDocs: {mkdocs_warnings:?}\nStandard: {standard_warnings:?}"
        );
    }

    #[test]
    fn test_keys_extension_standard_vs_mkdocs() {
        let content = "# Test\n\nPress ++ctrl+c++ to copy.\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // Both should handle this gracefully
        // Document any difference in behavior
        let _ = (mkdocs_warnings, standard_warnings);
    }

    #[test]
    fn test_mark_extension_standard_vs_mkdocs() {
        let content = "# Test\n\nThis is ==highlighted== text.\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // Document behavior difference
        let _ = (mkdocs_warnings, standard_warnings);
    }

    #[test]
    fn test_math_standard_vs_mkdocs() {
        let content = "# Test\n\nInline $x^2$ and block:\n\n$$\ny = mx + b\n$$\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // Math should work in both, but MkDocs may be more permissive
        let _ = (mkdocs_warnings, standard_warnings);
    }

    #[test]
    fn test_snippet_standard_vs_mkdocs() {
        let content = "# Test\n\n--8<-- \"file.md\"\n";

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should recognize snippets
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len(),
            "MkDocs should be more lenient with snippets.\n\
             MkDocs: {mkdocs_warnings:?}\nStandard: {standard_warnings:?}"
        );
    }

    #[test]
    fn test_complex_document_both_flavors() {
        let content = r#"# Complex Document

!!! note "Admonition"
    Content here.

=== "Tab 1"
    Tab content.

::: module.Class

Regular paragraph.
"#;

        let mkdocs_warnings = lint_mkdocs(content);
        let standard_warnings = lint_standard(content);

        // MkDocs should have fewer warnings for extension syntax
        assert!(
            mkdocs_warnings.len() <= standard_warnings.len(),
            "MkDocs should handle extensions better.\n\
             MkDocs ({} warnings): {mkdocs_warnings:?}\n\
             Standard ({} warnings): {standard_warnings:?}",
            mkdocs_warnings.len(),
            standard_warnings.len()
        );
    }
}

// =============================================================================
// PART 14: LINE ENDING TESTS
// Verify extensions work with different line ending styles
// =============================================================================

mod line_ending_tests {
    use super::*;

    #[test]
    fn test_crlf_admonitions() {
        let content = "# Test\r\n\r\n!!! note\r\n    Content.\r\n";
        let warnings = lint_mkdocs(content);
        // Should handle CRLF gracefully
        assert!(warnings.is_empty(), "CRLF admonitions should work: {warnings:?}");
    }

    #[test]
    fn test_crlf_content_tabs() {
        let content = "# Test\r\n\r\n=== \"Tab\"\r\n\r\n    Content.\r\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "CRLF content tabs should work: {warnings:?}");
    }

    #[test]
    fn test_crlf_mkdocstrings() {
        let content = "# Test\r\n\r\n::: module.Class\r\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "CRLF mkdocstrings should work: {warnings:?}");
    }

    #[test]
    fn test_crlf_inline_extensions() {
        let content = "# Test\r\n\r\nText with ==mark== and ++key++.\r\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "CRLF inline extensions should work: {warnings:?}");
    }

    #[test]
    fn test_crlf_nested_extensions() {
        let content = "# Test\r\n\r\n!!! note\r\n\r\n    === \"Tab\"\r\n\r\n        Content.\r\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "CRLF nested extensions should work: {warnings:?}");
    }

    #[test]
    fn test_mixed_line_endings() {
        // Mix of LF and CRLF (common in cross-platform projects)
        let content = "# Test\n\n!!! note\r\n    Content.\n\n=== \"Tab\"\r\n    Tab.\n";
        // Should handle gracefully without panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_cr_only_line_endings() {
        // Classic Mac line endings (rare but possible)
        let content = "# Test\r\r!!! note\r    Content.\r";
        // Should handle gracefully without panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_crlf_complex_document() {
        let content = "# Document\r\n\r\n!!! note \"Title\"\r\n    Content with ==mark==.\r\n\r\n\
                       === \"Tab A\"\r\n\r\n    Tab content.\r\n\r\n\
                       === \"Tab B\"\r\n\r\n    More content.\r\n\r\n\
                       ::: module.Class\r\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "CRLF complex document should work: {warnings:?}");
    }
}

// =============================================================================
// PART 15: MIXED INDENTATION TESTS
// Verify extensions handle mixed tabs and spaces
// =============================================================================

mod mixed_indentation_tests {
    use super::*;

    #[test]
    fn test_admonition_with_tab_indent() {
        let content = "# Test\n\n!!! note\n\tContent with tab.\n";
        // Tab indentation may trigger MD010, but should not crash
        let warnings = lint_mkdocs(content);
        // Filter out MD010 (hard tabs) to check extension handling
        let non_tab_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() != Some("MD010"))
            .collect();
        assert!(
            non_tab_warnings.is_empty(),
            "Tab-indented admonition should work (except MD010): {non_tab_warnings:?}"
        );
    }

    #[test]
    fn test_content_tab_with_tab_indent() {
        let content = "# Test\n\n=== \"Tab\"\n\n\tContent with tab.\n";
        let warnings = lint_mkdocs(content);
        let non_tab_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() != Some("MD010"))
            .collect();
        assert!(
            non_tab_warnings.is_empty(),
            "Tab-indented content tab should work: {non_tab_warnings:?}"
        );
    }

    #[test]
    fn test_mixed_spaces_and_tabs_in_admonition() {
        // 2 spaces + tab + 2 spaces (weird but possible)
        let content = "# Test\n\n!!! note\n  \t  Mixed indent.\n";
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_2_space_indent_admonition() {
        let content = "# Test\n\n!!! note\n  Two space indent.\n";
        // May or may not be recognized as admonition content
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_8_space_indent_admonition() {
        let content = "# Test\n\n!!! note\n        Eight space indent.\n";
        // Extra indentation should still work
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_nested_mixed_indentation() {
        let content = "# Test\n\n!!! note\n    === \"Tab\"\n\t    \tMixed deep.\n";
        // Complex mixed indentation
        let _ = lint_mkdocs(content);
    }
}

// =============================================================================
// PART 16: EXTENSIONS INSIDE BLOCKQUOTES
// Verify extensions work inside blockquote contexts
// =============================================================================

mod blockquote_tests {
    use super::*;

    #[test]
    fn test_inline_extensions_in_blockquote() {
        let content = "# Test\n\n> Quote with ==highlighted== text.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Inline extensions in blockquote should work: {warnings:?}"
        );
    }

    #[test]
    fn test_keys_in_blockquote() {
        let content = "# Test\n\n> Press ++ctrl+c++ to copy.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Keys in blockquote should work: {warnings:?}");
    }

    #[test]
    fn test_math_in_blockquote() {
        let content = "# Test\n\n> The equation $E = mc^2$ is famous.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Math in blockquote should work: {warnings:?}");
    }

    #[test]
    fn test_critic_in_blockquote() {
        let content = "# Test\n\n> Text with {++addition++} here.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Critic markup in blockquote should work: {warnings:?}"
        );
    }

    #[test]
    fn test_nested_blockquote_with_extensions() {
        let content = "# Test\n\n> Level 1\n> > Level 2 with ==mark==.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Nested blockquote with extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_multiline_blockquote_with_extensions() {
        let content = r#"# Test

> This is a blockquote.
> It has ==highlighted== text.
> And ++keyboard++ keys.
> Plus $math$ expressions.
"#;
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Multiline blockquote with extensions should work: {warnings:?}"
        );
    }

    #[test]
    fn test_blockquote_admonition_interaction() {
        // Admonition syntax inside blockquote (unusual but possible)
        let content = "# Test\n\n> !!! note\n>     This is unusual.\n";
        // Should handle gracefully without panic
        let _ = lint_mkdocs(content);
    }
}

// =============================================================================
// PART 17: ESCAPE CHARACTER TESTS
// Verify extensions handle escaped characters correctly
// =============================================================================

mod escape_tests {
    use super::*;

    #[test]
    fn test_escaped_mark_syntax() {
        // \== should not be treated as mark start
        let content = "# Test\n\nThis is \\==not marked\\== text.\n";
        let warnings = lint_mkdocs(content);
        // Should handle gracefully
        let _ = warnings;
    }

    #[test]
    fn test_escaped_keys_syntax() {
        // \++ should not be treated as keys
        let content = "# Test\n\nThis is \\++not a key\\++ combo.\n";
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_escaped_math_syntax() {
        // \$ should not start math
        let content = "# Test\n\nPrice is \\$100 dollars.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Escaped dollar should work: {warnings:?}");
    }

    #[test]
    fn test_escaped_caret_syntax() {
        // \^ should not start superscript
        let content = "# Test\n\nUse \\^text\\^ for literal carets.\n";
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_escaped_tilde_syntax() {
        // \~ should not start subscript
        let content = "# Test\n\nUse \\~text\\~ for literal tildes.\n";
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_backslash_in_admonition_title() {
        let content = "# Test\n\n!!! note \"Path: C:\\\\Users\"\n    Content.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Backslash in admonition title should work: {warnings:?}"
        );
    }

    #[test]
    fn test_backslash_in_tab_title() {
        let content = "# Test\n\n=== \"C:\\\\Path\"\n\n    Content.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Backslash in tab title should work: {warnings:?}");
    }

    #[test]
    fn test_special_chars_in_code_spans() {
        let content = "# Test\n\nUse `==` for mark and `++` for keys.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Special chars in code spans should work: {warnings:?}"
        );
    }

    #[test]
    fn test_html_entities_with_extensions() {
        let content = "# Test\n\nText with &amp; ==marked== &lt;content&gt;.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "HTML entities with extensions should work: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 18: MALFORMED EXTENSION RECOVERY TESTS
// Verify graceful handling of broken/invalid extension syntax
// =============================================================================

mod malformed_tests {
    use super::*;

    #[test]
    fn test_unclosed_admonition() {
        // Admonition without content
        let content = "# Test\n\n!!! note\n\nNext paragraph.\n";
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_admonition_wrong_indent() {
        // Content not indented properly
        let content = "# Test\n\n!!! note\nNot indented.\n";
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_tab_missing_quotes() {
        // Tab without quotes around title
        let content = "# Test\n\n=== Tab\n    Content.\n";
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_tab_unclosed_quote() {
        // Tab with unclosed quote
        let content = "# Test\n\n=== \"Tab\n    Content.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_mkdocstrings_no_path() {
        // ::: without path
        let content = "# Test\n\n:::\n";
        // Should handle gracefully
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_unclosed_mark() {
        // == without closing
        let content = "# Test\n\nThis is ==unclosed mark.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_unclosed_keys() {
        // ++ without closing
        let content = "# Test\n\nPress ++ctrl+c to copy.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_unclosed_math() {
        // $ without closing
        let content = "# Test\n\nEquation $x + y without closing.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_unclosed_superscript() {
        // ^ without closing
        let content = "# Test\n\nText with ^unclosed super.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_unclosed_subscript() {
        // ~ without closing
        let content = "# Test\n\nH~2 without closing O.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_nested_unclosed_extensions() {
        // Multiple unclosed
        let content = "# Test\n\n==mark with ^super and ~sub all unclosed.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_mismatched_extension_markers() {
        // Different opening and closing
        let content = "# Test\n\nThis ==opens but ^^ closes wrong.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_empty_extension_markers() {
        // Empty content between markers
        let content = "# Test\n\nEmpty ==== mark and ++++ keys.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_only_markers_no_content() {
        // Just markers
        let content = "# Test\n\n== ++ ^^ ~~ ::\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_deeply_broken_nesting() {
        let content = r#"# Test

!!! note
    === "Tab
        !!! warning
            Content without proper closing.

    === "Another
        More broken.
"#;
        // Should not panic even with severely broken nesting
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_interleaved_broken_extensions() {
        let content = "# Test\n\nStart ==mark then ^super then ~sub all ==cross^crossed~.\n";
        // Should not panic
        let _ = lint_mkdocs(content);
    }
}

// =============================================================================
// PART 19: EDGE POSITION TESTS
// Verify extensions at unusual document positions
// =============================================================================

mod position_tests {
    use super::*;

    #[test]
    fn test_extension_as_first_content() {
        // Extension as very first thing (no heading)
        let content = "!!! note\n    First thing in document.\n";
        // May have warnings but should not panic
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_extension_at_eof_no_newline() {
        let content = "# Test\n\n!!! note\n    No trailing newline";
        let _ = lint_mkdocs(content);
    }

    #[test]
    fn test_inline_extension_at_line_start() {
        let content = "# Test\n\n==highlighted== starts the line.\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Extension at line start should work: {warnings:?}");
    }

    #[test]
    fn test_inline_extension_at_line_end() {
        let content = "# Test\n\nLine ends with ==highlighted==\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Extension at line end should work: {warnings:?}");
    }

    #[test]
    fn test_extension_only_line() {
        let content = "# Test\n\n==only content==\n";
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Extension-only line should work: {warnings:?}");
    }

    #[test]
    fn test_extension_after_many_blank_lines() {
        let content = "# Test\n\n\n\n\n\n!!! note\n    After many blanks.\n";
        // MD012 may fire, but extension should still work
        let warnings = lint_mkdocs(content);
        let extension_issues: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() != Some("MD012"))
            .collect();
        assert!(
            extension_issues.is_empty(),
            "Extension after blanks should work: {extension_issues:?}"
        );
    }

    #[test]
    fn test_extension_between_headings() {
        let content = "# Heading 1\n\n!!! note\n    Between headings.\n\n## Heading 2\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Extension between headings should work: {warnings:?}"
        );
    }

    #[test]
    fn test_extension_in_list_item() {
        let content = "# Test\n\n- Item with ==mark== inside.\n- Another with ++key++.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Extensions in list items should work: {warnings:?}"
        );
    }

    #[test]
    fn test_extension_after_code_block() {
        let content = "# Test\n\n```python\ncode()\n```\n\n!!! note\n    After code.\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Extension after code block should work: {warnings:?}"
        );
    }

    #[test]
    fn test_extension_before_code_block() {
        let content = "# Test\n\n!!! note\n    Before code.\n\n```python\ncode()\n```\n";
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "Extension before code block should work: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 20: COMPREHENSIVE INTEGRATION TESTS
// Full document tests combining multiple aspects
// =============================================================================

mod integration_tests {
    use super::*;

    #[test]
    fn test_real_world_api_docs() {
        let content = r#"---
title: API Reference
---

# API Reference

[TOC]

## Overview

This module provides the core functionality.

!!! warning "Deprecation Notice"
    The old API is deprecated. Use the new one.

## Classes

::: mypackage.core.Client
    options:
        show_source: true
        members:
            - connect
            - disconnect

### Usage

=== "Basic"

    ```python
    client = Client()
    client.connect()
    ```

=== "Advanced"

    ```python
    client = Client(timeout=30)
    client.connect(retry=True)
    ```

## Keyboard Shortcuts

| Action | Shortcut |
|--------|----------|
| Copy   | ++ctrl+c++ |
| Paste  | ++ctrl+v++ |
| Save   | ++ctrl+s++ |

## Math Examples

The quadratic formula: $x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$

## See Also

- [OtherClass][] for related functionality.

*[API]: Application Programming Interface

[^1]: Additional reference.
"#;
        let warnings = lint_mkdocs(content);
        // Real-world doc should have minimal warnings
        assert!(
            warnings.len() <= 2,
            "Real-world API docs should work well: {warnings:?}"
        );
    }

    #[test]
    fn test_real_world_tutorial() {
        let content = r#"# Getting Started Tutorial

!!! info "Prerequisites"
    - Python 3.8+
    - pip installed

## Installation

=== "pip"

    ```bash
    pip install mypackage
    ```

=== "poetry"

    ```bash
    poetry add mypackage
    ```

=== "conda"

    ```bash
    conda install mypackage
    ```

## First Steps

1. Import the module
2. Create a client
3. Connect to the server

??? example "Complete Example"

    ```python
    from mypackage import Client

    client = Client()
    client.connect()
    print("Connected!")
    ```

## Tips

!!! tip
    Use ++ctrl+c++ to interrupt long-running operations.

!!! warning
    Always call `disconnect()` when done.

## Next Steps

See the [API Reference](api.md) for details.
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Tutorial should have no warnings: {warnings:?}");
    }

    #[test]
    fn test_real_world_changelog() {
        let content = r#"# Changelog

## [2.0.0] - 2024-01-15

!!! danger "Breaking Changes"
    - Removed deprecated `old_function()`
    - Changed signature of `process()`

### Added

- New ==highlighted== feature
- Support for ^superscript^ text

### Fixed

- Bug in {~~old~>new~~} handling

## [1.5.0] - 2024-01-01

??? note "Migration Guide"
    Follow these steps to upgrade:

    1. Update dependencies
    2. Run migrations
    3. Test thoroughly
"#;
        let warnings = lint_mkdocs(content);
        assert!(warnings.is_empty(), "Changelog should have no warnings: {warnings:?}");
    }

    #[test]
    fn test_stress_all_extensions_combined() {
        let content = r#"---
title: Complete Test
---

# Complete Extension Test

[TOC]

*[HTML]: Hypertext Markup Language

Text with ==mark==, ^super^, ~sub~, ++key++, $math$, and :emoji:.

!!! note "Admonition"

    === "Tab 1"

        ```python title="code.py"
        print("Hello")
        ```

    === "Tab 2"

        ::: module.Class

??? tip "Collapsible"

    Content with {++critic++} markup.

> Blockquote with ==extensions==.

Term
:   Definition with ++keys++.

| Header | Value |
|--------|-------|
| Key    | ++v++ |

$$
E = mc^2
$$

--8<-- "snippet.md"

[Reference][] link.

[^1]: Footnote.
"#;
        let warnings = lint_mkdocs(content);
        // With all extensions, expect very few warnings
        assert!(
            warnings.len() <= 3,
            "Complete test should have minimal warnings: {warnings:?}"
        );
    }
}

// =============================================================================
// PART 7: PER-EXTENSION REGRESSION TESTS
// Each extension gets a dedicated test verifying:
// 1. Zero warnings from lint (check mode)
// 2. Content unchanged after fix (round-trip safety)
// =============================================================================

mod per_extension_regression {
    use super::*;

    /// Apply all fixable rules and return the fixed content.
    /// Verifies round-trip: valid content should not change after fix.
    fn assert_check_and_fix_roundtrip(content: &str, extension_name: &str) {
        // Step 1: Lint should produce zero warnings
        let warnings = lint_mkdocs(content);
        assert!(
            warnings.is_empty(),
            "{extension_name}: expected zero warnings but got {}: {warnings:?}",
            warnings.len()
        );

        // Step 2: Fix should not modify valid content (round-trip safety)
        let config = create_mkdocs_config();
        let rules = filter_rules(&all_rules(&config), &config.global);
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        // MD054 intentionally returns Err (doesn't support auto-fix)
        let unfixable_rules: &[&str] = &["MD054"];
        for rule in &rules {
            match rule.fix(&ctx) {
                Ok(fixed) => {
                    assert_eq!(
                        fixed,
                        content,
                        "{extension_name}: rule {} modified valid content during fix",
                        rule.name()
                    );
                }
                Err(_) => {
                    assert!(
                        unfixable_rules.contains(&rule.name()),
                        "{extension_name}: unexpected Err from rule {} fix()",
                        rule.name()
                    );
                }
            }
        }
    }

    // ---- Python-Markdown Extensions ----

    #[test]
    fn test_abbr_roundtrip() {
        let content = "# Abbreviations\n\nThe HTML specification is maintained by the W3C.\n\n*[HTML]: Hyper Text Markup Language\n*[W3C]: World Wide Web Consortium\n";
        assert_check_and_fix_roundtrip(content, "abbr");
    }

    #[test]
    fn test_admonition_roundtrip() {
        let content = "# Admonitions\n\n!!! note \"Custom Title\"\n    This is a note admonition with a custom title.\n\n!!! warning\n    This is a warning.\n";
        assert_check_and_fix_roundtrip(content, "admonition");
    }

    #[test]
    fn test_attr_list_roundtrip() {
        // Attribute lists on headings and paragraphs
        let content = "# Attributes { #custom-id .special }\n\nA paragraph with attributes.\n{ .highlight }\n\nAnother paragraph.\n{ #other-id data-value=\"test\" }\n";
        assert_check_and_fix_roundtrip(content, "attr_list");
    }

    #[test]
    fn test_def_list_roundtrip() {
        let content = "# Definitions\n\nTerm 1\n:   Definition for term 1.\n\nTerm 2\n:   Definition for term 2.\n";
        assert_check_and_fix_roundtrip(content, "def_list");
    }

    #[test]
    fn test_footnotes_roundtrip() {
        let content = "# Footnotes\n\nText with a footnote reference.[^1]\n\nAnother reference.[^note]\n\n[^1]: First footnote definition.\n\n[^note]: Named footnote definition.\n";
        assert_check_and_fix_roundtrip(content, "footnotes");
    }

    #[test]
    fn test_md_in_html_roundtrip() {
        let content = "# HTML with Markdown\n\n<div markdown>\n\nThis is **markdown** inside HTML.\n\n- List item 1\n- List item 2\n\n</div>\n";
        assert_check_and_fix_roundtrip(content, "md_in_html");
    }

    #[test]
    fn test_toc_roundtrip() {
        let content =
            "# Table of Contents\n\n[TOC]\n\n## Section One\n\nContent here.\n\n## Section Two\n\nMore content.\n";
        assert_check_and_fix_roundtrip(content, "toc");
    }

    #[test]
    fn test_tables_roundtrip() {
        let content = "# Tables\n\n| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1   | Cell 2   |\n| Cell 3   | Cell 4   |\n";
        assert_check_and_fix_roundtrip(content, "tables");
    }

    #[test]
    fn test_meta_roundtrip() {
        let content = "---\nauthor: Test Author\ntags:\n  - test\n  - mkdocs\n---\n\n# Meta Extension\n\nContent after frontmatter.\n";
        assert_check_and_fix_roundtrip(content, "meta");
    }

    #[test]
    fn test_fenced_code_roundtrip() {
        let content =
            "# Fenced Code\n\n```python\nprint(\"hello\")\n```\n\n```yaml title=\"config.yml\"\nkey: value\n```\n";
        assert_check_and_fix_roundtrip(content, "fenced_code");
    }

    // ---- PyMdown Extensions ----

    #[test]
    fn test_arithmatex_roundtrip() {
        let content =
            "# Math\n\nInline math: $E = mc^2$\n\nBlock math:\n\n$$\n\\frac{n!}{k!(n-k)!} = \\binom{n}{k}\n$$\n";
        assert_check_and_fix_roundtrip(content, "arithmatex");
    }

    #[test]
    fn test_caret_roundtrip() {
        let content = "# Caret\n\nThis is ^^inserted text^^ and H^2^O is water.\n";
        assert_check_and_fix_roundtrip(content, "caret");
    }

    #[test]
    fn test_mark_roundtrip() {
        let content = "# Mark\n\nThis is ==marked text== for highlighting.\n";
        assert_check_and_fix_roundtrip(content, "mark");
    }

    #[test]
    fn test_tilde_roundtrip() {
        let content = "# Tilde\n\nThis is ~~deleted text~~ and H~2~O is water.\n";
        assert_check_and_fix_roundtrip(content, "tilde");
    }

    #[test]
    fn test_details_roundtrip() {
        let content = "# Details\n\n??? note \"Collapsible\"\n    This content is hidden by default.\n\n???+ tip \"Open by Default\"\n    This content is visible.\n";
        assert_check_and_fix_roundtrip(content, "details");
    }

    #[test]
    fn test_emoji_roundtrip() {
        let content = "# Emoji\n\nA thumbs up :thumbsup: and a :material-check: icon.\n";
        assert_check_and_fix_roundtrip(content, "emoji");
    }

    #[test]
    fn test_inlinehilite_roundtrip() {
        let content = "# Inline Highlight\n\nUse `#!python print(\"hello\")` for inline code.\n";
        assert_check_and_fix_roundtrip(content, "inlinehilite");
    }

    #[test]
    fn test_keys_roundtrip() {
        let content = "# Keys\n\nPress ++ctrl+alt+del++ to open task manager.\n";
        assert_check_and_fix_roundtrip(content, "keys");
    }

    #[test]
    fn test_smartsymbols_roundtrip() {
        let content = "# Smart Symbols\n\nCopyright (c) and trademark (tm) and arrows -->.\n";
        assert_check_and_fix_roundtrip(content, "smartsymbols");
    }

    #[test]
    fn test_snippets_roundtrip() {
        let content = "# Snippets\n\nContent before snippet.\n\n--8<-- \"path/to/file.md\"\n\nContent after snippet.\n";
        assert_check_and_fix_roundtrip(content, "snippets");
    }

    #[test]
    fn test_superfences_roundtrip() {
        let content =
            "# SuperFences\n\n```python hl_lines=\"2 3\"\ndef hello():\n    print(\"hello\")\n    return True\n```\n";
        assert_check_and_fix_roundtrip(content, "superfences");
    }

    #[test]
    fn test_tabbed_roundtrip() {
        let content = "# Tabs\n\n=== \"Python\"\n\n    ```python\n    print(\"hello\")\n    ```\n\n=== \"JavaScript\"\n\n    ```javascript\n    console.log(\"hello\")\n    ```\n";
        assert_check_and_fix_roundtrip(content, "tabbed");
    }

    #[test]
    fn test_tasklist_roundtrip() {
        let content = "# Tasks\n\n- [x] Completed task\n- [ ] Pending task\n- [x] Another done\n";
        assert_check_and_fix_roundtrip(content, "tasklist");
    }

    #[test]
    fn test_betterem_roundtrip() {
        let content = "# BetterEm\n\nThis is *emphasized* text and **strong** text.\n\nNested: ***bold and italic***\n";
        assert_check_and_fix_roundtrip(content, "betterem");
    }

    #[test]
    fn test_critic_roundtrip() {
        let content = "# Critic Markup\n\nThis is {++added text++} and {--removed text--}.\n\nThis is {~~old~>new~~} replacement.\n\n{==highlighted text==} and {>>comment text<<}.\n";
        assert_check_and_fix_roundtrip(content, "critic");
    }

    #[test]
    fn test_pymdown_blocks_details_roundtrip() {
        let content =
            "# PyMdown Blocks\n\n/// details | Click to expand\n    type: warning\n\nDetailed content inside.\n\n///\n";
        assert_check_and_fix_roundtrip(content, "pymdown_blocks_details");
    }

    #[test]
    fn test_pymdown_blocks_admonition_roundtrip() {
        let content =
            "# PyMdown Blocks\n\n/// admonition | Important Notice\n    type: note\n\nAdmonition content.\n\n///\n";
        assert_check_and_fix_roundtrip(content, "pymdown_blocks_admonition");
    }

    #[test]
    fn test_pymdown_blocks_caption_roundtrip() {
        let content = "# PyMdown Blocks\n\n/// caption\nFigure 1: Diagram description\n///\n";
        assert_check_and_fix_roundtrip(content, "pymdown_blocks_caption");
    }

    #[test]
    fn test_pymdown_blocks_html_roundtrip() {
        let content =
            "# PyMdown Blocks\n\n/// html | div.custom-class\n\nCustom HTML content with **markdown**.\n\n///\n";
        assert_check_and_fix_roundtrip(content, "pymdown_blocks_html");
    }

    // ---- mkdocstrings ----

    #[test]
    fn test_mkdocstrings_roundtrip() {
        let content =
            "# API Reference\n\n::: my_module.MyClass\n    options:\n      show_source: true\n      heading_level: 2\n";
        assert_check_and_fix_roundtrip(content, "mkdocstrings");
    }

    #[test]
    fn test_mkdocstrings_cross_references_roundtrip() {
        // Dotted paths are recognized as MkDocs auto-references by MD052
        let content = "# Cross References\n\nSee [my_module.MyClass][] and [my_module.function][] for details.\n";
        assert_check_and_fix_roundtrip(content, "mkdocstrings_cross_references");
    }

    // ---- MD051 footnote anchor handling ----

    #[test]
    fn test_md051_footnote_anchors_no_false_positive() {
        let content = "# Footnote Anchors\n\nSee the footnote.[^1]\n\n[:arrow_down: Jump to footnote](#fn:1)\n\n[:arrow_down: Jump to ref](#fnref:1)\n\n[^1]: The footnote content.\n";
        let warnings = lint_mkdocs(content);
        let md051_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051_warnings.is_empty(),
            "MD051 should not flag MkDocs footnote anchors: {md051_warnings:?}"
        );
    }

    #[test]
    fn test_md051_option_anchors_no_false_positive() {
        let content = "# Option Anchors\n\nSee the [abstract](#+type:abstract) type.\n\nConfigure [option](#+config.theme.name) in mkdocs.yml.\n";
        let warnings = lint_mkdocs(content);
        let md051_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051_warnings.is_empty(),
            "MD051 should not flag MkDocs option anchors: {md051_warnings:?}"
        );
    }

    // ---- MD051 negative tests: invalid fragments SHOULD still warn ----

    #[test]
    fn test_md051_still_flags_invalid_fragments_in_mkdocs() {
        let content =
            "# Valid Heading\n\n## Another Heading\n\n[link](#nonexistent-heading)\n\n[link](#also-not-real)\n";
        let warnings = lint_mkdocs(content);
        let md051_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert_eq!(
            md051_warnings.len(),
            2,
            "MD051 should flag invalid fragments even in MkDocs mode: {md051_warnings:?}"
        );
    }

    #[test]
    fn test_md051_footnote_skip_only_applies_to_fn_prefix() {
        // #fn: and #fnref: are skipped, but #function or #fnord are NOT
        let content = "# Heading\n\n[link](#function)\n\n[link](#fnord)\n";
        let warnings = lint_mkdocs(content);
        let md051_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert_eq!(
            md051_warnings.len(),
            2,
            "MD051 should only skip #fn: and #fnref: prefixes, not #function or #fnord: {md051_warnings:?}"
        );
    }

    #[test]
    fn test_md051_option_skip_requires_dot_or_colon() {
        // #+type:abstract and #+toc.slugify are skipped (Material option refs)
        // but #+plain (no dot or colon) should still be flagged
        let content = "# Heading\n\n[link](#+plain)\n\n[link](#+also-invalid)\n";
        let warnings = lint_mkdocs(content);
        let md051_warnings: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert_eq!(
            md051_warnings.len(),
            2,
            "MD051 should flag #+fragments without dot or colon: {md051_warnings:?}"
        );
    }

    // ---- End-to-end fmt test ----

    #[test]
    fn test_fmt_preserves_all_extensions() {
        // Document with fixable issues (trailing spaces, extra blanks) alongside
        // every category of MkDocs extension syntax
        let content = "# Format Test\n\n\
!!! note \"Important\"\n\
    Content with trailing spaces.   \n\n\
=== \"Tab 1\"\n\n\
    Tab content.\n\n\
::: my_module.Class\n\
    options:\n\
      show_source: true\n\n\
/// details | Summary\n\
    type: note\n\n\
Details content.\n\n\
///\n\n\n\
Text with ==mark== and ^^caret^^ and ++ctrl+c++.\n\n\
$E = mc^2$\n\n\
[^1]: A footnote.\n";

        let config = create_mkdocs_config();
        let rules = filter_rules(&all_rules(&config), &config.global);
        let warnings = lint(content, &rules, false, MarkdownFlavor::MkDocs, None, None).unwrap();

        // Verify the test document triggers the expected rules
        let md009_count = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD009"))
            .count();
        let md012_count = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD012"))
            .count();
        assert!(md009_count > 0, "Test document should trigger MD009 (trailing spaces)");
        assert!(md012_count > 0, "Test document should trigger MD012 (multiple blanks)");

        // Apply fixes using FixCoordinator (same path as real rumdl fmt)
        let coordinator = rumdl_lib::fix_coordinator::FixCoordinator::new();
        let mut fixed_content = content.to_string();
        let result = coordinator
            .apply_fixes_iterative(&rules, &warnings, &mut fixed_content, &config, 10, None)
            .expect("Fix should succeed");
        assert!(result.rules_fixed > 0, "Should have fixed some issues");

        // Verify all extension constructs are preserved
        assert!(fixed_content.contains("!!! note"), "Admonitions preserved");
        assert!(fixed_content.contains("=== \"Tab 1\""), "Tabs preserved");
        assert!(fixed_content.contains("::: my_module.Class"), "mkdocstrings preserved");
        assert!(fixed_content.contains("/// details"), "PyMdown blocks preserved");
        assert!(fixed_content.contains("==mark=="), "Mark preserved");
        assert!(fixed_content.contains("^^caret^^"), "Caret preserved");
        assert!(fixed_content.contains("++ctrl+c++"), "Keys preserved");
        assert!(fixed_content.contains("$E = mc^2$"), "Math preserved");
        assert!(fixed_content.contains("[^1]:"), "Footnotes preserved");

        // Verify fixes were actually applied
        assert!(!fixed_content.contains("   \n"), "Trailing spaces should be removed");

        // Re-lint the fixed content - rules that were fixed should produce zero warnings
        let re_warnings = lint(&fixed_content, &rules, false, MarkdownFlavor::MkDocs, None, None).unwrap();

        // MD009 (trailing spaces) and MD012 (multiple blanks) should be fully resolved
        let trailing_space_warnings: Vec<_> = re_warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD009"))
            .collect();
        assert!(
            trailing_space_warnings.is_empty(),
            "MD009 should produce zero warnings after fix: {trailing_space_warnings:?}"
        );

        let multiple_blank_warnings: Vec<_> = re_warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD012"))
            .collect();
        assert!(
            multiple_blank_warnings.is_empty(),
            "MD012 should produce zero warnings after fix: {multiple_blank_warnings:?}"
        );
    }
}

// =============================================================================
// MD051: Link fragment detection inside MkDocs admonitions and content tabs
// Issue #464: pulldown-cmark treats 4-space-indented admonition/tab content as
// indented code blocks, causing parse_links to miss links entirely.
// =============================================================================

mod md051_admonition_link_detection {
    use super::*;

    #[test]
    fn test_broken_link_inside_admonition_is_detected() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [two](#two)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside admonition"
        );
    }

    #[test]
    fn test_valid_link_inside_admonition_no_warning() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [one](#one)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "MD051 should not flag valid link '#one' inside admonition: {md051:?}"
        );
    }

    #[test]
    fn test_broken_link_inside_content_tab_is_detected() {
        let content = "# Test\n\n## One\n\n=== \"Tab 1\"\n\n    See [two](#two)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside content tab"
        );
    }

    #[test]
    fn test_valid_link_inside_content_tab_no_warning() {
        let content = "# Test\n\n## One\n\n=== \"Tab 1\"\n\n    See [one](#one)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "MD051 should not flag valid link '#one' inside content tab: {md051:?}"
        );
    }

    #[test]
    fn test_link_inside_fenced_code_in_admonition_not_flagged() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    ```markdown\n    See [two](#two)\n    ```\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "MD051 should not flag links inside fenced code blocks within admonitions: {md051:?}"
        );
    }

    #[test]
    fn test_broken_link_with_fenced_code_in_same_admonition() {
        // Fenced code block coexists with a link in the same admonition.
        // The link should still be detected even though pulldown-cmark may merge
        // the indented content into a single code block range.
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [two](#two)\n\n    ```python\n    code = \"example\"\n    ```\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' even when fenced code exists in same admonition"
        );
    }

    #[test]
    fn test_broken_link_inside_nested_admonition_is_detected() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    !!! warning\n\n        See [two](#two)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside nested admonition"
        );
    }

    #[test]
    fn test_mixed_valid_and_broken_links_in_admonition() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [one](#one) and [two](#two)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert_eq!(
            md051.len(),
            1,
            "MD051 should flag only the broken link '#two', not '#one': {md051:?}"
        );
    }

    #[test]
    fn test_lint_context_parses_links_inside_admonitions() {
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [one](#one)\n\n## Three\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let admonition_links: Vec<_> = ctx.links.iter().filter(|l| l.url.contains("#one")).collect();
        assert!(
            !admonition_links.is_empty(),
            "LintContext should parse links inside MkDocs admonitions, found: {:?}",
            ctx.links.iter().map(|l| l.url.as_ref()).collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_lint_context_parses_links_inside_content_tabs() {
        let content = "# Test\n\n## One\n\n=== \"Tab 1\"\n\n    See [one](#one)\n\n## Three\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let tab_links: Vec<_> = ctx.links.iter().filter(|l| l.url.contains("#one")).collect();
        assert!(
            !tab_links.is_empty(),
            "LintContext should parse links inside MkDocs content tabs, found: {:?}",
            ctx.links.iter().map(|l| l.url.as_ref()).collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_collapsible_admonition_link_detection() {
        let content = "# Test\n\n## One\n\n??? note\n\n    See [two](#two)\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside collapsible admonition"
        );
    }

    #[test]
    fn test_standard_flavor_admonition_not_affected() {
        // In Standard flavor, 4-space-indented content is a code block, not an admonition
        let content = "# Test\n\n## One\n\n!!! note\n\n    See [two](#two)\n\n## Three\n";
        let warnings = lint_standard(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "Standard flavor should not detect links inside 4-space-indented content: {md051:?}"
        );
    }
}

// =============================================================================
// Image parsing inside MkDocs admonitions and content tabs
// Companion to the MD051 link detection fix: parse_images had the same gap
// where inline images inside admonitions were missed by the regex fallback.
// =============================================================================

mod image_parsing_in_admonitions {
    use super::*;

    #[test]
    fn test_lint_context_parses_images_inside_admonitions() {
        let content = "# Test\n\n!!! note\n\n    ![alt text](image.png)\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let admonition_images: Vec<_> = ctx.images.iter().filter(|img| img.url.contains("image.png")).collect();
        assert!(
            !admonition_images.is_empty(),
            "LintContext should parse images inside MkDocs admonitions, found: {:?}",
            ctx.images.iter().map(|img| img.url.as_ref()).collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_lint_context_parses_images_inside_content_tabs() {
        let content = "# Test\n\n=== \"Tab 1\"\n\n    ![alt text](image.png)\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let tab_images: Vec<_> = ctx.images.iter().filter(|img| img.url.contains("image.png")).collect();
        assert!(
            !tab_images.is_empty(),
            "LintContext should parse images inside MkDocs content tabs, found: {:?}",
            ctx.images.iter().map(|img| img.url.as_ref()).collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_image_inside_fenced_code_in_admonition_not_parsed() {
        let content = "# Test\n\n!!! note\n\n    ```markdown\n    ![alt](image.png)\n    ```\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let fenced_images: Vec<_> = ctx.images.iter().filter(|img| img.url.contains("image.png")).collect();
        assert!(
            fenced_images.is_empty(),
            "Images inside fenced code blocks within admonitions should not be parsed: {fenced_images:?}",
        );
    }

    #[test]
    fn test_md045_detects_missing_alt_text_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    ![](image.png)\n";
        let warnings = lint_mkdocs(content);
        let md045: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD045"))
            .collect();
        assert!(
            !md045.is_empty(),
            "MD045 should detect missing alt text for images inside admonitions"
        );
    }

    #[test]
    fn test_md045_no_warning_with_alt_text_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    ![descriptive alt](image.png)\n";
        let warnings = lint_mkdocs(content);
        let md045: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD045"))
            .collect();
        assert!(
            md045.is_empty(),
            "MD045 should not flag images with alt text inside admonitions: {md045:?}"
        );
    }

    #[test]
    fn test_standard_flavor_images_in_indented_content_not_parsed() {
        // In Standard flavor, 4-space-indented content is code, not admonition
        let content = "# Test\n\n!!! note\n\n    ![alt](image.png)\n";
        let ctx = LintContext::new(content, MarkdownFlavor::Standard, None);
        let images: Vec<_> = ctx.images.iter().filter(|img| img.url.contains("image.png")).collect();
        assert!(
            images.is_empty(),
            "Standard flavor should not parse images inside 4-space-indented content: {images:?}"
        );
    }
}

// =============================================================================
// Broader behavioral impact: code_blocks mutation affects all downstream consumers
// Verifies that rules beyond MD051 correctly process content inside MkDocs
// admonitions and content tabs after the code_blocks filtering fix.
// =============================================================================

mod admonition_content_broader_rules {
    use super::*;

    // --- MD034: Bare URLs ---

    #[test]
    fn test_md034_detects_bare_url_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    Visit https://example.com for details\n";
        let warnings = lint_mkdocs(content);
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(!md034.is_empty(), "MD034 should detect bare URLs inside admonitions");
    }

    #[test]
    fn test_md034_detects_bare_url_in_content_tab() {
        let content = "# Test\n\n=== \"Tab 1\"\n\n    Visit https://example.com for details\n";
        let warnings = lint_mkdocs(content);
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(!md034.is_empty(), "MD034 should detect bare URLs inside content tabs");
    }

    #[test]
    fn test_md034_no_bare_url_in_fenced_code_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    ```\n    https://example.com\n    ```\n";
        let warnings = lint_mkdocs(content);
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag bare URLs inside fenced code blocks within admonitions: {md034:?}"
        );
    }

    // --- MD039: No space in links ---

    #[test]
    fn test_md039_detects_space_in_link_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    [ link text ](https://example.com)\n";
        let warnings = lint_mkdocs(content);
        let md039: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD039"))
            .collect();
        assert!(
            !md039.is_empty(),
            "MD039 should detect spaces inside link text in admonitions"
        );
    }

    // --- MD011: Reversed link syntax ---

    #[test]
    fn test_md011_detects_reversed_link_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    (reversed link)[https://example.com]\n";
        let warnings = lint_mkdocs(content);
        let md011: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD011"))
            .collect();
        assert!(
            !md011.is_empty(),
            "MD011 should detect reversed link syntax inside admonitions"
        );
    }

    // --- MD037: No space in emphasis ---

    #[test]
    fn test_md037_detects_space_in_emphasis_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    This is * not emphasis *\n";
        let warnings = lint_mkdocs(content);
        let _md037: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD037"))
            .collect();
        // MD037 may or may not flag this depending on how it handles the specific pattern.
        // The key assertion is that the rule RUNS on admonition content (no skip).
        // We verify by checking LintContext doesn't mark this line as code.
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let line_4 = ctx.lines.get(4); // "    This is * not emphasis *"
        assert!(
            line_4.is_some_and(|l| !l.in_code_block),
            "Line inside admonition should not be marked as code block"
        );
    }

    // --- MD012: No multiple blanks ---

    #[test]
    fn test_md012_detects_multiple_blanks_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    First paragraph\n\n\n    Second paragraph\n";
        let warnings = lint_mkdocs(content);
        let md012: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD012"))
            .collect();
        assert!(
            !md012.is_empty(),
            "MD012 should detect multiple blank lines inside admonitions"
        );
    }

    // --- Verify LineInfo correctness ---

    #[test]
    fn test_admonition_lines_not_marked_as_code_block() {
        let content = "# Test\n\n!!! note\n\n    Content here\n    More content\n\n## Next\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        // Lines 4 and 5 (0-indexed) are admonition content
        for i in 4..6 {
            let info = &ctx.lines[i];
            assert!(
                !info.in_code_block,
                "Line {} should not be in_code_block (is admonition content): {:?}",
                i + 1,
                info
            );
            assert!(info.in_admonition, "Line {} should be in_admonition: {:?}", i + 1, info);
        }
    }

    #[test]
    fn test_code_spans_detected_in_content_tab() {
        // Issue #487: pulldown-cmark misses code spans inside MkDocs content tabs
        // because it treats 4-space-indented content as indented code blocks
        let content = "# Test\n\n=== \"Tab 1\"\n\n    Visit `https://example.com` to get started.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect exactly one code span inside content tab"
        );
        assert_eq!(code_spans[0].content, "https://example.com");
    }

    #[test]
    fn test_code_spans_detected_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    Visit `https://example.com` for details.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(code_spans.len(), 1, "Should detect code span inside admonition");
        assert_eq!(code_spans[0].content, "https://example.com");
    }

    #[test]
    fn test_multiple_code_spans_in_mkdocs_container() {
        let content = "# Test\n\n=== \"Tab\"\n\n    Use `foo` and `bar` together with `baz`.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(code_spans.len(), 3, "Should detect all three code spans");
        assert_eq!(code_spans[0].content, "foo");
        assert_eq!(code_spans[1].content, "bar");
        assert_eq!(code_spans[2].content, "baz");
    }

    #[test]
    fn test_double_backtick_code_spans_in_mkdocs_container() {
        let content = "# Test\n\n=== \"Tab\"\n\n    Use ``code with ` backtick`` here.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(code_spans.len(), 1, "Should detect double-backtick code span");
        assert_eq!(code_spans[0].content, "code with ` backtick");
    }

    #[test]
    fn test_md034_no_warning_for_url_in_code_span_in_content_tab() {
        // Issue #487: URLs inside backtick code spans within MkDocs content tabs
        // should NOT be flagged by MD034
        let content = "# Test\n\n=== \"Tab\"\n\n    1.  Visit `https://example.com` to get started.\n\n        - Set **URL** to `https://example.com/api`\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag URLs inside backtick code spans in content tabs: {md034:?}"
        );
    }

    #[test]
    fn test_md034_fix_preserves_url_in_code_span_in_content_tab() {
        // Issue #487: fix mode should not modify URLs inside code spans
        let content = "# Test\n\n=== \"Tab\"\n\n    1.  Visit `https://example.com` to get started.\n\n        - Set **URL** to `https://example.com/api`\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD034NoBareUrls;
        let fixed = rule.fix(&ctx).unwrap();
        assert_eq!(fixed, content, "Fix should not modify URLs in code spans");
    }

    #[test]
    fn test_md034_still_flags_bare_url_in_content_tab() {
        // Bare URLs (not in code spans) inside content tabs should still be flagged
        let content = "# Test\n\n=== \"Tab 1\"\n\n    Visit https://example.com for details.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            !md034.is_empty(),
            "MD034 should still flag bare URLs (not in code spans) inside content tabs"
        );
    }

    #[test]
    fn test_md034_no_warning_for_url_in_code_span_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    Visit `https://example.com` for details.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag URLs inside code spans in admonitions: {md034:?}"
        );
    }

    #[test]
    fn test_unmatched_triple_backtick_doesnt_hide_inner_code_span() {
        // When triple backticks have no match, single-backtick spans later on
        // the same line should still be detected
        let content = "# Test\n\n=== \"Tab\"\n\n    See ``` and `foo` here.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect `foo` even after unmatched triple backticks"
        );
        assert_eq!(code_spans[0].content, "foo");
    }

    #[test]
    fn test_escaped_backtick_not_detected_as_code_span() {
        // Backslash-escaped backticks should not form code spans
        let content = "# Test\n\n=== \"Tab\"\n\n    This is \\`not code\\` text.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert!(
            code_spans.is_empty(),
            "Escaped backticks should not form code spans, got: {code_spans:?}"
        );
    }

    #[test]
    fn test_code_span_with_escaped_backslash_before_backtick() {
        // Double backslash before backtick means the backslash is escaped,
        // so the backtick IS a code span opener
        let content = "# Test\n\n=== \"Tab\"\n\n    See \\\\`code` here.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Double-escaped backslash should not prevent code span detection"
        );
        assert_eq!(code_spans[0].content, "code");
    }

    #[test]
    fn test_content_tab_lines_not_marked_as_code_block() {
        let content = "# Test\n\n=== \"Tab 1\"\n\n    Content here\n    More content\n\n## Next\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        for i in 4..6 {
            let info = &ctx.lines[i];
            assert!(
                !info.in_code_block,
                "Line {} should not be in_code_block (is tab content): {:?}",
                i + 1,
                info
            );
            assert!(
                info.in_content_tab,
                "Line {} should be in_content_tab: {:?}",
                i + 1,
                info
            );
        }
    }

    #[test]
    fn test_fenced_code_in_admonition_still_marked_as_code_block() {
        let content = "# Test\n\n!!! note\n\n    ```python\n    code = True\n    ```\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);
        // Line 5 (0-indexed) is "    code = True" inside fenced code within admonition
        let info = &ctx.lines[5];
        assert!(
            info.in_code_block,
            "Line inside fenced code block within admonition should still be in_code_block: {info:?}",
        );
        assert!(
            info.in_admonition,
            "Line inside fenced code block within admonition should also be in_admonition: {info:?}",
        );
    }

    #[test]
    fn test_code_blocks_byte_ranges_exclude_admonition_content() {
        // Verify that the code_blocks byte ranges don't include admonition content
        let content = "# Test\n\n!!! note\n\n    Content here\n\n## Next\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        // The admonition content line should NOT be covered by any code_blocks range
        let content_line_offset = content.find("    Content here").unwrap();
        let in_code_block = ctx
            .code_blocks
            .iter()
            .any(|&(start, end)| content_line_offset >= start && content_line_offset < end);
        assert!(
            !in_code_block,
            "Admonition content should not be in code_blocks byte ranges. Ranges: {:?}, offset: {}",
            ctx.code_blocks, content_line_offset
        );
    }

    #[test]
    fn test_code_blocks_byte_ranges_preserve_fenced_code_in_admonition() {
        // Verify that fenced code blocks within admonitions ARE still in code_blocks
        let content = "# Test\n\n!!! note\n\n    ```python\n    code = True\n    ```\n\n## Next\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_line_offset = content.find("    code = True").unwrap();
        let in_code_block = ctx
            .code_blocks
            .iter()
            .any(|&(start, end)| code_line_offset >= start && code_line_offset < end);
        assert!(
            in_code_block,
            "Fenced code content within admonition should be in code_blocks byte ranges. Ranges: {:?}, offset: {}",
            ctx.code_blocks, code_line_offset
        );
    }

    #[test]
    fn test_mixed_content_and_fenced_code_in_admonition_byte_ranges() {
        // The critical edge case: content AND fenced code in same admonition.
        // Content should NOT be in code_blocks, fenced code SHOULD be.
        let content =
            "# Test\n\n!!! note\n\n    Regular content\n\n    ```python\n    code = True\n    ```\n\n## Next\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let regular_offset = content.find("    Regular content").unwrap();
        let code_offset = content.find("    code = True").unwrap();

        let regular_in_code = ctx
            .code_blocks
            .iter()
            .any(|&(start, end)| regular_offset >= start && regular_offset < end);
        let code_in_code = ctx
            .code_blocks
            .iter()
            .any(|&(start, end)| code_offset >= start && code_offset < end);

        assert!(
            !regular_in_code,
            "Regular admonition content should not be in code_blocks byte ranges"
        );
        assert!(
            code_in_code,
            "Fenced code content in admonition should be in code_blocks byte ranges"
        );
    }

    // --- Multi-line code span tests ---

    #[test]
    fn test_multiline_code_span_in_content_tab() {
        // A backtick code span that opens on one line and closes on the next
        let content = "# Test\n\n=== \"Tab\"\n\n    Use `some code\n    spanning lines` here.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect multi-line code span in content tab, got: {code_spans:?}"
        );
        assert!(
            code_spans[0].content.contains("some code"),
            "Code span content should contain 'some code': {:?}",
            code_spans[0].content
        );
        assert!(
            code_spans[0].content.contains("spanning lines"),
            "Code span content should contain 'spanning lines': {:?}",
            code_spans[0].content
        );
    }

    #[test]
    fn test_multiline_code_span_in_admonition() {
        let content = "# Test\n\n!!! note\n\n    Run `command\n    --flag value` to proceed.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect multi-line code span in admonition, got: {code_spans:?}"
        );
        assert!(
            code_spans[0].content.contains("command"),
            "Code span should contain 'command': {:?}",
            code_spans[0].content
        );
        assert!(
            code_spans[0].content.contains("--flag value"),
            "Code span should contain '--flag value': {:?}",
            code_spans[0].content
        );
    }

    #[test]
    fn test_mixed_single_and_multiline_code_spans_in_container() {
        // Single-line and multi-line code spans coexist in the same container
        let content = "# Test\n\n=== \"Tab\"\n\n    Use `single` and `multi\n    line span` together.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            2,
            "Should detect both single-line and multi-line code spans, got: {code_spans:?}"
        );
        assert_eq!(code_spans[0].content, "single");
        assert!(
            code_spans[1].content.contains("multi"),
            "Second span should contain 'multi': {:?}",
            code_spans[1].content
        );
    }

    #[test]
    fn test_multiline_code_span_with_url_in_container() {
        // The actual MD034 scenario from issue #487: multi-line code span containing a URL
        let content = "# Test\n\n=== \"Tab\"\n\n    Visit `https://example.com/\n    api/v2` for the endpoint.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect multi-line code span containing URL, got: {code_spans:?}"
        );
        assert!(
            code_spans[0].content.contains("https://example.com/"),
            "Code span should contain URL: {:?}",
            code_spans[0].content
        );

        // MD034 should NOT flag this URL since it's inside a code span
        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag URL inside multi-line code span: {md034:?}"
        );
    }

    #[test]
    fn test_multiline_code_span_three_lines() {
        // Code span spanning three lines
        let content = "# Test\n\n=== \"Tab\"\n\n    Run `first\n    second\n    third` now.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect code span spanning three lines, got: {code_spans:?}"
        );
        assert!(
            code_spans[0].content.contains("first"),
            "Should contain 'first': {:?}",
            code_spans[0].content
        );
        assert!(
            code_spans[0].content.contains("third"),
            "Should contain 'third': {:?}",
            code_spans[0].content
        );
    }

    #[test]
    fn test_md034_no_warning_for_email_in_multiline_code_span() {
        // Email addresses inside multi-line code spans should not be flagged
        let content = "# Test\n\n=== \"Tab\"\n\n    Contact `admin\n    @example.com` for help.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect multi-line code span containing email fragment"
        );

        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag email inside multi-line code span: {md034:?}"
        );
    }

    #[test]
    fn test_code_span_in_nested_container() {
        // Admonition inside content tab: content at indent 8 should be dedented correctly
        let content = "# Test\n\n=== \"Tab\"\n\n    !!! note\n\n        Visit `https://example.com` here.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect code span in nested container (admonition inside content tab)"
        );
        assert_eq!(code_spans[0].content, "https://example.com");

        // MD034 should not flag the URL
        let rule = rumdl_lib::MD034NoBareUrls;
        let warnings = rule.check(&ctx).unwrap();
        let md034: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD034"))
            .collect();
        assert!(
            md034.is_empty(),
            "MD034 should not flag URL in code span inside nested container: {md034:?}"
        );
    }

    #[test]
    fn test_multiline_code_span_in_nested_container() {
        // Multi-line code span inside admonition within content tab
        let content = "# Test\n\n=== \"Tab\"\n\n    !!! note\n\n        Run `command\n        --verbose` now.\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        let code_spans = ctx.code_spans();
        assert_eq!(
            code_spans.len(),
            1,
            "Should detect multi-line code span in nested container, got: {code_spans:?}"
        );
        assert!(
            code_spans[0].content.contains("command"),
            "Code span should contain 'command': {:?}",
            code_spans[0].content
        );
    }
}

mod html_markdown_link_detection {
    use super::*;

    #[test]
    fn test_broken_link_inside_div_markdown_is_detected() {
        let content = "# Test\n\n## One\n\n<div markdown>\n\n    See [two](#two)\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside <div markdown>"
        );
    }

    #[test]
    fn test_valid_link_inside_div_markdown_no_warning() {
        let content = "# Test\n\n## One\n\n<div markdown>\n\n    See [one](#one)\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "MD051 should not flag valid link '#one' inside <div markdown>: {md051:?}"
        );
    }

    #[test]
    fn test_broken_link_inside_section_markdown() {
        let content = "# Test\n\n## One\n\n<section markdown>\n\n    See [two](#two)\n\n</section>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside <section markdown>"
        );
    }

    #[test]
    fn test_fenced_code_inside_div_markdown_preserved() {
        // Fenced code blocks within <div markdown> should NOT have links detected
        let content =
            "# Test\n\n## One\n\n<div markdown>\n\n    ```\n    See [two](#two)\n    ```\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            md051.is_empty(),
            "MD051 should NOT detect links inside fenced code within <div markdown>: {md051:?}"
        );
    }

    #[test]
    fn test_mixed_content_and_fenced_code_in_div_markdown() {
        // Link outside fenced code should be detected, link inside should not
        let content = "# Test\n\n## One\n\n<div markdown>\n\n    See [two](#two)\n\n    ```\n    See [three](#three)\n    ```\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' outside fenced code in <div markdown>"
        );
        // Should be exactly 1 warning (only for #two, not for #three in the code block)
        assert_eq!(
            md051.len(),
            1,
            "Should only detect link outside fenced code, not inside: {md051:?}"
        );
    }

    #[test]
    fn test_image_inside_div_markdown_detected() {
        let content = "# Test\n\n<div markdown>\n\n    ![]()\n\n</div>\n";
        let warnings = lint_mkdocs(content);
        let md045: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD045"))
            .collect();
        assert!(
            !md045.is_empty(),
            "MD045 should detect image without alt text inside <div markdown>"
        );
    }

    #[test]
    fn test_link_context_line_info_in_div_markdown() {
        let content = "# Test\n\n<div markdown>\n\n    Some content with [link](#test)\n\n</div>\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        // Line 4 (0-indexed) = "    Some content with [link](#test)"
        let line_info = &ctx.lines[4];
        assert!(
            line_info.in_mkdocs_html_markdown,
            "Content inside <div markdown> should have in_mkdocs_html_markdown set"
        );
        assert!(
            !line_info.in_code_block,
            "Content inside <div markdown> should NOT have in_code_block set"
        );
    }

    #[test]
    fn test_code_blocks_byte_ranges_exclude_div_markdown_content() {
        let content = "# Test\n\n<div markdown>\n\n    Regular content here\n\n</div>\n";
        let ctx = LintContext::new(content, MarkdownFlavor::MkDocs, None);

        // Find the byte offset of the indented content
        let content_offset = content.find("    Regular content").unwrap();

        let in_code = ctx
            .code_blocks
            .iter()
            .any(|&(start, end)| content_offset >= start && content_offset < end);

        assert!(
            !in_code,
            "Regular content inside <div markdown> should not be in code_blocks byte ranges"
        );
    }

    #[test]
    fn test_div_markdown_with_markdown_eq_1() {
        let content = "# Test\n\n## One\n\n<div markdown=\"1\">\n\n    See [two](#two)\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link '#two' inside <div markdown=\"1\">"
        );
    }

    #[test]
    fn test_standard_flavor_div_markdown_honored() {
        // The `markdown` attribute on a block-level HTML element is an explicit,
        // author-supplied opt-in for Markdown processing of the content. rumdl
        // honors it regardless of the configured flavor so `rumdl fmt` cannot
        // silently mangle pages like Material grid cards when no flavor is set
        // (regression for issue #583). That means links in the indented content
        // ARE detected as Markdown links even under standard flavor.
        let content = "# Test\n\n## One\n\n<div markdown>\n\n    See [two](#two)\n\n</div>\n\n## Three\n";
        let warnings = lint_standard(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "Standard flavor should honor `<div markdown>` and detect broken link '#two': {warnings:?}"
        );
    }

    #[test]
    fn test_div_markdown_inside_admonition() {
        // Nested container: <div markdown> inside an admonition
        // Both handlers clear in_code_block — should work without conflict
        let content =
            "# Test\n\n## One\n\n!!! note\n\n    <div markdown>\n\n        See [two](#two)\n\n    </div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link in <div markdown> nested inside admonition"
        );
    }

    #[test]
    fn test_non_indented_div_markdown_content() {
        // Content inside <div markdown> that is NOT indented 4 spaces
        // pulldown-cmark handles this normally (no indented-code-block misclassification)
        let content = "# Test\n\n## One\n\n<div markdown>\n\nSee [two](#two)\n\n</div>\n\n## Three\n";
        let warnings = lint_mkdocs(content);
        let md051: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD051"))
            .collect();
        assert!(
            !md051.is_empty(),
            "MD051 should detect broken link in non-indented <div markdown> content"
        );
    }

    #[test]
    fn test_md042_empty_link_inside_admonition() {
        // MD042 should detect [text]() inside MkDocs containers
        let content = "# Test\n\n!!! note\n\n    [click here]()\n";
        let warnings = lint_mkdocs(content);
        let md042: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD042"))
            .collect();
        assert!(
            !md042.is_empty(),
            "MD042 should detect empty-URL link inside admonition"
        );
    }

    #[test]
    fn test_md042_empty_link_inside_div_markdown() {
        // MD042 should detect [text]() inside <div markdown> containers
        let content = "# Test\n\n<div markdown>\n\n    [click here]()\n\n</div>\n";
        let warnings = lint_mkdocs(content);
        let md042: Vec<_> = warnings
            .iter()
            .filter(|w| w.rule_name.as_deref() == Some("MD042"))
            .collect();
        assert!(
            !md042.is_empty(),
            "MD042 should detect empty-URL link inside <div markdown>"
        );
    }
}
