use crate::events::{Consumer, Event};
use crate::parsers::wasot::{MetaLine, QuotedString};
use std::borrow::Cow;
use std::io::{self, Write};

/// Listener that reconstructs Wasot markup from events
pub struct WasotReconstructor<W: Write> {
    writer: W,
    block_stack: Vec<usize>,      // Track block levels for proper closing
    just_closed: bool,            // Track if the last event was a block close
    document_id_handled: bool,    // Whether we've handled the document ID
    generated_id: Option<String>, // Lazily generated document ID
}

impl WasotReconstructor<io::Stdout> {
    /// Create a new Wasot reconstructor that writes to stdout
    pub fn new() -> Self {
        Self {
            writer: io::stdout(),
            block_stack: Vec::new(),
            just_closed: false,
            document_id_handled: false,
            generated_id: None,
        }
    }
}

impl<W: Write> WasotReconstructor<W> {
    /// Create a new Wasot reconstructor with a custom writer
    pub fn new_with_writer(writer: W) -> Self {
        Self {
            writer,
            block_stack: Vec::new(),
            just_closed: false,
            document_id_handled: false,
            generated_id: None,
        }
    }

    /// Escape quotes in a string for inclusion in a quoted context
    /// Note: Only escape quotes, not backslashes, since the parser preserves backslashes as-is
    /// Returns Cow to avoid allocation when no quotes are present
    fn escape_quotes(s: &str) -> Cow<'_, str> {
        if s.contains('"') {
            Cow::Owned(s.replace('"', "\\\""))
        } else {
            Cow::Borrowed(s)
        }
    }

    /// Check if a string needs to be quoted (contains special characters)
    fn needs_quoting(s: &str) -> bool {
        s.contains('"') || s.contains(' ') || s.contains('=') || s.contains('\t')
    }

    /// Generate a UUID v4 encoded in base36
    /// UUID v4 has 122 random bits with version (4) and variant bits set
    fn generate_document_id() -> String {
        use rand::Rng;

        let mut rng = rand::thread_rng();
        let mut bytes = [0u8; 16];
        rng.fill(&mut bytes);

        // Set version to 4 (UUID v4)
        bytes[6] = (bytes[6] & 0x0f) | 0x40;
        // Set variant to RFC 4122
        bytes[8] = (bytes[8] & 0x3f) | 0x80;

        // Convert to u128 and encode as base36
        let value = u128::from_be_bytes(bytes);
        crate::base36::u128_to_base36(value)
    }

    /// Get or generate the document ID
    fn get_or_generate_id(&mut self) -> String {
        if self.generated_id.is_none() {
            self.generated_id = Some(Self::generate_document_id());
        }
        self.generated_id.clone().unwrap()
    }

    /// Ensure document ID is written (call before first content)
    fn ensure_document_id(&mut self) -> io::Result<()> {
        if !self.document_id_handled {
            let id = self.get_or_generate_id();
            writeln!(self.writer, "./id {id}")?;
            writeln!(self.writer)?; // Blank line after ./id
            self.document_id_handled = true;
        }
        Ok(())
    }

    /// Get dots string for a given level (cached for common levels)
    fn dots_for_level(level: usize) -> &'static str {
        // Pre-computed dots for common levels (1-10)
        const DOTS: [&str; 11] = [
            "",           // 0
            ".",          // 1
            "..",         // 2
            "...",        // 3
            "....",       // 4
            ".....",      // 5
            "......",     // 6
            ".......",    // 7
            "........",   // 8
            ".........",  // 9
            "..........", // 10
        ];

        if level < DOTS.len() {
            DOTS[level]
        } else {
            // Fallback for deep nesting (rare) - this leaks but is acceptable
            // since deep nesting is uncommon and the strings are small
            Box::leak(".".repeat(level).into_boxed_str())
        }
    }

    /// Generate the meta-line string from a MetaLine
    fn format_meta_line(&self, meta_line: &MetaLine) -> String {
        // Pre-allocate capacity to reduce allocations
        let mut line = String::with_capacity(128);

        // Add dots for level (use cached string)
        line.push_str(Self::dots_for_level(meta_line.level));

        // Add type name (quote if it contains spaces, quotes, or tabs, or is empty)
        if meta_line.type_name.contains(' ')
            || meta_line.type_name.contains('"')
            || meta_line.type_name.contains('\t')
            || meta_line.type_name.is_empty()
        {
            line.push('"');
            line.push_str(&Self::escape_quotes(&meta_line.type_name));
            line.push('"');
        } else {
            line.push_str(&meta_line.type_name);
        }

        // Add inline text if present (strip whitespace)
        let inline_content = meta_line.inline_text.content.trim();
        let has_args =
            !meta_line.arguments.args.is_empty() || !meta_line.arguments.kwargs.is_empty();

        if !inline_content.is_empty() {
            line.push(' ');
            if meta_line.inline_text.quoted {
                line.push('"');
                line.push_str(&Self::escape_quotes(inline_content));
                line.push('"');
            } else {
                line.push_str(inline_content);
            }
            // Add ".." separator if there are also arguments
            if has_args {
                line.push_str(" ..");
            }
        } else if has_args {
            // No inline text, but we have arguments - need ".." to separate them from type name
            line.push_str(" ..");
        }

        // Add arguments
        for arg in &meta_line.arguments.args {
            line.push(' ');
            if arg.quoted || Self::needs_quoting(&arg.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&arg.content));
                line.push('"');
            } else {
                line.push_str(&arg.content);
            }
        }

        // Add keyword arguments
        for (key, value) in &meta_line.arguments.kwargs {
            line.push(' ');
            if key.quoted || Self::needs_quoting(&key.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&key.content));
                line.push('"');
            } else {
                line.push_str(&key.content);
            }
            line.push('=');
            if value.quoted || Self::needs_quoting(&value.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&value.content));
                line.push('"');
            } else {
                line.push_str(&value.content);
            }
        }

        // Add ID if present
        if !meta_line.id.is_empty() {
            line.push_str(" :");
            line.push_str(meta_line.id.as_str());
        }

        line
    }

    /// Generate the meta-line string from a MetaLine for void elements
    /// (adds trailing : after type name)
    fn format_void_meta_line(&self, meta_line: &MetaLine) -> String {
        // Pre-allocate capacity to reduce allocations
        let mut line = String::with_capacity(128);

        // Add dots for level (use cached string)
        line.push_str(Self::dots_for_level(meta_line.level));

        // Add type name (quote if it contains spaces, quotes, or tabs, or is empty)
        if meta_line.type_name.contains(' ')
            || meta_line.type_name.contains('"')
            || meta_line.type_name.contains('\t')
            || meta_line.type_name.is_empty()
        {
            line.push('"');
            line.push_str(&Self::escape_quotes(&meta_line.type_name));
            line.push('"');
        } else {
            line.push_str(&meta_line.type_name);
        }

        // Add the void marker colon
        line.push(':');

        // Add inline text if present (strip whitespace)
        let inline_content = meta_line.inline_text.content.trim();
        let has_args =
            !meta_line.arguments.args.is_empty() || !meta_line.arguments.kwargs.is_empty();

        if !inline_content.is_empty() {
            line.push(' ');
            if meta_line.inline_text.quoted {
                line.push('"');
                line.push_str(&Self::escape_quotes(inline_content));
                line.push('"');
            } else {
                line.push_str(inline_content);
            }
            // Add ".." separator if there are also arguments
            if has_args {
                line.push_str(" ..");
            }
        } else if has_args {
            // No inline text, but we have arguments - need ".." to separate them from type name
            line.push_str(" ..");
        }

        // Add arguments
        for arg in &meta_line.arguments.args {
            line.push(' ');
            if arg.quoted || Self::needs_quoting(&arg.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&arg.content));
                line.push('"');
            } else {
                line.push_str(&arg.content);
            }
        }

        // Add keyword arguments
        for (key, value) in &meta_line.arguments.kwargs {
            line.push(' ');
            if key.quoted || Self::needs_quoting(&key.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&key.content));
                line.push('"');
            } else {
                line.push_str(&key.content);
            }
            line.push('=');
            if value.quoted || Self::needs_quoting(&value.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&value.content));
                line.push('"');
            } else {
                line.push_str(&value.content);
            }
        }

        // Add ID if present
        if !meta_line.id.is_empty() {
            line.push_str(" :");
            line.push_str(meta_line.id.as_str());
        }

        line
    }

    /// Generate the context line string from a MetaLine (for ./type syntax)
    fn format_context_line(&self, meta_line: &MetaLine) -> String {
        // Pre-allocate capacity to reduce allocations
        let mut line = String::with_capacity(128);

        // Add dots for level (one less than the level, then ./)
        line.push_str(Self::dots_for_level(meta_line.level.saturating_sub(1)));
        line.push_str("./");

        // Add type name (quote if it contains spaces, quotes, or tabs, or is empty)
        if meta_line.type_name.contains(' ')
            || meta_line.type_name.contains('"')
            || meta_line.type_name.contains('\t')
            || meta_line.type_name.is_empty()
        {
            line.push('"');
            line.push_str(&Self::escape_quotes(&meta_line.type_name));
            line.push('"');
        } else {
            line.push_str(&meta_line.type_name);
        }

        // Add inline text if present
        let has_args =
            !meta_line.arguments.args.is_empty() || !meta_line.arguments.kwargs.is_empty();

        if !meta_line.inline_text.content.is_empty() {
            line.push(' ');
            if meta_line.inline_text.quoted {
                line.push('"');
                line.push_str(&Self::escape_quotes(&meta_line.inline_text.content));
                line.push('"');
            } else {
                line.push_str(&meta_line.inline_text.content);
            }
            // Add ".." separator if there are also arguments
            if has_args {
                line.push_str(" ..");
            }
        } else if has_args {
            // No inline text, but we have arguments - need ".." to separate them from type name
            line.push_str(" ..");
        }

        // Add arguments
        for arg in &meta_line.arguments.args {
            line.push(' ');
            if arg.quoted || Self::needs_quoting(&arg.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&arg.content));
                line.push('"');
            } else {
                line.push_str(&arg.content);
            }
        }

        // Add keyword arguments
        for (key, value) in &meta_line.arguments.kwargs {
            line.push(' ');
            if key.quoted || Self::needs_quoting(&key.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&key.content));
                line.push('"');
            } else {
                line.push_str(&key.content);
            }
            line.push('=');
            if value.quoted || Self::needs_quoting(&value.content) {
                line.push('"');
                line.push_str(&Self::escape_quotes(&value.content));
                line.push('"');
            } else {
                line.push_str(&value.content);
            }
        }

        // Add ID if present
        if !meta_line.id.is_empty() {
            line.push_str(" :");
            line.push_str(meta_line.id.as_str());
        }

        line
    }

    /// Close blocks that are at higher levels than the current level
    fn close_blocks_to_level(&mut self, target_level: usize) -> io::Result<()> {
        while let Some(&last_level) = self.block_stack.last() {
            if last_level < target_level {
                break;
            }

            // Close the block
            // let dots = ".".repeat(last_level);
            // writeln!(self.writer, "{dots}/")?;
            self.just_closed = true;

            // closing explicitly is only required if the next block is a text

            self.block_stack.pop();
        }
        Ok(())
    }
}

