use crate::emitters::jsonl_emitter::{JsonlBlock, JsonlElement, JsonlQuotedString};
use crate::events::Event;
use crate::parsers::wasot::{Block, Element};
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;

/// JSON Lines exporter that writes root-level blocks to a file
pub struct ToJsonlConsumer {
    writer: BufWriter<File>,
    block_stack: Vec<Block>,
}

/// Convert a Block to JsonlBlock format for roundtrip compatibility
fn block_to_jsonl(block: &Block) -> JsonlBlock {
    let meta = &block.meta_line;

    // Convert args with quoting info
    let args: Vec<JsonlQuotedString> = meta
        .arguments
        .args
        .iter()
        .map(|a| JsonlQuotedString {
            content: a.content.clone(),
            quoted: a.quoted,
        })
        .collect();

    // Convert kwargs with quoting info
    let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta
        .arguments
        .kwargs
        .iter()
        .map(|(k, v)| {
            (
                JsonlQuotedString {
                    content: k.content.clone(),
                    quoted: k.quoted,
                },
                JsonlQuotedString {
                    content: v.content.clone(),
                    quoted: v.quoted,
                },
            )
        })
        .collect();

    // Convert children to JsonlElement content
    let content: Vec<JsonlElement> = block
        .children
        .iter()
        .map(|child| match child {
            Element::Text(text) => JsonlElement::Text {
                lines: text.lines.clone(),
            },
            Element::Block(nested_block) => JsonlElement::Block(block_to_jsonl(nested_block)),
            Element::ContextLine(meta) => {
                // Convert context lines to blocks with is_context_line flag
                let args: Vec<JsonlQuotedString> = meta
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta.inline_text.content.clone(),
                        quoted: meta.inline_text.quoted,
                    })
                };
                let id = if meta.id.value.is_empty() {
                    None
                } else {
                    Some(meta.id.value.clone())
                };
                JsonlElement::Block(JsonlBlock {
                    block_type: meta.type_name.clone(),
                    id,
                    level: meta.level,
                    is_void: false,
                    is_context_line: true,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                })
            }
            Element::VoidElement(meta) => {
                // Convert void elements to blocks with is_void: true
                let args: Vec<JsonlQuotedString> = meta
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta.inline_text.content.clone(),
                        quoted: meta.inline_text.quoted,
                    })
                };
                let id = if meta.id.value.is_empty() {
                    None
                } else {
                    Some(meta.id.value.clone())
                };
                JsonlElement::Block(JsonlBlock {
                    block_type: meta.type_name.clone(),
                    id,
                    level: meta.level,
                    is_void: true,
                    is_context_line: false,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                })
            }
        })
        .collect();

    // Get inline text (non-empty only) with quoting info
    let inline_text = if meta.inline_text.content.is_empty() {
        None
    } else {
        Some(JsonlQuotedString {
            content: meta.inline_text.content.clone(),
            quoted: meta.inline_text.quoted,
        })
    };

    // Get id (non-empty only)
    let id = if meta.id.value.is_empty() {
        None
    } else {
        Some(meta.id.value.clone())
    };

    JsonlBlock {
        block_type: meta.type_name.clone(),
        id,
        level: meta.level,
        is_void: meta.is_void,
        is_context_line: false,
        args,
        kwargs,
        inline_text,
        content,
    }
}

impl ToJsonlConsumer {
    /// Create a new JsonLinesExporter that writes to the given file path
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
        let file = File::create(path)?;
        let writer = BufWriter::new(file);
        Ok(ToJsonlConsumer {
            writer,
            block_stack: Vec::new(),
        })
    }

    /// Write a block as a JSON line in JsonlElement format
    fn write_block(&mut self, block: &Block) -> Result<(), Box<dyn std::error::Error>> {
        let jsonl_block = block_to_jsonl(block);
        let element = JsonlElement::Block(jsonl_block);
        let json = serde_json::to_string(&element)?;
        writeln!(self.writer, "{json}")?;
        self.writer.flush()?;
        Ok(())
    }

    /// Finish writing and close the file
    pub fn finish(mut self) -> Result<(), std::io::Error> {
        self.writer.flush()
    }
}

