use crate::emitters::id_scanner::IdScanner;
use crate::events::events::Emitter;
use crate::events::{BusReaderIterator, Event};
use crate::parsers::wasot::ElementParser;
use crate::parsers::wasot::{
    is_meta_line, parse_footnotes, FootnoteInterceptor, InterceptorChain, MetaLine,
};
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 Wasot files and emits parsing events
pub struct WasotFileEmitter {
    /// Path to the Wasot 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>>>,
    /// Accumulated text lines for batching
    accumulated_text_lines: Vec<String>,
    /// Line index of the first accumulated text line
    first_text_line_index: Option<usize>,
    /// Next ID to assign to blocks without IDs
    next_auto_id: Arc<Mutex<u64>>,
    /// Whether to automatically convert inline links to footnotes
    auto_convert_links_to_footnotes: bool,
    /// Whether to automatically generate IDs for blocks without explicit IDs
    auto_generate_ids: bool,
    /// Interceptor chain for processing events
    interceptor_chain: InterceptorChain,
}

impl WasotFileEmitter {
    /// Create a new Wasot 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)));
        let interceptor_chain = InterceptorChain::new();

        Self {
            file_path: Some(file_path.as_ref().to_string_lossy().into_owned()),
            reader: None,
            bus,
            accumulated_text_lines: Vec::new(),
            first_text_line_index: None,
            next_auto_id: Arc::new(Mutex::new(1)),
            auto_convert_links_to_footnotes: true,
            auto_generate_ids: true,
            interceptor_chain,
        }
    }

    /// Create a new Wasot 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)));
        let interceptor_chain = InterceptorChain::new();

        Self {
            file_path: None,
            reader: Some(Box::new(reader)),
            bus,
            accumulated_text_lines: Vec::new(),
            first_text_line_index: None,
            next_auto_id: Arc::new(Mutex::new(1)),
            auto_convert_links_to_footnotes: true,
            auto_generate_ids: true,
            interceptor_chain,
        }
    }

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

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

    /// Set whether to automatically convert inline links to footnotes
    pub fn with_auto_convert_links_to_footnotes(mut self, auto_convert: bool) -> Self {
        self.auto_convert_links_to_footnotes = auto_convert;
        self
    }

    /// Set whether to automatically generate IDs for blocks without explicit IDs
    ///
    /// When enabled (default), the emitter will:
    /// 1. Pre-scan the file to find the maximum existing numeric ID
    /// 2. Assign auto-generated IDs (starting from max + 1) to blocks without explicit IDs
    ///
    /// When disabled, blocks without explicit IDs will have empty IDs in events.
    pub fn with_auto_generate_ids(mut self, auto_generate: bool) -> Self {
        self.auto_generate_ids = auto_generate;
        self
    }

    /// Set the starting value for auto-generated IDs.
    ///
    /// This is useful when you already know the maximum existing ID in the document
    /// (e.g., from an LSP document index) and want to avoid re-scanning the content.
    ///
    /// The emitter will assign IDs starting from this value.
    pub fn with_starting_auto_id(self, starting_id: u64) -> Self {
        if let Ok(mut next_id) = self.next_auto_id.lock() {
            *next_id = starting_id;
        }
        self
    }

    /// Flush any accumulated text lines as a TextFinished event
    fn flush_accumulated_text(&mut self) {
        if !self.accumulated_text_lines.is_empty() {
            // Parse links from each text line
            let links: Vec<Vec<crate::parsers::wasot::Link>> = self
                .accumulated_text_lines
                .iter()
                .map(|line| {
                    use crate::parsers::wasot::{detect_links, parse_link};
                    let spans = detect_links(line);
                    spans
                        .into_iter()
                        .map(|span| parse_link(&span.content, span.start_index, span.length))
                        .collect()
                })
                .collect();

            // Parse existing footnotes from all text lines
            let footnotes = parse_footnotes(&self.accumulated_text_lines);

            // Create the initial event
            let event = Event::TextFinished {
                lines: self.accumulated_text_lines.clone(),
                links,
                footnotes,
            };

            // Process through interceptor chain
            let processed_event = self.interceptor_chain.process(event);

            // Broadcast the processed event
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(processed_event);
            }

            self.accumulated_text_lines.clear();
            self.first_text_line_index = None;
        }
    }

    /// Add a text line to the accumulator
    fn accumulate_text_line(&mut self, line: String, line_index: usize) {
        if self.first_text_line_index.is_none() {
            self.first_text_line_index = Some(line_index);
        }
        self.accumulated_text_lines.push(line);
    }

    /// Process a single line and emit events to the bus
    fn process_line_with_events(
        &mut self,
        element_parser: &mut ElementParser,
        line: &str,
        line_index: usize,
    ) -> Result<(), String> {
        // First, process with ElementParser to detect auto-closing
        let closed_count = element_parser.process_line(line, line_index)?;

        // If blocks were closed and we have accumulated text, flush it first
        if closed_count > 0 && !self.accumulated_text_lines.is_empty() {
            self.flush_accumulated_text();
        }

        // Emit BlockFinished events for each block that was auto-closed BEFORE emitting the new block
        for _ in 0..closed_count {
            let event = Event::BlockFinished;
            if let Ok(mut bus) = self.bus.lock() {
                bus.broadcast(event);
            }
        }

        // Now emit appropriate events based on line type
        if is_meta_line(line) {
            // Flush any remaining accumulated text before processing meta line
            self.flush_accumulated_text();

            if let Some(mut meta_line) = MetaLine::parse(line, line_index) {
                // Don't emit events for explicit close blocks - they were already handled by ElementParser
                if meta_line.type_name == "/" {
                    // This was already handled by ElementParser above
                } else if meta_line.type_name.starts_with('/') {
                    // Context line (./mytag or ../mytag) - strip leading '/' and emit ContextLine
                    meta_line.type_name = meta_line.type_name[1..].to_string();
                    let event = Event::ContextLine { meta_line };
                    if let Ok(mut bus) = self.bus.lock() {
                        bus.broadcast(event);
                    }
                } else {
                    // Auto-generate ID if needed (but not for close blocks or implicit text blocks)
                    if self.auto_generate_ids
                        && meta_line.id.is_empty()
                        && meta_line.type_name != "text"
                    {
                        if let Ok(mut next_id) = self.next_auto_id.lock() {
                            meta_line.id = crate::parsers::wasot::Id::from_u64(*next_id);
                            *next_id += 1;
                        }
                    }

                    // Check if this is a heading (empty type_name means heading)
                    let is_heading = meta_line.type_name.is_empty();

                    // Check if this is a void element (self-closing with no children)
                    if meta_line.is_void {
                        // Emit VoidElement event - self-closing, no BlockFinished needed
                        let event = Event::VoidElement {
                            meta_line: meta_line.clone(),
                        };
                        if let Ok(mut bus) = self.bus.lock() {
                            bus.broadcast(event);
                        }
                    } else {
                        // Emit BlockStarted event
                        let event = Event::BlockStarted {
                            meta_line: meta_line.clone(),
                        };
                        if let Ok(mut bus) = self.bus.lock() {
                            bus.broadcast(event);
                        }

                        // If it's a heading, immediately emit BlockFinished since headings are self-closing
                        if is_heading {
                            let event = Event::BlockFinished;
                            if let Ok(mut bus) = self.bus.lock() {
                                bus.broadcast(event);
                            }
                        }
                    }
                }
            }
        } else {
            // This is text content - accumulate it
            self.accumulate_text_line(line.to_string(), line_index);
        }

        Ok(())
    }
}