impl<W: Write> WasotReconstructor<W> {
    /// Process a single event (public method for compatibility)
    pub fn on_element_event(&mut self, event: &Event) {
        self.process_event(event);
    }

    fn process_event(&mut self, event: &Event) {
        match event {
            Event::BlockStarted { meta_line } => {
                self.just_closed = false;

                // Ensure document ID is written before first block
                if let Err(e) = self.ensure_document_id() {
                    eprintln!("Error writing document ID: {e}");
                    return;
                }

                // Close any blocks that need to be closed before this level
                if let Err(e) = self.close_blocks_to_level(meta_line.level) {
                    eprintln!("Error closing blocks: {e}");
                    return;
                }

                // Write the meta-line
                let line = self.format_meta_line(meta_line);
                if let Err(e) = writeln!(self.writer, "{line}") {
                    eprintln!("Error writing meta-line: {e}");
                    return;
                }

                // Add this block to the stack
                self.block_stack.push(meta_line.level);
            }

            Event::TextFinished {
                lines, footnotes, ..
            } => {
                self.just_closed = false;

                // Ensure document ID is written before first text
                if let Err(e) = self.ensure_document_id() {
                    eprintln!("Error writing document ID: {e}");
                    return;
                }

                // Write each line of text content
                for line in lines {
                    if let Err(e) = writeln!(self.writer, "{line}") {
                        eprintln!("Error writing text line: {e}");
                        return;
                    }
                }

                // Write footnotes if any exist AND they're not already in the text lines
                // (footnotes may already be present in the text if they came from the original file)
                if !footnotes.is_empty() {
                    // Check if footnotes are already present in text lines
                    // Avoid format! allocation by checking components separately
                    let footnotes_in_text = footnotes.iter().all(|f| {
                        lines.iter().any(|line| {
                            let trimmed = line.trim();
                            // Check if line starts with *{marker} and contains the link
                            if let Some(rest) = trimmed.strip_prefix('*') {
                                // Parse the marker number
                                let marker_str = f.marker.to_string();
                                if let Some(after_marker) = rest.strip_prefix(&marker_str) {
                                    // Check if there's a space and then the link
                                    after_marker.trim_start().starts_with(&f.link)
                                } else {
                                    false
                                }
                            } else {
                                false
                            }
                        })
                    });

                    // Only write footnotes if they're not already in the text
                    if !footnotes_in_text {
                        // Add blank line before footnotes
                        if let Err(e) = writeln!(self.writer) {
                            eprintln!("Error writing blank line before footnotes: {e}");
                            return;
                        }

                        // Write each footnote
                        for footnote in footnotes {
                            if let Err(e) =
                                writeln!(self.writer, "*{} {}", footnote.marker, footnote.link)
                            {
                                eprintln!("Error writing footnote: {e}");
                                return;
                            }
                        }

                        // Add blank line after footnotes to separate from next block
                        if let Err(e) = writeln!(self.writer) {
                            eprintln!("Error writing blank line after footnotes: {e}");
                        }
                    }
                }
            }

            Event::BlockFinished => {
                // The block will be closed by the next BlockStarted or at the end
                // We don't explicitly close here because the closing is handled
                // by level changes or the final cleanup
            }

            Event::ContextLine { meta_line } => {
                self.just_closed = false;

                // Handle /id context line specially
                if meta_line.type_name == "id" {
                    // If the /id line has no value, fill in a generated ID
                    if meta_line.inline_text.content.is_empty() {
                        let id = self.get_or_generate_id();
                        let mut modified_meta = meta_line.clone();
                        modified_meta.inline_text = QuotedString::new(id, false);
                        let line = self.format_context_line(&modified_meta);
                        if let Err(e) = writeln!(self.writer, "{line}") {
                            eprintln!("Error writing context line: {e}");
                        }
                    } else {
                        // /id line has a value, output as-is
                        let line = self.format_context_line(meta_line);
                        if let Err(e) = writeln!(self.writer, "{line}") {
                            eprintln!("Error writing context line: {e}");
                        }
                    }
                    // Mark document ID as handled
                    self.document_id_handled = true;
                } else {
                    // Non-id context lines - ensure document ID first
                    if let Err(e) = self.ensure_document_id() {
                        eprintln!("Error writing document ID: {e}");
                        return;
                    }
                    // Write context line with ./ prefix
                    let line = self.format_context_line(meta_line);
                    if let Err(e) = writeln!(self.writer, "{line}") {
                        eprintln!("Error writing context line: {e}");
                    }
                }
            }

            Event::VoidElement { meta_line } => {
                self.just_closed = false;

                // Ensure document ID is written before first void element
                if let Err(e) = self.ensure_document_id() {
                    eprintln!("Error writing document ID: {e}");
                    return;
                }

                // Close any blocks that need to be closed before this level
                if let Err(e) = self.close_blocks_to_level(meta_line.level) {
                    eprintln!("Error closing blocks: {e}");
                    return;
                }

                // Write the void element meta-line (with trailing :)
                let line = self.format_void_meta_line(meta_line);
                if let Err(e) = writeln!(self.writer, "{line}") {
                    eprintln!("Error writing void element: {e}");
                }
                // Void elements don't push to block stack - they are self-closing
            }
        }
    }
}

