use crate::base36::u64_to_base36;
use crate::events::events::{Emitter, Event};
use crate::events::BusReaderIterator;
use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};
use bus::Bus;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Read};
use std::path::Path;
use std::sync::{Arc, Mutex};

/// Emitter that reads Markdown files and emits events for Wasot conversion
pub struct MarkdownEmitter {
    /// Path to the Markdown file to read
    file_path: Option<String>,
    /// Optional reader for stdin or other sources
    reader: Option<Box<dyn Read + Send + Sync>>,
    /// Bus for events
    bus: Arc<Mutex<Bus<Event>>>,
    /// Track if we're in a code block
    in_code_block: bool,
    /// Track the code block language
    code_block_language: Option<String>,
    /// Track if we're in a quote block
    in_quote_block: bool,
    /// Accumulated lines for current block
    accumulated_lines: Vec<String>,
    /// Current line number
    current_line: usize,
    /// Next ID to assign to blocks without IDs
    next_auto_id: Arc<Mutex<u64>>,
    /// Whether to wrap paragraphs in blocks
    paragraph_blocks: bool,
}

impl MarkdownEmitter {
    /// Create a new Markdown file emitter from a file path
    pub fn from_file<P: AsRef<Path>>(file_path: P) -> Self {
        let bus = Arc::new(Mutex::new(Bus::new(1000)));

        Self {
            file_path: Some(file_path.as_ref().to_string_lossy().into_owned()),
            reader: None,
            bus,
            in_code_block: false,
            code_block_language: None,
            in_quote_block: false,
            accumulated_lines: Vec::new(),
            current_line: 0,
            next_auto_id: Arc::new(Mutex::new(1)),
            paragraph_blocks: false,
        }
    }

    /// Create a new Markdown file emitter from a reader (e.g., stdin)
    pub fn from_reader<R: Read + Send + Sync + 'static>(reader: R) -> Self {
        let bus = Arc::new(Mutex::new(Bus::new(1000)));