impl ToJsonlConsumer {
    /// Process an event
    pub fn process_event(&mut self, event: &Event) {
        match event {
            Event::BlockStarted { meta_line, .. } => {
                // Check if this is a void block (like headings) - handle immediately
                if meta_line.is_void {
                    // Void blocks are self-closing - handle like VoidElement
                    if let Some(parent) = self.block_stack.last_mut() {
                        parent.add_child(Element::VoidElement(meta_line.clone()));
                        return;
                    }
                    // Root-level void block - write as JsonlElement::Block
                    let args: Vec<JsonlQuotedString> = meta_line
                        .arguments
                        .args
                        .iter()
                        .map(|a| JsonlQuotedString {
                            content: a.content.clone(),
                            quoted: a.quoted,
                        })
                        .collect();
                    let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                        .arguments
                        .kwargs
                        .iter()
                        .map(|(k, v)| {
                            (
                                JsonlQuotedString {
                                    content: k.content.clone(),
                                    quoted: k.quoted,
                                },
                                JsonlQuotedString {
                                    content: v.content.clone(),
                                    quoted: v.quoted,
                                },
                            )
                        })
                        .collect();
                    let inline_text = if meta_line.inline_text.content.is_empty() {
                        None
                    } else {
                        Some(JsonlQuotedString {
                            content: meta_line.inline_text.content.clone(),
                            quoted: meta_line.inline_text.quoted,
                        })
                    };
                    let id = if meta_line.id.value.is_empty() {
                        None
                    } else {
                        Some(meta_line.id.value.clone())
                    };
                    let jsonl_block = JsonlBlock {
                        block_type: meta_line.type_name.clone(),
                        id,
                        level: meta_line.level,
                        is_void: true,
                        is_context_line: false,
                        args,
                        kwargs,
                        inline_text,
                        content: vec![],
                    };
                    let element = JsonlElement::Block(jsonl_block);
                    if let Ok(json) = serde_json::to_string(&element) {
                        if let Err(e) = writeln!(self.writer, "{json}") {
                            eprintln!("Error writing void block to JSON: {e}");
                        }
                        let _ = self.writer.flush();
                    }
                    return;
                }
                // Regular block - create and push onto stack for later use in BlockFinished
                let block = Block::new(meta_line.clone());
                self.block_stack.push(block);
            }
            Event::BlockFinished => {
                // Pop the block from our stack
                if let Some(block) = self.block_stack.pop() {
                    if block.level() == 1 {
                        // Root-level block - write to output
                        if let Err(e) = self.write_block(&block) {
                            eprintln!("Error writing block to JSON: {e}");
                        }
                    } else {
                        // Nested block - add to parent's children if parent exists
                        if let Some(parent) = self.block_stack.last_mut() {
                            parent.add_child(Element::Block(block));
                        } else {
                            // No parent on stack - this is an orphaned nested block
                            // Still write it to output (e.g., headings at level 2+)
                            if let Err(e) = self.write_block(&block) {
                                eprintln!("Error writing orphaned nested block to JSON: {e}");
                            }
                        }
                    }
                } else {
                    eprintln!("Warning: BlockFinished event without matching BlockStarted");
                }
            }
            Event::TextFinished { lines, links, .. } => {
                if let Some(block) = self.block_stack.last_mut() {
                    // Add text content with links to the current block on the stack
                    for (i, line) in lines.iter().enumerate() {
                        let line_links = links.get(i).cloned().unwrap_or_default();
                        block.add_content_line_with_links(line.clone(), i, line_links);
                    }
                } else {
                    // Orphaned text (not inside any block) - write as JsonlElement::Text
                    let element = JsonlElement::Text {
                        lines: lines.clone(),
                    };
                    if let Ok(json) = serde_json::to_string(&element) {
                        if let Err(e) = writeln!(self.writer, "{json}") {
                            eprintln!("Error writing orphaned text to JSON: {e}");
                        }
                        let _ = self.writer.flush();
                    }
                }
            }
            Event::ContextLine { meta_line } => {
                // If inside a block, add to parent's children; otherwise write directly
                if let Some(parent) = self.block_stack.last_mut() {
                    parent.add_child(Element::ContextLine(meta_line.clone()));
                    return;
                }
                // Context lines (like document IDs) - write as JsonlElement::Block
                let args: Vec<JsonlQuotedString> = meta_line
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta_line.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta_line.inline_text.content.clone(),
                        quoted: meta_line.inline_text.quoted,
                    })
                };
                let id = if meta_line.id.value.is_empty() {
                    None
                } else {
                    Some(meta_line.id.value.clone())
                };
                let jsonl_block = JsonlBlock {
                    block_type: meta_line.type_name.clone(),
                    id,
                    level: meta_line.level,
                    is_void: false,
                    is_context_line: true,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                };
                let element = JsonlElement::Block(jsonl_block);
                if let Ok(json) = serde_json::to_string(&element) {
                    if let Err(e) = writeln!(self.writer, "{json}") {
                        eprintln!("Error writing context line to JSON: {e}");
                    }
                    let _ = self.writer.flush();
                }
            }
            Event::VoidElement { meta_line } => {
                // If inside a block, add to parent's children; otherwise write directly
                if let Some(parent) = self.block_stack.last_mut() {
                    parent.add_child(Element::VoidElement(meta_line.clone()));
                    return;
                }
                // Void elements are self-closing - write as JsonlElement::Block with empty content
                let args: Vec<JsonlQuotedString> = meta_line
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta_line.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta_line.inline_text.content.clone(),
                        quoted: meta_line.inline_text.quoted,
                    })
                };
                let id = if meta_line.id.value.is_empty() {
                    None
                } else {
                    Some(meta_line.id.value.clone())
                };
                let jsonl_block = JsonlBlock {
                    block_type: meta_line.type_name.clone(),
                    id,
                    level: meta_line.level,
                    is_void: true,
                    is_context_line: false,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                };
                let element = JsonlElement::Block(jsonl_block);
                if let Ok(json) = serde_json::to_string(&element) {
                    if let Err(e) = writeln!(self.writer, "{json}") {
                        eprintln!("Error writing void element to JSON: {e}");
                    }
                    let _ = self.writer.flush();
                }
            }
        }
    }
}