impl<W: Write> Drop for WasotReconstructor<W> {
    fn drop(&mut self) {
        // Close all remaining blocks when the reconstructor is dropped
        let _ = self.close_blocks_to_level(0);
        let _ = self.writer.flush();
    }
}

impl<W: Write + Send + Sync> Consumer for WasotReconstructor<W> {
    fn handle(&mut self, event: Event) {
        self.process_event(&event);
    }
}

impl Default for WasotReconstructor<io::Stdout> {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parsers::wasot::{footnote_parser::Footnote, Id, ParsedArguments, QuotedString};

    #[test]
    fn test_new() {
        let reconstructor = WasotReconstructor::new();
        assert!(reconstructor.block_stack.is_empty());
        assert!(!reconstructor.just_closed);
        assert!(!reconstructor.document_id_handled);
        assert!(reconstructor.generated_id.is_none());
    }

    #[test]
    fn test_new_with_writer() {
        let writer = Vec::new();
        let reconstructor = WasotReconstructor::new_with_writer(writer);
        assert!(reconstructor.block_stack.is_empty());
        assert!(!reconstructor.just_closed);
        assert!(!reconstructor.document_id_handled);
        assert!(reconstructor.generated_id.is_none());
    }

    #[test]
    fn test_format_meta_line_simple() {
        let reconstructor = WasotReconstructor::new();

        // Simple type-only meta-line
        let meta_line = MetaLine {
            type_name: "paragraph".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(reconstructor.format_meta_line(&meta_line), ".paragraph");
    }

    #[test]
    fn test_format_meta_line_with_spaces_in_type_name() {
        let reconstructor = WasotReconstructor::new();

        let meta_line = MetaLine {
            type_name: "custom type".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            ".\"custom type\""
        );
    }

    #[test]
    fn test_format_meta_line_with_inline_text() {
        let reconstructor = WasotReconstructor::new();

        // Meta-line with unquoted inline text (no args, so no "..")
        let meta_line = MetaLine {
            type_name: "heading".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("Hello World".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            ".heading Hello World"
        );

        // Meta-line with quoted inline text (no args, so no "..")
        let meta_line = MetaLine {
            type_name: "heading".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("Hello World".to_string(), true),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            ".heading \"Hello World\""
        );
    }

    #[test]
    fn test_format_meta_line_with_arguments() {
        let reconstructor = WasotReconstructor::new();

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 2,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![
                    QuotedString::new("arg1".to_string(), false),
                    QuotedString::new("arg with spaces".to_string(), true),
                ],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            "..block .. arg1 \"arg with spaces\""
        );
    }

    #[test]
    fn test_format_meta_line_with_kwargs() {
        let reconstructor = WasotReconstructor::new();

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![
                    (
                        QuotedString::new("key".to_string(), false),
                        QuotedString::new("value".to_string(), false),
                    ),
                    (
                        QuotedString::new("quoted key".to_string(), true),
                        QuotedString::new("quoted value".to_string(), true),
                    ),
                ],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            ".block .. key=value \"quoted key\"=\"quoted value\""
        );
    }