        Self {
            file_path: None,
            reader: Some(Box::new(reader)),
            bus,
            in_code_block: false,
            code_block_language: None,
            in_quote_block: false,
            accumulated_lines: Vec::new(),
            current_line: 0,
            next_auto_id: Arc::new(Mutex::new(1)),
            paragraph_blocks: false,
        }
    }

    /// Create a new Markdown file emitter from stdin
    pub fn from_stdin() -> Self {
        Self::from_reader(io::stdin())
    }

    /// Create a new Markdown file emitter from a string
    pub fn from_string(content: String) -> Self {
        Self::from_reader(io::Cursor::new(content))
    }

    /// Set whether to wrap paragraphs in blocks
    pub fn with_paragraph_blocks(mut self, paragraph_blocks: bool) -> Self {
        self.paragraph_blocks = paragraph_blocks;
        self
    }

    /// Generate next auto ID
    fn get_next_auto_id(&self) -> String {
        let mut id = self.next_auto_id.lock().unwrap();
        let current = *id;
        *id += 1;
        u64_to_base36(current)
    }

    /// Flush accumulated text as a TextFinished event
    fn flush_text(&mut self) {
        if !self.accumulated_lines.is_empty() {
            // add empty line
            self.accumulated_lines.push("".to_string());

            let event = Event::TextFinished {
                lines: self.accumulated_lines.clone(),
                links: vec![],
                footnotes: vec![],
            };

            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(event);
            }

            self.accumulated_lines.clear();
        }
    }

    /// Process a single line of markdown
    fn process_line(&mut self, line: &str) {
        self.current_line += 1;

        // Check for code block markers
        if let Some(stripped) = line.strip_prefix("```") {
            if self.in_code_block {
                // End of code block
                self.flush_text();

                // Emit BlockFinished for code block
                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(Event::BlockFinished);
                }

                self.in_code_block = false;
                self.code_block_language = None;
            } else {
                // Start of code block
                self.flush_text();

                // Extract language if specified
                let language = stripped.trim().to_string();
                let language = if language.is_empty() {
                    None
                } else {
                    Some(language.clone())
                };

                // Create MetaLine for code block
                let mut args = vec![];
                if let Some(ref lang) = language {
                    args.push(QuotedString::new(lang.clone(), false));
                }

                let meta_line = MetaLine {
                    type_name: "code".to_string(),
                    inline_text: QuotedString::new(String::new(), false),
                    level: 1,
                    id: Id::new(self.get_next_auto_id()),
                    line_index: Some(self.current_line - 1),
                    arguments: ParsedArguments {
                        args,
                        kwargs: vec![],
                    },
                    is_void: false,
                };

                // Emit BlockStarted for code block
                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(Event::BlockStarted { meta_line });
                }

                self.in_code_block = true;
                self.code_block_language = language;
            }
            return;
        }

        // If we're in a code block, accumulate the line
        if self.in_code_block {
            self.accumulated_lines.push(line.to_string());
            return;
        }

        // Check for headings
        if line.starts_with('#') {
            self.flush_text();

            let mut level = 0;
            let mut chars = line.chars();
            while let Some('#') = chars.next() {
                level += 1;
            }

            let heading_text = line[level..].trim();

            // Create MetaLine for heading
            let type_name = "#".repeat(level);
            let meta_line = MetaLine {
                type_name,
                inline_text: QuotedString::new(heading_text.to_string(), false),
                level: 1,
                id: Id::new(self.get_next_auto_id()),
                line_index: Some(self.current_line - 1),
                arguments: ParsedArguments {
                    args: vec![],
                    kwargs: vec![],
                },
                is_void: false,
            };

            // Emit BlockStarted and BlockFinished for self-closing heading
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(Event::BlockStarted { meta_line });
                bus.broadcast(Event::BlockFinished);
            }
            return;
        }

        // Check for block quotes
        if let Some(stripped) = line.strip_prefix('>') {
            // If not already in a quote, start one
            if !self.in_quote_block {
                self.flush_text();

                // Create MetaLine for quote block
                let meta_line = MetaLine {
                    type_name: "quote".to_string(),
                    inline_text: QuotedString::new(String::new(), false),
                    level: 1,
                    id: Id::new(self.get_next_auto_id()),
                    line_index: Some(self.current_line - 1),
                    arguments: ParsedArguments {
                        args: vec![],
                        kwargs: vec![],
                    },
                    is_void: false,
                };

                // Emit BlockStarted for quote
                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(Event::BlockStarted { meta_line });
                }
                self.in_quote_block = true;
            }

            // Add the quote content (without the > prefix)
            let content = stripped.trim_start();
            self.accumulated_lines.push(content.to_string());
            return;
        }

        // If we were in a quote block and hit a non-quote line, end the quote
        if self.in_quote_block {
            self.flush_text();
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(Event::BlockFinished);
            }
            self.in_quote_block = false;
        }

        // Check for empty lines
        if line.trim().is_empty() {
            // Flush any accumulated text as a paragraph if paragraph_blocks is enabled
            if !self.accumulated_lines.is_empty() {
                if self.paragraph_blocks {
                    // Create paragraph block
                    let meta_line = MetaLine {
                        type_name: "paragraph".to_string(),
                        inline_text: QuotedString::new(String::new(), false),
                        level: 1,
                        id: Id::new(self.get_next_auto_id()),
                        line_index: Some(
                            self.current_line
                                .saturating_sub(self.accumulated_lines.len())
                                .saturating_sub(1),
                        ),
                        arguments: ParsedArguments {
                            args: vec![],
                            kwargs: vec![],
                        },
                        is_void: false,
                    };

                    if let Ok(mut bus) = self.bus.lock() {
                        bus.broadcast(Event::BlockStarted { meta_line });
                    }

                    self.flush_text();

                    if let Ok(mut bus) = self.bus.lock() {
                        bus.broadcast(Event::BlockFinished);
                    }
                } else {
                    // Just flush as text without wrapping in paragraph block
                    self.flush_text();
                }
            }
            return;
        }

        // Regular text line - accumulate it
        self.accumulated_lines.push(line.to_string());
    }
}

impl Emitter for MarkdownEmitter {
    type Reader = BusReaderIterator;

    fn subscribe(&self) -> Self::Reader {
        let rx = self.bus.lock().unwrap().add_rx();
        BusReaderIterator::new(rx)
    }