// Note: Consumer trait implementation for ToJsonlConsumer is not provided
// because it requires a file path for creation, making it incompatible
// with the generic Consumer interface. Use the process_event method
// directly when you need file-based JSON Lines export.

/// Streaming JSON exporter that handles large files by writing blocks as they're processed
pub struct StreamingJsonExporter {
    writer: BufWriter<File>,
    block_stack: Vec<Block>,
}

impl StreamingJsonExporter {
    /// Create a new StreamingJsonExporter that writes to the given file path
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
        let file = File::create(path)?;
        let writer = BufWriter::new(file);
        Ok(StreamingJsonExporter {
            writer,
            block_stack: Vec::new(),
        })
    }

    /// Write a block as a JSON line in JsonlElement format
    fn write_block(&mut self, block: &Block) -> Result<(), Box<dyn std::error::Error>> {
        let jsonl_block = block_to_jsonl(block);
        let element = JsonlElement::Block(jsonl_block);
        let json = serde_json::to_string(&element)?;
        writeln!(self.writer, "{json}")?;
        self.writer.flush()?;
        Ok(())
    }

    /// Finish writing and close the file
    pub fn finish(mut self) -> Result<(), std::io::Error> {
        self.writer.flush()
    }
}

impl StreamingJsonExporter {
    /// Process an event
    pub fn process_event(&mut self, event: &Event) {
        match event {
            Event::BlockStarted { meta_line, .. } => {
                // Check if this is a void block (like headings) - handle immediately
                if meta_line.is_void {
                    // Void blocks are self-closing - handle like VoidElement
                    if let Some(parent) = self.block_stack.last_mut() {
                        parent.add_child(Element::VoidElement(meta_line.clone()));
                        return;
                    }
                    // Root-level void block - write as JsonlElement::Block
                    let args: Vec<JsonlQuotedString> = meta_line
                        .arguments
                        .args
                        .iter()
                        .map(|a| JsonlQuotedString {
                            content: a.content.clone(),
                            quoted: a.quoted,
                        })
                        .collect();
                    let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                        .arguments
                        .kwargs
                        .iter()
                        .map(|(k, v)| {
                            (
                                JsonlQuotedString {
                                    content: k.content.clone(),
                                    quoted: k.quoted,
                                },
                                JsonlQuotedString {
                                    content: v.content.clone(),
                                    quoted: v.quoted,
                                },
                            )
                        })
                        .collect();
                    let inline_text = if meta_line.inline_text.content.is_empty() {
                        None
                    } else {
                        Some(JsonlQuotedString {
                            content: meta_line.inline_text.content.clone(),
                            quoted: meta_line.inline_text.quoted,
                        })
                    };
                    let id = if meta_line.id.value.is_empty() {
                        None
                    } else {
                        Some(meta_line.id.value.clone())
                    };
                    let jsonl_block = JsonlBlock {
                        block_type: meta_line.type_name.clone(),
                        id,
                        level: meta_line.level,
                        is_void: true,
                        is_context_line: false,
                        args,
                        kwargs,
                        inline_text,
                        content: vec![],
                    };
                    let element = JsonlElement::Block(jsonl_block);
                    if let Ok(json) = serde_json::to_string(&element) {
                        if let Err(e) = writeln!(self.writer, "{json}") {
                            eprintln!("Error writing void block to JSON: {e}");
                        }
                        let _ = self.writer.flush();
                    }
                    return;
                }
                // Regular block - create and push onto stack
                let block = Block::new(meta_line.clone());
                self.block_stack.push(block);
            }
            Event::BlockFinished => {
                // Pop the block from our stack
                if let Some(block) = self.block_stack.pop() {
                    if block.level() == 1 {
                        // Root-level block - write to output
                        if let Err(e) = self.write_block(&block) {
                            eprintln!("Error writing block to JSON: {e}");
                        }
                    } else {
                        // Nested block - add to parent's children if parent exists
                        if let Some(parent) = self.block_stack.last_mut() {
                            parent.add_child(Element::Block(block));
                        } else {
                            // No parent on stack - this is an orphaned nested block
                            // Still write it to output (e.g., headings at level 2+)
                            if let Err(e) = self.write_block(&block) {
                                eprintln!("Error writing orphaned nested block to JSON: {e}");
                            }
                        }
                    }
                } else {
                    eprintln!("Warning: BlockFinished event without matching BlockStarted");
                }
            }
            Event::TextFinished { lines, links, .. } => {
                if let Some(block) = self.block_stack.last_mut() {
                    // Add text content with links to the current block on the stack
                    for (i, line) in lines.iter().enumerate() {
                        let line_links = links.get(i).cloned().unwrap_or_default();
                        block.add_content_line_with_links(line.clone(), i, line_links);
                    }
                } else {
                    // Orphaned text (not inside any block) - write as JsonlElement::Text
                    let element = JsonlElement::Text {
                        lines: lines.clone(),
                    };
                    if let Ok(json) = serde_json::to_string(&element) {
                        if let Err(e) = writeln!(self.writer, "{json}") {
                            eprintln!("Error writing orphaned text to JSON: {e}");
                        }
                        let _ = self.writer.flush();
                    }
                }
            }
            Event::ContextLine { meta_line } => {
                // If inside a block, add to parent's children; otherwise write directly
                if let Some(parent) = self.block_stack.last_mut() {
                    parent.add_child(Element::ContextLine(meta_line.clone()));
                    return;
                }
                // Context lines (like document IDs) - write as JsonlElement::Block
                let args: Vec<JsonlQuotedString> = meta_line
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta_line.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta_line.inline_text.content.clone(),
                        quoted: meta_line.inline_text.quoted,
                    })
                };
                let id = if meta_line.id.value.is_empty() {
                    None
                } else {
                    Some(meta_line.id.value.clone())
                };
                let jsonl_block = JsonlBlock {
                    block_type: meta_line.type_name.clone(),
                    id,
                    level: meta_line.level,
                    is_void: false,
                    is_context_line: true,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                };
                let element = JsonlElement::Block(jsonl_block);
                if let Ok(json) = serde_json::to_string(&element) {
                    if let Err(e) = writeln!(self.writer, "{json}") {
                        eprintln!("Error writing context line to JSON: {e}");
                    }
                    let _ = self.writer.flush();
                }
            }
            Event::VoidElement { meta_line } => {
                // If inside a block, add to parent's children; otherwise write directly
                if let Some(parent) = self.block_stack.last_mut() {
                    parent.add_child(Element::VoidElement(meta_line.clone()));
                    return;
                }
                // Void elements are self-closing - write as JsonlElement::Block with empty content
                let args: Vec<JsonlQuotedString> = meta_line
                    .arguments
                    .args
                    .iter()
                    .map(|a| JsonlQuotedString {
                        content: a.content.clone(),
                        quoted: a.quoted,
                    })
                    .collect();
                let kwargs: Vec<(JsonlQuotedString, JsonlQuotedString)> = meta_line
                    .arguments
                    .kwargs
                    .iter()
                    .map(|(k, v)| {
                        (
                            JsonlQuotedString {
                                content: k.content.clone(),
                                quoted: k.quoted,
                            },
                            JsonlQuotedString {
                                content: v.content.clone(),
                                quoted: v.quoted,
                            },
                        )
                    })
                    .collect();
                let inline_text = if meta_line.inline_text.content.is_empty() {
                    None
                } else {
                    Some(JsonlQuotedString {
                        content: meta_line.inline_text.content.clone(),
                        quoted: meta_line.inline_text.quoted,
                    })
                };
                let id = if meta_line.id.value.is_empty() {
                    None
                } else {
                    Some(meta_line.id.value.clone())
                };
                let jsonl_block = JsonlBlock {
                    block_type: meta_line.type_name.clone(),
                    id,
                    level: meta_line.level,
                    is_void: true,
                    is_context_line: false,
                    args,
                    kwargs,
                    inline_text,
                    content: vec![],
                };
                let element = JsonlElement::Block(jsonl_block);
                if let Ok(json) = serde_json::to_string(&element) {
                    if let Err(e) = writeln!(self.writer, "{json}") {
                        eprintln!("Error writing void element to JSON: {e}");
                    }
                    let _ = self.writer.flush();
                }
            }
        }
    }
}