    #[test]
    fn test_format_meta_line_with_id() {
        let reconstructor = WasotReconstructor::new();

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::from("test123"),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            ".block :test123"
        );
    }

    #[test]
    fn test_format_meta_line_complete() {
        let reconstructor = WasotReconstructor::new();

        // Meta-line with all features
        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 2,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("inline content".to_string(), false),
            id: Id::from("test"),
            arguments: ParsedArguments {
                args: vec![
                    QuotedString::new("arg1".to_string(), false),
                    QuotedString::new("arg with spaces".to_string(), true),
                ],
                kwargs: vec![(
                    QuotedString::new("key".to_string(), false),
                    QuotedString::new("value".to_string(), false),
                )],
            },
        };

        assert_eq!(
            reconstructor.format_meta_line(&meta_line),
            "..block inline content .. arg1 \"arg with spaces\" key=value :test"
        );
    }

    #[test]
    fn test_close_blocks_to_level() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Add some blocks to the stack
        reconstructor.block_stack.push(1);
        reconstructor.block_stack.push(2);
        reconstructor.block_stack.push(3);

        // Close to level 2 (should close level 3 block)
        reconstructor.close_blocks_to_level(3).unwrap();
        assert_eq!(reconstructor.block_stack.len(), 2);
        assert_eq!(reconstructor.block_stack, vec![1, 2]);
        assert!(reconstructor.just_closed);

        // Close all blocks
        reconstructor.close_blocks_to_level(0).unwrap();
        assert!(reconstructor.block_stack.is_empty());
        assert!(reconstructor.just_closed);
    }

    #[test]
    fn test_process_block_started_event() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

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

        let event = Event::BlockStarted {
            meta_line: meta_line.clone(),
        };

        reconstructor.process_event(&event);

        assert_eq!(reconstructor.block_stack, vec![1]);
        assert!(!reconstructor.just_closed);
    }

    #[test]
    fn test_process_text_finished_event() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        let event = Event::TextFinished {
            lines: vec!["Line 1".to_string(), "Line 2".to_string()],
            links: vec![vec![], vec![]], // Empty links for each line
            footnotes: vec![],
        };

        reconstructor.process_event(&event);
        assert!(!reconstructor.just_closed);
    }

    #[test]
    fn test_process_text_finished_event_with_footnotes() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        let footnotes = vec![
            Footnote {
                marker: 1,
                link: "https://example.com".to_string(),
            },
            Footnote {
                marker: 2,
                link: "https://example.org".to_string(),
            },
        ];

        let event = Event::TextFinished {
            lines: vec!["Line with footnote".to_string()],
            links: vec![vec![]], // Empty links for the line
            footnotes,
        };

        reconstructor.process_event(&event);
        assert!(!reconstructor.just_closed);
    }

    #[test]
    fn test_process_block_finished_event() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        let event = Event::BlockFinished;
        reconstructor.process_event(&event);

        // BlockFinished event doesn't change state directly
        assert!(!reconstructor.just_closed);
    }

    #[test]
    fn test_on_element_event() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

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

        let event = Event::BlockStarted {
            meta_line: meta_line.clone(),
        };

        reconstructor.on_element_event(&event);
        assert_eq!(reconstructor.block_stack, vec![1]);
    }

    #[test]
    fn test_consumer_trait() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

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

        let event = Event::BlockStarted {
            meta_line: meta_line.clone(),
        };

        reconstructor.handle(event);
        assert_eq!(reconstructor.block_stack, vec![1]);
    }

    #[test]
    fn test_default() {
        let reconstructor = WasotReconstructor::default();
        assert!(reconstructor.block_stack.is_empty());
        assert!(!reconstructor.just_closed);
    }

    #[test]
    fn test_drop() {
        let writer = Vec::new();
        {
            let mut reconstructor = WasotReconstructor::new_with_writer(writer);
            reconstructor.block_stack.push(1);
            reconstructor.block_stack.push(2);
        } // Drop happens here

        // The drop implementation should have called close_blocks_to_level(0)
        // and flushed the writer, but we can't directly test this without more complex setup
    }

    #[test]
    fn test_format_context_line_simple() {
        let reconstructor = WasotReconstructor::new();

        // Simple context line: ./id
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(reconstructor.format_context_line(&meta_line), "./id");
    }

    #[test]
    fn test_format_context_line_with_inline_text() {
        let reconstructor = WasotReconstructor::new();

        // Context line with inline text: ./id 123
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("123".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(reconstructor.format_context_line(&meta_line), "./id 123");
    }

    #[test]
    fn test_format_context_line_with_quoted_inline_text() {
        let reconstructor = WasotReconstructor::new();

        // Context line with quoted inline text: ./id "some text"
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("some text".to_string(), true),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_context_line(&meta_line),
            "./id \"some text\""
        );
    }

    #[test]
    fn test_format_context_line_nested() {
        let reconstructor = WasotReconstructor::new();

        // Nested context line: ../id 456
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 2,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("456".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(reconstructor.format_context_line(&meta_line), "../id 456");
    }

    #[test]
    fn test_format_context_line_with_arguments() {
        let reconstructor = WasotReconstructor::new();

        // Context line with inline text and arguments: ./tag text .. arg1 key=value
        let meta_line = MetaLine {
            type_name: "tag".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("text".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![QuotedString::new("arg1".to_string(), false)],
                kwargs: vec![(
                    QuotedString::new("key".to_string(), false),
                    QuotedString::new("value".to_string(), false),
                )],
            },
        };

        assert_eq!(
            reconstructor.format_context_line(&meta_line),
            "./tag text .. arg1 key=value"
        );
    }

    #[test]
    fn test_format_context_line_with_id() {
        let reconstructor = WasotReconstructor::new();

        // Context line with ID: ./id 123 :myid
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("123".to_string(), false),
            id: Id::from("myid"),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        assert_eq!(
            reconstructor.format_context_line(&meta_line),
            "./id 123 :myid"
        );
    }

    #[test]
    fn test_process_context_line_event() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Context line: ./id 123
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("123".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::ContextLine { meta_line };
        reconstructor.process_event(&event);

        // Check that the output is correct
        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        assert_eq!(output, "./id 123\n");
    }

    // === Document ID Generation Tests ===

    #[test]
    fn test_generate_document_id_format() {
        // Generate several IDs and verify they're valid base36
        for _ in 0..10 {
            let id = WasotReconstructor::<Vec<u8>>::generate_document_id();
            assert!(!id.is_empty());
            // Should be valid base36 (only lowercase alphanumeric)
            assert!(id
                .chars()
                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()));
            // UUID v4 in base36 should be around 25 chars max
            assert!(id.len() <= 25);
        }
    }

    #[test]
    fn test_generate_document_id_uniqueness() {
        // Generate several IDs and verify they're unique
        let ids: Vec<String> = (0..100)
            .map(|_| WasotReconstructor::<Vec<u8>>::generate_document_id())
            .collect();

        // Check all IDs are unique
        let mut sorted = ids.clone();
        sorted.sort();
        sorted.dedup();
        assert_eq!(sorted.len(), ids.len(), "Generated IDs should be unique");
    }

    #[test]
    fn test_get_or_generate_id_caches() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // First call generates ID
        let id1 = reconstructor.get_or_generate_id();
        assert!(!id1.is_empty());
        assert!(reconstructor.generated_id.is_some());

        // Second call returns same ID
        let id2 = reconstructor.get_or_generate_id();
        assert_eq!(id1, id2);
    }

    #[test]
    fn test_auto_generate_id_on_first_block() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Process a block without /id line first
        let meta_line = MetaLine {
            type_name: "paragraph".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::BlockStarted { meta_line };
        reconstructor.process_event(&event);

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should have auto-generated ./id line before the block with blank line after
        assert!(
            output.starts_with("./id "),
            "Output should start with './id ': {output}"
        );
        // Check for blank line after ./id
        let lines: Vec<&str> = output.lines().collect();
        assert!(lines.len() >= 2, "Should have at least 2 lines: {output}");
        assert!(lines[1].is_empty(), "Second line should be blank: {output}");
        assert!(
            output.contains(".paragraph"),
            "Output should contain the block"
        );
        assert!(reconstructor.document_id_handled);
    }

    #[test]
    fn test_auto_generate_id_on_first_text() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Process text without /id line first
        let event = Event::TextFinished {
            lines: vec!["Hello world".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };
        reconstructor.process_event(&event);

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should have auto-generated ./id line before the text with blank line after
        assert!(
            output.starts_with("./id "),
            "Output should start with './id ': {output}"
        );
        // Check for blank line after ./id
        let lines: Vec<&str> = output.lines().collect();
        assert!(lines.len() >= 2, "Should have at least 2 lines: {output}");
        assert!(lines[1].is_empty(), "Second line should be blank: {output}");
        assert!(
            output.contains("Hello world"),
            "Output should contain the text"
        );
        assert!(reconstructor.document_id_handled);
    }

    #[test]
    fn test_fill_empty_id_line() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Process /id line without value
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false), // Empty!
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::ContextLine { meta_line };
        reconstructor.process_event(&event);

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should have filled in the ID
        assert!(
            output.starts_with("./id "),
            "Output should start with './id ': {output}"
        );
        // Should have more than just "./id \n"
        let id_value = output.trim_start_matches("./id ").trim();
        assert!(!id_value.is_empty(), "ID value should be filled in");
        assert!(reconstructor.document_id_handled);
    }

    #[test]
    fn test_preserve_existing_id_line() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Process /id line with existing value
        let meta_line = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("my-custom-id".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::ContextLine { meta_line };
        reconstructor.process_event(&event);

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should preserve the existing ID
        assert_eq!(output, "./id my-custom-id\n");
        assert!(reconstructor.document_id_handled);
    }

    #[test]
    fn test_no_duplicate_id_after_explicit_id() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // First, explicit /id line
        let id_meta = MetaLine {
            type_name: "id".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("doc123".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        reconstructor.process_event(&Event::ContextLine { meta_line: id_meta });

        // Then a block
        let block_meta = MetaLine {
            type_name: "paragraph".to_string(),
            level: 1,
            line_index: Some(1),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        reconstructor.process_event(&Event::BlockStarted {
            meta_line: block_meta,
        });

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should only have one ./id line
        let id_count = output.matches("./id").count();
        assert_eq!(id_count, 1, "Should have exactly one ./id line: {output}");
    }

    #[test]
    fn test_non_id_context_line_triggers_auto_id() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Process a non-id context line without /id first
        let meta_line = MetaLine {
            type_name: "author".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("John Doe".to_string(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::ContextLine { meta_line };
        reconstructor.process_event(&event);

        let output = String::from_utf8(reconstructor.writer.clone()).unwrap();
        // Should have auto-generated ./id line before the ./author line with blank line after
        assert!(
            output.starts_with("./id "),
            "Output should start with './id ': {output}"
        );
        // Check for blank line after ./id
        let lines: Vec<&str> = output.lines().collect();
        assert!(lines.len() >= 2, "Should have at least 2 lines: {output}");
        assert!(lines[1].is_empty(), "Second line should be blank: {output}");
        assert!(
            output.contains("./author John Doe"),
            "Output should contain the author line"
        );
    }

    #[test]
    fn test_id_roundtrip_is_valid_base36() {
        let writer = Vec::new();
        let mut reconstructor = WasotReconstructor::new_with_writer(writer);

        // Generate an ID
        let id = reconstructor.get_or_generate_id();

        // Verify it can be decoded as base36 u128
        let result = crate::base36::base36_to_u128(&id);
        assert!(result.is_ok(), "Generated ID should be valid base36: {id}");

        // Verify roundtrip
        let decoded = result.unwrap();
        let re_encoded = crate::base36::u128_to_base36(decoded);
        assert_eq!(id, re_encoded, "Base36 roundtrip should match");
    }
}