impl Emitter for WasotFileEmitter {
    type Reader = BusReaderIterator;

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

    fn run(mut self) {
        // Add FootnoteInterceptor if auto_convert_links_to_footnotes is enabled
        if self.auto_convert_links_to_footnotes {
            self.interceptor_chain = self
                .interceptor_chain
                .add_interceptor(FootnoteInterceptor::new());
        }

        // Initialize interceptor chain
        self.interceptor_chain.initialize();

        // First pass: scan for maximum ID if we have a file and auto-generation is enabled
        if self.auto_generate_ids {
            if let Some(ref file_path) = self.file_path {
                if let Ok(Some(max)) = IdScanner::scan_file(file_path) {
                    // Set next_auto_id to max + 1
                    if let Ok(mut next_id) = self.next_auto_id.lock() {
                        *next_id = max + 1;
                    }
                }
            }
        }

        // Create an element parser in streaming mode (memory-efficient, doesn't accumulate blocks)
        let mut element_parser = ElementParser::new_streaming();

        // Get the reader
        let reader: Box<dyn Read + Send + Sync> = match (&self.file_path, &mut self.reader) {
            (Some(path), _) => match File::open(path) {
                Ok(file) => Box::new(file),
                Err(e) => {
                    eprintln!("Error opening file: {e}");
                    return;
                }
            },
            (None, Some(reader)) => {
                // We need to take ownership of the reader to avoid double-borrow
                let mut buffer = Vec::new();
                match reader.read_to_end(&mut buffer) {
                    Ok(_) => Box::new(io::Cursor::new(buffer)),
                    Err(e) => {
                        eprintln!("Error reading from reader: {e}");
                        return;
                    }
                }
            }
            (None, None) => {
                eprintln!("No file path or reader provided");
                return;
            }
        };

        let buf_reader = BufReader::new(reader);

        // Process each line and emit events
        for (line_index, line_result) in buf_reader.lines().enumerate() {
            let line = match line_result {
                Ok(line) => line,
                Err(e) => {
                    eprintln!("Error reading line: {e}");
                    break;
                }
            };

            // Process line with custom event emission
            if let Err(e) = self.process_line_with_events(&mut element_parser, &line, line_index) {
                eprintln!("Error processing line: {e}");
                break;
            }
        }

        // Flush any remaining accumulated text
        self.flush_accumulated_text();

        // Finish parsing and emit BlockFinished events
        match element_parser.finish() {
            Ok(closed_count) => {
                // Emit BlockFinished events for each block that was closed during finish
                for _ in 0..closed_count {
                    let event = Event::BlockFinished;
                    if let Ok(mut bus) = self.bus.lock() {
                        bus.broadcast(event);
                    }
                }
            }
            Err(e) => {
                eprintln!("Error finishing parsing: {e}");
            }
        }

        // Finalize interceptor chain
        self.interceptor_chain.finalize();
    }
}

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

    #[test]
    fn test_auto_closing() {
        use std::thread;

        let content = ".outer\n..section\n...paragraph\n.outer";
        // Actual event sequence based on implementation:
        // BlockStarted outer
        // BlockStarted section
        // BlockStarted paragraph
        // BlockFinished (paragraph)
        // BlockFinished (section)
        // BlockFinished (outer)
        // BlockStarted outer
        // BlockFinished (outer)

        let emitter = WasotFileEmitter::from_string(content.to_string());
        let receiver = emitter.subscribe();

        // Run the emitter in a background thread
        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut collected_events = Vec::new();

        // Collect all events
        for event in receiver {
            collected_events.push(event.clone());

            // Stop after we've collected enough events
            if collected_events.len() >= 8 {
                break;
            }
        }

        // Wait for the emitter to finish
        let _ = handle.join();

        // Verify we have the expected number of events
        assert_eq!(collected_events.len(), 8, "Expected 8 events total");

        // Verify the sequence of events
        // Event 0: BlockStarted outer
        match &collected_events[0] {
            Event::BlockStarted { meta_line } => {
                assert_eq!(
                    meta_line.type_name, "outer",
                    "First block should be 'outer'"
                );
            }
            _ => panic!("Expected BlockStarted for outer at position 0"),
        }

        // Event 1: BlockStarted section
        match &collected_events[1] {
            Event::BlockStarted { meta_line } => {
                assert_eq!(
                    meta_line.type_name, "section",
                    "Second block should be 'section'"
                );
            }
            _ => panic!("Expected BlockStarted for section at position 1"),
        }

        // Event 2: BlockStarted paragraph
        match &collected_events[2] {
            Event::BlockStarted { meta_line } => {
                assert_eq!(
                    meta_line.type_name, "paragraph",
                    "Third block should be 'paragraph'"
                );
            }
            _ => panic!("Expected BlockStarted for paragraph at position 2"),
        }

        // Closing
        match &collected_events[3] {
            Event::BlockFinished => {}
            _ => panic!("Expected BlockFinished at position 3"),
        }

        // Closing
        match &collected_events[4] {
            Event::BlockFinished => {}
            _ => panic!("Expected BlockFinished at position 4"),
        }

        // Closing
        match &collected_events[5] {
            Event::BlockFinished => {}
            _ => panic!("Expected BlockFinished at position 5"),
        }

        // Event 3: BlockStarted outer (new block starts before auto-closing)
        match &collected_events[6] {
            Event::BlockStarted { meta_line } => {
                assert_eq!(
                    meta_line.type_name, "outer",
                    "Fourth block should be 'outer'"
                );
            }
            _ => panic!("Expected BlockStarted for outer at position 3"),
        }

        // Closing
        match &collected_events[7] {
            Event::BlockFinished => {}
            _ => panic!("Expected BlockFinished at position 7"),
        }
    }

    #[test]
    fn test_wasot_file_emitter_from_string() {
        use std::thread;

        let content = ".block\nTest content\n./";
        let emitter = WasotFileEmitter::from_string(content.to_string());
        let receiver = emitter.subscribe();

        // Run the emitter in a background thread
        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut collected_events = Vec::new();

        // Collect events
        for event in receiver.take(3) {
            collected_events.push(event);
        }

        // Wait for the emitter to finish
        let _ = handle.join();

        assert!(!collected_events.is_empty());

        // Should have BlockStarted, Text, BlockFinished (FileStarted/FileFinished not emitted by ElementParser)
        assert_eq!(collected_events.len(), 3);
    }

    #[test]
    fn test_text_batching() {
        use crate::Event;
        use std::thread;

        // Test that consecutive text lines are batched into a single TextFinished event
        let content = "Line 1\nLine 2\nLine 3\n.block\nBlock text line 1\nBlock text line 2";
        let emitter = WasotFileEmitter::from_string(content.to_string());
        let receiver = emitter.subscribe();

        // Run the emitter in a background thread
        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut collected_events = Vec::new();
        let mut consecutive_text_events = 0;
        let mut last_was_text = false;

        // Collect events and check for consecutive TextFinished events
        for event in receiver {
            match &event {
                Event::TextFinished { lines, .. } => {
                    // Debug assert: should never have consecutive TextFinished events
                    debug_assert!(
                        !last_was_text,
                        "Found consecutive TextFinished events - text batching failed!"
                    );

                    if last_was_text {
                        consecutive_text_events += 1;
                    }
                    last_was_text = true;

                    // Verify the first TextFinished event contains all 3 initial lines
                    if collected_events.is_empty() {
                        assert_eq!(
                            lines.len(),
                            3,
                            "First text event should contain 3 batched lines"
                        );
                        assert_eq!(lines[0], "Line 1");
                        assert_eq!(lines[1], "Line 2");
                        assert_eq!(lines[2], "Line 3");
                    }
                }
                Event::BlockStarted { .. }
                | Event::BlockFinished
                | Event::ContextLine { .. }
                | Event::VoidElement { .. } => {
                    last_was_text = false;
                }
            }
            collected_events.push(event);

            // Prevent infinite loop in case of issues
            if collected_events.len() > 10 {
                break;
            }
        }

        // Wait for the emitter to finish
        let _ = handle.join();

        // Should have no consecutive TextFinished events
        assert_eq!(
            consecutive_text_events, 0,
            "Found {consecutive_text_events} consecutive TextFinished events, but text should be batched"
        );

        // Should have at least: TextFinished (3 lines), BlockStarted, TextFinished (2 lines), BlockFinished
        assert!(
            collected_events.len() >= 4,
            "Expected at least 4 events, got {}",
            collected_events.len()
        );
    }

    #[test]
    fn test_heading_self_closing() {
        use crate::Event;
        use std::thread;

        // Test that headings emit both BlockStarted and BlockFinished immediately
        let content = ".block\nBlock content\n..\nHeading content\n.footer\nFooter content";
        let emitter = WasotFileEmitter::from_string(content.to_string());
        let receiver = emitter.subscribe();

        // Run the emitter in a background thread
        let handle = thread::spawn(move || {
            emitter.run();
        });

        let mut collected_events = Vec::new();
        let mut block_stack = Vec::new();

        // Simulate what DemoConsumer does to verify proper nesting
        for event in receiver {
            match &event {
                Event::BlockStarted { meta_line } => {
                    block_stack.push(meta_line.type_name.clone());
                }
                Event::BlockFinished => {
                    block_stack.pop();
                }
                Event::TextFinished { .. }
                | Event::ContextLine { .. }
                | Event::VoidElement { .. } => {}
            }
            collected_events.push(event);

            // Prevent infinite loop
            if collected_events.len() > 20 {
                break;
            }
        }

        // Wait for the emitter to finish
        let _ = handle.join();

        // At the end, all blocks should be properly closed (empty stack)
        assert!(
            block_stack.is_empty(),
            "Found unclosed blocks: {block_stack:?}. All blocks should be properly closed."
        );

        // Should have collected several events
        assert!(
            collected_events.len() >= 6,
            "Expected at least 6 events (3 BlockStarted, 3 TextFinished), got {}",
            collected_events.len()
        );
    }

    #[test]
    fn test_from_file_constructor() {
        use std::path::PathBuf;

        // Test lines 36-38 and 41, 44, 46 in from_file function
        let test_path = PathBuf::from("test_file.wasot");
        let emitter = WasotFileEmitter::from_file(&test_path);

        // Verify the fields are set correctly
        assert!(emitter.file_path.is_some());
        assert_eq!(emitter.file_path.unwrap(), "test_file.wasot");
        assert!(emitter.reader.is_none());
        assert!(emitter.accumulated_text_lines.is_empty());
        assert!(emitter.first_text_line_index.is_none());
        assert!(emitter.auto_convert_links_to_footnotes);
    }

    #[test]
    fn test_from_reader_constructor() {
        use std::io::Cursor;

        // Test lines 53-55, 59, 61, 63 in from_reader function
        let data = "test content";
        let cursor = Cursor::new(data);
        let emitter = WasotFileEmitter::from_reader(cursor);

        // Verify the fields are set correctly
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
        assert!(emitter.accumulated_text_lines.is_empty());
        assert!(emitter.first_text_line_index.is_none());
        assert!(emitter.auto_convert_links_to_footnotes);
    }

    #[test]
    fn test_from_stdin_constructor() {
        // Test lines 70-71 in from_stdin function
        let emitter = WasotFileEmitter::from_stdin();

        // Verify it creates an emitter with no file path and a reader
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
    }

    #[test]
    fn test_from_string_constructor() {
        // Test lines 75-76 in from_string function
        let content = "test content";
        let emitter = WasotFileEmitter::from_string(content.to_string());

        // Verify it creates an emitter with no file path and a reader
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
    }

    #[test]
    fn test_with_auto_convert_links_to_footnotes() {
        use std::io::Cursor;

        // Test lines 80-82 in with_auto_convert_links_to_footnotes function
        let cursor = Cursor::new("test");
        let emitter =
            WasotFileEmitter::from_reader(cursor).with_auto_convert_links_to_footnotes(false);

        assert!(!emitter.auto_convert_links_to_footnotes);

        // Test setting it to true
        let cursor2 = Cursor::new("test");
        let emitter2 =
            WasotFileEmitter::from_reader(cursor2).with_auto_convert_links_to_footnotes(true);

        assert!(emitter2.auto_convert_links_to_footnotes);
    }

    #[test]
    fn test_flush_accumulated_text_empty() {
        use std::io::Cursor;

        // Test that flush_accumulated_text handles empty accumulated text lines
        let cursor = Cursor::new("test");
        let mut emitter = WasotFileEmitter::from_reader(cursor);

        // Should not panic when there are no accumulated lines
        emitter.flush_accumulated_text();

        // Verify state remains consistent
        assert!(emitter.accumulated_text_lines.is_empty());
        assert!(emitter.first_text_line_index.is_none());
    }

    #[test]
    fn test_accumulate_text_line() {
        use std::io::Cursor;

        // Test lines 126-128 and 130 in accumulate_text_line function
        let cursor = Cursor::new("test");
        let mut emitter = WasotFileEmitter::from_reader(cursor);

        // First line should set first_text_line_index
        emitter.accumulate_text_line("Line 1".to_string(), 5);
        assert_eq!(emitter.first_text_line_index, Some(5));
        assert_eq!(emitter.accumulated_text_lines.len(), 1);
        assert_eq!(emitter.accumulated_text_lines[0], "Line 1");

        // Second line should not change first_text_line_index
        emitter.accumulate_text_line("Line 2".to_string(), 6);
        assert_eq!(emitter.first_text_line_index, Some(5)); // Unchanged
        assert_eq!(emitter.accumulated_text_lines.len(), 2);
        assert_eq!(emitter.accumulated_text_lines[1], "Line 2");
    }

    #[test]
    fn test_subscribe_method() {
        use std::io::Cursor;

        // Test lines 206-208 in subscribe function
        let cursor = Cursor::new("test");
        let emitter = WasotFileEmitter::from_reader(cursor);

        // Should be able to create multiple subscriptions
        let _reader1 = emitter.subscribe();
        let _reader2 = emitter.subscribe();

        // Both should be valid BusReaderIterator instances
    }

    #[test]
    fn test_emitter_fields_initialization() {
        use std::io::Cursor;

        // Test that all fields are properly initialized
        let cursor = Cursor::new("test content");
        let emitter = WasotFileEmitter::from_reader(cursor);

        // Verify all expected fields are initialized correctly
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
        assert!(emitter.accumulated_text_lines.is_empty());
        assert!(emitter.first_text_line_index.is_none());
        assert!(emitter.auto_convert_links_to_footnotes);
        assert!(emitter.auto_generate_ids);

        // Verify the Arc<Mutex<u64>> is initialized to 1
        assert_eq!(*emitter.next_auto_id.lock().unwrap(), 1);
    }

    #[test]
    fn test_with_auto_generate_ids() {
        use std::io::Cursor;

        // Test the with_auto_generate_ids builder method
        let cursor = Cursor::new("test");
        let emitter = WasotFileEmitter::from_reader(cursor).with_auto_generate_ids(false);

        assert!(!emitter.auto_generate_ids);

        // Test setting it to true
        let cursor2 = Cursor::new("test");
        let emitter2 = WasotFileEmitter::from_reader(cursor2).with_auto_generate_ids(true);

        assert!(emitter2.auto_generate_ids);
    }

    #[test]
    fn test_no_auto_ids_emits_empty_ids() {
        use crate::Event;
        use std::thread;

        // Test that blocks without explicit IDs have empty IDs when auto-generation is disabled
        let content = ".block\nSome text\n./";
        let emitter =
            WasotFileEmitter::from_string(content.to_string()).with_auto_generate_ids(false);
        let receiver = emitter.subscribe();

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

        let mut found_block = false;
        for event in receiver {
            if let Event::BlockStarted { meta_line } = event {
                if meta_line.type_name == "block" {
                    // ID should be empty when auto-generation is disabled
                    assert!(
                        meta_line.id.is_empty(),
                        "Expected empty ID, got: {}",
                        meta_line.id.value
                    );
                    found_block = true;
                    break;
                }
            }
        }

        let _ = handle.join();
        assert!(found_block, "Should have found a block event");
    }

    #[test]
    fn test_auto_ids_generates_ids() {
        use crate::Event;
        use std::thread;

        // Test that blocks without explicit IDs get auto-generated IDs when enabled (default)
        let content = ".block\nSome text\n./";
        let emitter = WasotFileEmitter::from_string(content.to_string());
        let receiver = emitter.subscribe();

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

        let mut found_block = false;
        for event in receiver {
            if let Event::BlockStarted { meta_line } = event {
                if meta_line.type_name == "block" {
                    // ID should NOT be empty when auto-generation is enabled
                    assert!(
                        !meta_line.id.is_empty(),
                        "Expected auto-generated ID, got empty"
                    );
                    found_block = true;
                    break;
                }
            }
        }

        let _ = handle.join();
        assert!(found_block, "Should have found a block event");
    }
}