    fn run(mut self) {
        // Open the file or use the provided reader
        let reader: Box<dyn BufRead> = if let Some(reader) = self.reader.take() {
            Box::new(BufReader::new(reader))
        } else if let Some(ref path) = self.file_path {
            match File::open(path) {
                Ok(file) => Box::new(BufReader::new(file)),
                Err(e) => {
                    eprintln!("Error opening file '{path}': {e}");
                    return;
                }
            }
        } else {
            eprintln!("No file path or reader provided");
            return;
        };

        // Process each line
        for line in reader.lines() {
            match line {
                Ok(line) => self.process_line(&line),
                Err(e) => {
                    eprintln!("Error reading line: {e}");
                    break;
                }
            }
        }

        // Flush any remaining text as a paragraph if it exists
        if !self.accumulated_lines.is_empty() && !self.in_code_block && !self.in_quote_block {
            if self.paragraph_blocks {
                // Create paragraph block for remaining text
                let meta_line = MetaLine {
                    type_name: "paragraph".to_string(),
                    inline_text: QuotedString::new(String::new(), false),
                    level: 1,
                    id: Id::new(self.get_next_auto_id()),
                    line_index: Some(
                        self.current_line
                            .saturating_sub(self.accumulated_lines.len()),
                    ),
                    arguments: ParsedArguments {
                        args: vec![],
                        kwargs: vec![],
                    },
                    is_void: false,
                };

                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(Event::BlockStarted { meta_line });
                }

                self.flush_text();

                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(Event::BlockFinished);
                }
            } else {
                // Just flush as text without wrapping in paragraph block
                self.flush_text();
            }
        } else {
            self.flush_text();
        }

        // Close any open blocks
        if self.in_code_block {
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(Event::BlockFinished);
            }
        }
        if self.in_quote_block {
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(Event::BlockFinished);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::{Emitter, Event};
    use std::io::Cursor;
    use std::thread;

    #[test]
    fn test_from_file() {
        let emitter = MarkdownEmitter::from_file("test.md");
        assert_eq!(emitter.file_path, Some("test.md".to_string()));
        assert!(emitter.reader.is_none());
        assert!(!emitter.in_code_block);
        assert!(emitter.code_block_language.is_none());
        assert!(!emitter.in_quote_block);
        assert!(emitter.accumulated_lines.is_empty());
        assert_eq!(emitter.current_line, 0);
        assert!(!emitter.paragraph_blocks);
    }

    #[test]
    fn test_from_reader() {
        let input = b"# Hello\nWorld";
        let reader = Cursor::new(input);
        let emitter = MarkdownEmitter::from_reader(reader);
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
        assert!(!emitter.in_code_block);
        assert!(!emitter.in_quote_block);
        assert!(!emitter.paragraph_blocks);
    }

    #[test]
    fn test_from_stdin() {
        let emitter = MarkdownEmitter::from_stdin();
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
    }

    #[test]
    fn test_with_paragraph_blocks() {
        let emitter = MarkdownEmitter::from_file("test.md").with_paragraph_blocks(true);
        assert!(emitter.paragraph_blocks);

        let emitter = MarkdownEmitter::from_file("test.md").with_paragraph_blocks(false);
        assert!(!emitter.paragraph_blocks);
    }

    #[test]
    fn test_get_next_auto_id() {
        let emitter = MarkdownEmitter::from_file("test.md");
        assert_eq!(emitter.get_next_auto_id(), "1");
        assert_eq!(emitter.get_next_auto_id(), "2");
        assert_eq!(emitter.get_next_auto_id(), "3");
    }

    #[test]
    fn test_flush_text_empty() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        // Should not panic with empty accumulated_lines
        emitter.flush_text();
        assert!(emitter.accumulated_lines.is_empty());
    }

    #[test]
    fn test_flush_text_with_content() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.accumulated_lines.push("Hello".to_string());
        emitter.accumulated_lines.push("World".to_string());

        let receiver = emitter.subscribe();

        // Run flush in background thread
        let handle = thread::spawn(move || {
            emitter.flush_text();
            emitter
        });

        // Check the event was broadcast
        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if !events.is_empty() {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.accumulated_lines.is_empty());

        assert_eq!(events.len(), 1);
        if let Event::TextFinished {
            lines,
            links,
            footnotes,
        } = &events[0]
        {
            assert_eq!(lines.len(), 3); // Original 2 + empty line added
            assert_eq!(lines[0], "Hello");
            assert_eq!(lines[1], "World");
            assert_eq!(lines[2], "");
            assert!(links.is_empty());
            assert!(footnotes.is_empty());
        } else {
            panic!("Expected TextFinished event");
        }
    }

    #[test]
    fn test_process_line_code_block_start() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("```rust");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if !events.is_empty() {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.in_code_block);
        assert_eq!(emitter.code_block_language, Some("rust".to_string()));
        assert_eq!(emitter.current_line, 1);

        assert_eq!(events.len(), 1);
        if let Event::BlockStarted { meta_line } = &events[0] {
            assert_eq!(meta_line.type_name, "code");
            assert_eq!(meta_line.level, 1);
            assert_eq!(meta_line.arguments.args.len(), 1);
            assert_eq!(meta_line.arguments.args[0].content, "rust");
        } else {
            panic!("Expected BlockStarted event");
        }
    }

    #[test]
    fn test_process_line_code_block_without_language() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("```");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if !events.is_empty() {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.in_code_block);
        assert_eq!(emitter.code_block_language, None);

        if let Event::BlockStarted { meta_line } = &events[0] {
            assert_eq!(meta_line.type_name, "code");
            assert_eq!(meta_line.arguments.args.len(), 0);
        }
    }

    #[test]
    fn test_process_line_code_block_end() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.in_code_block = true;
        emitter.code_block_language = Some("python".to_string());
        emitter.accumulated_lines.push("print('hello')".to_string());

        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("```");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 2 {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(!emitter.in_code_block);
        assert_eq!(emitter.code_block_language, None);
        assert!(emitter.accumulated_lines.is_empty());

        assert_eq!(events.len(), 2);
        // First event should be TextFinished, second should be BlockFinished
        match (&events[0], &events[1]) {
            (Event::TextFinished { lines, .. }, Event::BlockFinished) => {
                assert_eq!(lines.len(), 2); // Code line + empty line
                assert_eq!(lines[0], "print('hello')");
            }
            _ => panic!("Expected TextFinished then BlockFinished events"),
        }
    }

    #[test]
    fn test_process_line_in_code_block_accumulates() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.in_code_block = true;

        emitter.process_line("def hello():");
        emitter.process_line("    print('world')");

        assert_eq!(emitter.accumulated_lines.len(), 2);
        assert_eq!(emitter.accumulated_lines[0], "def hello():");
        assert_eq!(emitter.accumulated_lines[1], "    print('world')");
        assert_eq!(emitter.current_line, 2);
    }

    #[test]
    fn test_process_line_heading() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("## My Heading");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 2 {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert_eq!(emitter.current_line, 1);

        assert_eq!(events.len(), 2);
        match (&events[0], &events[1]) {
            (Event::BlockStarted { meta_line }, Event::BlockFinished) => {
                assert_eq!(meta_line.type_name, "##");
                assert_eq!(meta_line.inline_text.content, "My Heading");
                assert_eq!(meta_line.level, 1);
                assert_eq!(meta_line.line_index, Some(0));
            }
            _ => panic!("Expected BlockStarted then BlockFinished events"),
        }
    }

    #[test]
    fn test_process_line_heading_levels() {
        let test_cases = vec![
            ("# H1", "#", "H1"),
            ("## H2", "##", "H2"),
            ("### H3", "###", "H3"),
            ("#### H4", "####", "H4"),
            ("##### H5", "#####", "H5"),
            ("###### H6", "######", "H6"),
        ];

        for (input, expected_type, expected_text) in test_cases {
            let mut emitter = MarkdownEmitter::from_file("test.md");
            let receiver = emitter.subscribe();

            let handle = thread::spawn(move || {
                emitter.process_line(input);
                emitter
            });

            let mut events = Vec::new();
            for event in receiver {
                events.push(event);
                if events.len() >= 2 {
                    break;
                }
            }

            handle.join().unwrap();

            if let Event::BlockStarted { meta_line } = &events[0] {
                assert_eq!(meta_line.type_name, expected_type);
                assert_eq!(meta_line.inline_text.content, expected_text);
            }
        }
    }

    #[test]
    fn test_process_line_quote_start() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("> This is a quote");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if !events.is_empty() {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.in_quote_block);
        assert_eq!(emitter.accumulated_lines.len(), 1);
        assert_eq!(emitter.accumulated_lines[0], "This is a quote");

        if let Event::BlockStarted { meta_line } = &events[0] {
            assert_eq!(meta_line.type_name, "quote");
            assert_eq!(meta_line.level, 1);
        }
    }

    #[test]
    fn test_process_line_quote_continuation() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.in_quote_block = true;

        emitter.process_line("> First line");
        emitter.process_line("> Second line");

        assert!(emitter.in_quote_block);
        assert_eq!(emitter.accumulated_lines.len(), 2);
        assert_eq!(emitter.accumulated_lines[0], "First line");
        assert_eq!(emitter.accumulated_lines[1], "Second line");
    }

    #[test]
    fn test_process_line_quote_end() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.in_quote_block = true;
        emitter.accumulated_lines.push("Quote content".to_string());

        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("Regular text");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 2 {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(!emitter.in_quote_block);
        assert_eq!(emitter.accumulated_lines.len(), 1);
        assert_eq!(emitter.accumulated_lines[0], "Regular text");

        match (&events[0], &events[1]) {
            (Event::TextFinished { .. }, Event::BlockFinished) => {
                // Expected
            }
            _ => panic!("Expected TextFinished then BlockFinished events"),
        }
    }

    #[test]
    fn test_process_line_empty_line_without_paragraph_blocks() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.paragraph_blocks = false;
        emitter.accumulated_lines.push("Some text".to_string());

        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if !events.is_empty() {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.accumulated_lines.is_empty());

        // Should just flush text without paragraph wrapper
        if let Event::TextFinished { .. } = &events[0] {
            // Expected
        } else {
            panic!("Expected TextFinished event");
        }
    }

    #[test]
    fn test_process_line_empty_line_with_paragraph_blocks() {
        let mut emitter = MarkdownEmitter::from_file("test.md");
        emitter.paragraph_blocks = true;
        emitter.accumulated_lines.push("Some text".to_string());

        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.process_line("");
            emitter
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 3 {
                break;
            }
        }

        let emitter = handle.join().unwrap();
        assert!(emitter.accumulated_lines.is_empty());

        // Should wrap in paragraph block
        match (&events[0], &events[1], &events[2]) {
            (
                Event::BlockStarted { meta_line },
                Event::TextFinished { .. },
                Event::BlockFinished,
            ) => {
                assert_eq!(meta_line.type_name, "paragraph");
                assert_eq!(meta_line.level, 1);
            }
            _ => panic!("Expected BlockStarted, TextFinished, BlockFinished events"),
        }
    }

    #[test]
    fn test_process_line_regular_text() {
        let mut emitter = MarkdownEmitter::from_file("test.md");

        emitter.process_line("This is regular text");
        emitter.process_line("This is more text");

        assert_eq!(emitter.accumulated_lines.len(), 2);
        assert_eq!(emitter.accumulated_lines[0], "This is regular text");
        assert_eq!(emitter.accumulated_lines[1], "This is more text");
        assert_eq!(emitter.current_line, 2);
    }

    #[test]
    fn test_run_with_string_input() {
        let input = "# Heading\n\nSome text\n\n```rust\nlet x = 1;\n```\n\n> Quote\n> More quote\n\nRegular text";
        let reader = Cursor::new(input.as_bytes());
        let emitter = MarkdownEmitter::from_reader(reader);
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 20 {
                break;
            }
        }

        handle.join().unwrap();

        // Should have various events for different markdown elements
        let block_started_count = events
            .iter()
            .filter(|e| matches!(e, Event::BlockStarted { .. }))
            .count();
        let block_finished_count = events
            .iter()
            .filter(|e| matches!(e, Event::BlockFinished))
            .count();
        let text_finished_count = events
            .iter()
            .filter(|e| matches!(e, Event::TextFinished { .. }))
            .count();

        assert!(block_started_count >= 3); // At least heading, code, quote
        assert!(block_finished_count >= 3);
        assert!(text_finished_count >= 2); // At least code content and quote content
    }

    #[test]
    fn test_run_closes_open_blocks() {
        let input = "```python\nprint('hello')\n"; // Unclosed code block
        let reader = Cursor::new(input.as_bytes());
        let emitter = MarkdownEmitter::from_reader(reader);
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 10 {
                break;
            }
        }

        handle.join().unwrap();

        // Should close the unclosed code block at the end
        let block_started_count = events
            .iter()
            .filter(|e| matches!(e, Event::BlockStarted { .. }))
            .count();
        let block_finished_count = events
            .iter()
            .filter(|e| matches!(e, Event::BlockFinished))
            .count();

        assert_eq!(block_started_count, block_finished_count); // All blocks should be closed
    }

    #[test]
    fn test_run_with_paragraph_blocks_final_text() {
        let input = "Final paragraph text";
        let reader = Cursor::new(input.as_bytes());
        let emitter = MarkdownEmitter::from_reader(reader).with_paragraph_blocks(true);
        let receiver = emitter.subscribe();

        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut events = Vec::new();
        for event in receiver {
            events.push(event);
            if events.len() >= 10 {
                break;
            }
        }

        handle.join().unwrap();

        // Should wrap final text in paragraph block
        let has_paragraph = events.iter().any(|e| {
            if let Event::BlockStarted { meta_line } = e {
                meta_line.type_name == "paragraph"
            } else {
                false
            }
        });

        assert!(has_paragraph);
    }
}