// Note: Consumer trait implementation for StreamingJsonExporter is not provided
// because it requires a file path for creation, making it incompatible
// with the generic Consumer interface. Use the process_event method
// directly when you need file-based streaming JSON export.

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::Event;
    use crate::parsers::wasot::ParsedArguments;
    use crate::parsers::wasot::{Id, MetaLine, QuotedString};
    use std::fs;
    use std::io::Read;

    // Helper function to create test MetaLine
    fn create_test_meta_line(block_type: &str, level: usize) -> MetaLine {
        MetaLine {
            type_name: block_type.to_string(),
            level,
            inline_text: QuotedString::new("Test content".to_string(), false),
            id: Id::empty(),
            line_index: Some(0),
            is_void: false,
            arguments: ParsedArguments {
                args: Vec::new(),
                kwargs: Vec::new(),
            },
        }
    }

    // Helper function to create test Block
    fn create_test_block(block_type: &str, level: usize) -> Block {
        let meta_line = create_test_meta_line(block_type, level);
        Block::new(meta_line)
    }

    // Helper to read file contents
    fn read_file_contents(path: &std::path::Path) -> Result<String, std::io::Error> {
        let mut file = std::fs::File::open(path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }

    // Tests for ToJsonlConsumer

    #[test]
    fn test_to_jsonl_consumer_new() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_new.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let consumer = ToJsonlConsumer::new(&test_file);
        assert!(consumer.is_ok());

        let consumer = consumer.unwrap();
        assert!(consumer.block_stack.is_empty());

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_new_invalid_path() {
        let invalid_path = "/invalid/nonexistent/path/file.jsonl";
        let result = ToJsonlConsumer::new(invalid_path);
        assert!(result.is_err());
    }

    #[test]
    fn test_to_jsonl_consumer_write_block() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_write_block.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();
        let block = create_test_block("paragraph", 1);

        let result = consumer.write_block(&block);
        assert!(result.is_ok());

        // Manually flush and finish
        drop(consumer);

        // Verify file contents
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("paragraph"));
        assert!(contents.contains("Test content"));

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_finish() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_finish.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let consumer = ToJsonlConsumer::new(&test_file).unwrap();
        let result = consumer.finish();
        assert!(result.is_ok());

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_process_event_block_started() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_block_started.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();
        let meta_line = create_test_meta_line("paragraph", 1);
        let event = Event::BlockStarted { meta_line };

        assert_eq!(consumer.block_stack.len(), 0);
        consumer.process_event(&event);
        assert_eq!(consumer.block_stack.len(), 1);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_process_event_block_finished_root_level() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_block_finished_root.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // First start a block
        let meta_line = create_test_meta_line("paragraph", 1);
        let start_event = Event::BlockStarted { meta_line };
        consumer.process_event(&start_event);

        assert_eq!(consumer.block_stack.len(), 1);

        // Then finish the block
        let finish_event = Event::BlockFinished;
        consumer.process_event(&finish_event);

        assert_eq!(consumer.block_stack.len(), 0);

        // Finish to flush
        drop(consumer);

        // Verify file contents - root level block should be written
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("paragraph"));

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_process_event_block_finished_nested_level() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_block_finished_nested.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Start a nested block (level 2)
        let meta_line = create_test_meta_line("nested", 2);
        let start_event = Event::BlockStarted { meta_line };
        consumer.process_event(&start_event);

        // Finish the nested block
        let finish_event = Event::BlockFinished;
        consumer.process_event(&finish_event);

        // Finish to flush
        drop(consumer);

        // Verify file contents - orphaned nested blocks SHOULD be written for proper roundtripping
        let contents = read_file_contents(&test_file).unwrap_or_default();
        assert!(
            contents.contains("nested"),
            "Orphaned nested blocks should be written for proper roundtripping"
        );

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_process_event_block_finished_no_matching_start() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_block_finished_no_start.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Try to finish a block without starting one
        let finish_event = Event::BlockFinished;
        consumer.process_event(&finish_event);

        // Should handle gracefully without panicking
        assert_eq!(consumer.block_stack.len(), 0);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_process_event_text_finished() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_text_finished.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();
        let text_event = Event::TextFinished {
            lines: vec!["Text line 1".to_string(), "Text line 2".to_string()],
            links: vec![vec![], vec![]],
            footnotes: vec![],
        };

        consumer.process_event(&text_event);
        // Should handle without errors - text events are ignored by ToJsonlConsumer

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    // Tests for StreamingJsonExporter

    #[test]
    fn test_streaming_json_exporter_new() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_new.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let exporter = StreamingJsonExporter::new(&test_file);
        assert!(exporter.is_ok());

        let exporter = exporter.unwrap();
        assert!(exporter.block_stack.is_empty());

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_new_invalid_path() {
        let invalid_path = "/invalid/nonexistent/path/file.jsonl";
        let result = StreamingJsonExporter::new(invalid_path);
        assert!(result.is_err());
    }

    #[test]
    fn test_streaming_json_exporter_write_block() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_write_block.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();
        let block = create_test_block("section", 1);

        let result = exporter.write_block(&block);
        assert!(result.is_ok());

        // Manually flush and finish
        drop(exporter);

        // Verify file contents
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("section"));
        assert!(contents.contains("Test content"));

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_finish() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_finish.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let exporter = StreamingJsonExporter::new(&test_file).unwrap();
        let result = exporter.finish();
        assert!(result.is_ok());

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_process_event_block_started() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_block_started.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();
        let meta_line = create_test_meta_line("header", 1);
        let event = Event::BlockStarted { meta_line };

        assert_eq!(exporter.block_stack.len(), 0);
        exporter.process_event(&event);
        assert_eq!(exporter.block_stack.len(), 1);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_process_event_block_finished_root_level() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_block_finished_root.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();

        // First start a block
        let meta_line = create_test_meta_line("article", 1);
        let start_event = Event::BlockStarted { meta_line };
        exporter.process_event(&start_event);

        assert_eq!(exporter.block_stack.len(), 1);

        // Then finish the block
        let finish_event = Event::BlockFinished;
        exporter.process_event(&finish_event);

        assert_eq!(exporter.block_stack.len(), 0);

        // Finish to flush
        drop(exporter);

        // Verify file contents - root level block should be written
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("article"));

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_process_event_block_finished_nested_level() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_block_finished_nested.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();

        // Start a nested block (level 3)
        let meta_line = create_test_meta_line("subsection", 3);
        let start_event = Event::BlockStarted { meta_line };
        exporter.process_event(&start_event);

        // Finish the nested block
        let finish_event = Event::BlockFinished;
        exporter.process_event(&finish_event);

        // Finish to flush
        drop(exporter);

        // Verify file contents - orphaned nested blocks ARE now written for roundtrip support
        let contents = read_file_contents(&test_file).unwrap_or_default();
        assert!(
            contents.contains("subsection"),
            "Orphaned nested blocks should be written for proper roundtripping"
        );

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_process_event_block_finished_no_matching_start() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_block_finished_no_start.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();

        // Try to finish a block without starting one
        let finish_event = Event::BlockFinished;
        exporter.process_event(&finish_event);

        // Should handle gracefully without panicking
        assert_eq!(exporter.block_stack.len(), 0);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_process_event_text_finished() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_text_finished.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();
        let text_event = Event::TextFinished {
            lines: vec!["Another text line".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };

        exporter.process_event(&text_event);
        // Should handle without errors - text events are ignored by StreamingJsonExporter

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    // Integration tests

    #[test]
    fn test_to_jsonl_consumer_full_workflow() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_full_workflow.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Process multiple blocks
        for i in 1..=3 {
            let meta_line = create_test_meta_line(&format!("block{i}"), 1);
            let start_event = Event::BlockStarted { meta_line };
            consumer.process_event(&start_event);

            let text_event = Event::TextFinished {
                lines: vec![format!("Content for block {i}")],
                links: vec![vec![]],
                footnotes: vec![],
            };
            consumer.process_event(&text_event);

            let finish_event = Event::BlockFinished;
            consumer.process_event(&finish_event);
        }

        // Finish to flush
        drop(consumer);

        // Verify all blocks were written
        let contents = read_file_contents(&test_file).unwrap();
        for i in 1..=3 {
            assert!(contents.contains(&format!("block{i}")));
        }

        // Should have 3 JSON lines
        let lines: Vec<&str> = contents.trim().split('\n').collect();
        assert_eq!(lines.len(), 3);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_full_workflow() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_full_workflow.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();

        // Process nested blocks - nested block should be included in root's content
        let root_meta = create_test_meta_line("root", 1);
        let root_start = Event::BlockStarted {
            meta_line: root_meta,
        };
        exporter.process_event(&root_start);

        let nested_meta = create_test_meta_line("nested", 2);
        let nested_start = Event::BlockStarted {
            meta_line: nested_meta,
        };
        exporter.process_event(&nested_start);

        let nested_finish = Event::BlockFinished;
        exporter.process_event(&nested_finish);

        let root_finish = Event::BlockFinished;
        exporter.process_event(&root_finish);

        // Finish to flush
        drop(exporter);

        // Verify root block was written with nested block in content
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("root"), "Should contain root block");
        assert!(
            contents.contains("\"nested\""),
            "Should contain nested block in root's content"
        );

        // Should have 1 JSON line (root block contains nested)
        let lines: Vec<&str> = contents
            .trim()
            .split('\n')
            .filter(|l| !l.is_empty())
            .collect();
        assert_eq!(lines.len(), 1);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_multiple_root_level_blocks() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_multiple_root_blocks.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Mix of root level and orphaned nested blocks
        let events = vec![
            // Root block 1
            Event::BlockStarted {
                meta_line: create_test_meta_line("paragraph", 1),
            },
            Event::BlockFinished,
            // Orphaned nested block (written for proper roundtripping)
            Event::BlockStarted {
                meta_line: create_test_meta_line("quote", 2),
            },
            Event::BlockFinished,
            // Root block 2
            Event::BlockStarted {
                meta_line: create_test_meta_line("header", 1),
            },
            Event::BlockFinished,
        ];

        for event in events {
            consumer.process_event(&event);
        }

        // Finish to flush
        drop(consumer);

        // Verify all blocks were written (orphaned nested blocks are written for roundtripping)
        let contents = read_file_contents(&test_file).unwrap();
        assert!(contents.contains("paragraph"));
        assert!(contents.contains("header"));
        assert!(
            contents.contains("\"block_type\":\"quote\""),
            "Orphaned nested blocks should be written for proper roundtripping"
        );

        // Should have 3 JSON lines (all blocks written)
        let lines: Vec<&str> = contents
            .trim()
            .split('\n')
            .filter(|l| !l.is_empty())
            .collect();
        assert_eq!(lines.len(), 3);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_orphaned_text() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_orphaned_text.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Send text without any block - should be written as JsonlElement::Text
        let text_event = Event::TextFinished {
            lines: vec!["standalone text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };
        consumer.process_event(&text_event);

        // Finish to flush
        drop(consumer);

        // Verify JsonlElement::Text was written in the roundtrip-compatible format
        let contents = read_file_contents(&test_file).unwrap();
        assert!(
            contents.contains("\"text\""),
            "Should contain text element type"
        );
        assert!(
            contents.contains("standalone text"),
            "Should contain the text content"
        );
        assert!(
            contents.contains("\"lines\""),
            "Should contain lines field in JsonlElement::Text format"
        );

        // Should have 1 JSON line
        let lines: Vec<&str> = contents
            .trim()
            .split('\n')
            .filter(|l| !l.is_empty())
            .collect();
        assert_eq!(lines.len(), 1);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_to_jsonl_consumer_text_before_and_after_block() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_text_before_after.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut consumer = ToJsonlConsumer::new(&test_file).unwrap();

        // Orphaned text before block
        consumer.process_event(&Event::TextFinished {
            lines: vec!["leading text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });

        // Block with text
        let meta_line = create_test_meta_line("paragraph", 1);
        consumer.process_event(&Event::BlockStarted { meta_line });
        consumer.process_event(&Event::TextFinished {
            lines: vec!["block text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });
        consumer.process_event(&Event::BlockFinished);

        // Orphaned text after block
        consumer.process_event(&Event::TextFinished {
            lines: vec!["trailing text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });

        // Finish to flush
        drop(consumer);

        // Verify all elements were written
        let contents = read_file_contents(&test_file).unwrap();

        // Should have 3 JSON lines: text element, block, text element
        let json_lines: Vec<&str> = contents
            .trim()
            .split('\n')
            .filter(|l| !l.is_empty())
            .collect();
        assert_eq!(json_lines.len(), 3, "Should have 3 JSON lines");

        assert!(
            contents.contains("leading text"),
            "Should contain leading text"
        );
        assert!(contents.contains("block text"), "Should contain block text");
        assert!(
            contents.contains("trailing text"),
            "Should contain trailing text"
        );

        // Clean up
        let _ = fs::remove_file(&test_file);
    }

    #[test]
    fn test_streaming_json_exporter_orphaned_text() {
        let temp_dir = std::env::temp_dir();
        let test_file = temp_dir.join("test_streaming_orphaned_text.jsonl");

        // Clean up any existing file
        let _ = fs::remove_file(&test_file);

        let mut exporter = StreamingJsonExporter::new(&test_file).unwrap();

        // Send text without any block - should be written as Element::Text
        let text_event = Event::TextFinished {
            lines: vec!["orphaned streaming text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };
        exporter.process_event(&text_event);

        // Finish to flush
        drop(exporter);

        // Verify Element::Text was written
        let contents = read_file_contents(&test_file).unwrap();
        assert!(
            contents.contains("\"text\""),
            "Should contain text element type"
        );
        assert!(
            contents.contains("orphaned streaming text"),
            "Should contain the text content"
        );

        // Should have 1 JSON line
        let lines: Vec<&str> = contents
            .trim()
            .split('\n')
            .filter(|l| !l.is_empty())
            .collect();
        assert_eq!(lines.len(), 1);

        // Clean up
        let _ = fs::remove_file(&test_file);
    }
}
