#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct QuotedString {
    pub content: String,
    pub quoted: bool,
    pub links: Vec<Link>,
}

impl QuotedString {
    /// Creates a new QuotedString with empty links
    pub fn new(content: String, quoted: bool) -> Self {
        Self {
            content,
            quoted,
            links: Vec::new(),
        }
    }

    /// Creates a new QuotedString with parsed links
    pub fn with_links(content: String, quoted: bool) -> Self {
        let mut qs = Self::new(content, quoted);
        qs.parse_links();
        qs
    }

    /// Parses links in the content and populates the links field
    pub fn parse_links(&mut self) {
        use super::link_parser::{detect_links, parse_link};

        let spans = detect_links(&self.content);
        self.links = spans
            .into_iter()
            .map(|span| parse_link(&span.content, span.start_index, span.length))
            .collect();
    }
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Id {
    pub value: String,
}

impl Id {
    pub fn new(value: String) -> Self {
        Id { value }
    }

    pub fn empty() -> Self {
        Id {
            value: String::new(),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.value.is_empty()
    }

    pub fn as_str(&self) -> &str {
        &self.value
    }

    /// Create an ID from a u64 value using base36 encoding
    pub fn from_u64(value: u64) -> Self {
        Id {
            value: crate::base36::u64_to_base36(value),
        }
    }

    /// Try to parse the ID as a base36-encoded u64
    /// Returns None if the ID is not a valid base36 u64
    pub fn as_u64(&self) -> Option<u64> {
        if self.value.is_empty() {
            return None;
        }
        crate::base36::base36_to_u64(&self.value).ok()
    }

    /// Check if this ID is a valid base36-encoded u64
    pub fn is_numeric(&self) -> bool {
        self.as_u64().is_some()
    }
}

impl From<String> for Id {
    fn from(value: String) -> Self {
        Id::new(value)
    }
}

impl From<&str> for Id {
    fn from(value: &str) -> Self {
        Id::new(value.to_string())
    }
}

impl From<u64> for Id {
    fn from(value: u64) -> Self {
        Id::from_u64(value)
    }
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct MetaLine {
    pub type_name: String,
    pub inline_text: QuotedString,
    pub level: usize,
    pub id: Id,
    pub line_index: Option<usize>,
    pub arguments: super::arguments::ParsedArguments,
    /// If true, this is a void element (self-closing, no children)
    /// Syntax: `.hr:` (trailing colon marks void element)
    #[serde(default)]
    pub is_void: bool,
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ExternalIdReference {
    pub document_id: Id,
    pub local_id: Id,
    pub document_path: Option<String>,
}

impl ExternalIdReference {
    /// Parse a document ID reference from a string like "document-id.local-id"
    /// Automatically resolves the document path from the global index if available.
    pub fn parse(s: &str) -> Option<Self> {
        use super::document_id_index::lookup_document_path;

        let (doc, local) = s.split_once('.')?;
        if doc.is_empty() || local.is_empty() {
            return None;
        }

        // Try to resolve the document path from the global index
        let document_path = lookup_document_path(doc).map(String::from);

        Some(Self {
            document_id: Id::new(doc.to_string()),
            local_id: Id::new(local.to_string()),
            document_path,
        })
    }

    /// Create a new ExternalIdReference with explicit values
    pub fn new(document_id: Id, local_id: Id) -> Self {
        Self {
            document_id,
            local_id,
            document_path: None,
        }
    }

    /// Create a new ExternalIdReference with a document path
    pub fn with_path(document_id: Id, local_id: Id, document_path: String) -> Self {
        Self {
            document_id,
            local_id,
            document_path: Some(document_path),
        }
    }

    /// Convert back to the qualified string format "document-id.local-id"
    pub fn to_qualified_string(&self) -> String {
        format!("{}.{}", self.document_id.value, self.local_id.value)
    }
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TargetType {
    Uri,
    Id,
    FootNote,
    IdInDocument(ExternalIdReference),
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Link {
    pub name: Option<QuotedString>,
    pub target: Option<String>,
    pub target_type: TargetType,
    pub arguments: super::arguments::ParsedArguments,
    pub start_index: usize,
    pub length: usize,
}

/// Extracts ID from a string matching the pattern: whitespace + colon + lowercase letters/numbers/dots/dashes
/// Returns (id, `cleaned_string`)
fn extract_id_string(input: &str) -> (String, String) {
    use regex::Regex;
    use std::sync::OnceLock;

    // Cache the compiled regex - this is critical for performance!
    // Without caching, regex compilation happens on every call (~300KB allocations each time)
    static ID_REGEX: OnceLock<Regex> = OnceLock::new();

    debug_assert!(
        !input.is_empty(),
        "extract_id_string called with empty input"
    );

    // Pattern matches: whitespace + colon + letters (including Unicode)/numbers/dashes/underscores/colons
    // Updated to support Unicode characters, uppercase letters, and starting with digits
    // Note: dots are NOT allowed in IDs (reserved for document.local-id references)
    let regex = ID_REGEX.get_or_init(|| Regex::new(r"\s:[A-Za-z0-9\-_:\p{L}]+\s*$").unwrap());

    if let Some(matched) = regex.find(input) {
        let match_str = matched.as_str();
        debug_assert!(!match_str.is_empty(), "Regex matched empty string");
        debug_assert!(
            match_str.contains(':'),
            "Matched string should contain colon"
        );

        // Extract the ID by removing the leading whitespace and colon
        let id = match_str.trim().trim_start_matches(':').to_string();
        // Empty ID is valid - happens when pattern is just " :"

        // Remove the match from the input string
        let cleaned_string = input.replace(match_str, "").trim_end().to_string();
        debug_assert!(
            cleaned_string.len() <= input.len(),
            "Cleaned string should not be longer than input"
        );

        (id, cleaned_string)
    } else {
        (String::new(), input.to_string())
    }
}

impl MetaLine {
    /// Parse a meta-line into its components
    ///
    /// Meta-lines define blocks in Wasot markup. They consist of:
    /// - One or more dots (defining nesting level)
    /// - Block type name (optional for headings)
    /// - Optional inline text
    /// - Optional parameters
    /// - Optional ID
    ///
    /// # Examples
    /// ```
    /// use wasot_compiler::MetaLine;
    ///
    /// // Simple block
    /// let meta = MetaLine::parse(".p", 0).unwrap();
    /// assert_eq!(meta.type_name, "p");
    /// assert_eq!(meta.level, 1);
    ///
    /// // Block with inline text
    /// let meta = MetaLine::parse(".h1 Welcome", 0).unwrap();
    /// assert_eq!(meta.type_name, "h1");
    /// assert_eq!(meta.inline_text.content, "Welcome");
    ///
    /// // Nested block with parameters
    /// let meta = MetaLine::parse("..div class=container .. id=main", 0).unwrap();
    /// assert_eq!(meta.type_name, "div");
    /// assert_eq!(meta.level, 2);
    ///
    /// // Heading (empty type name)
    /// let meta = MetaLine::parse(".. Main Heading", 0).unwrap();
    /// assert_eq!(meta.type_name, "");
    /// assert_eq!(meta.level, 2);
    /// assert_eq!(meta.inline_text.content, "Main Heading");
    ///
    /// // Not a meta-line
    /// assert!(MetaLine::parse("regular text", 0).is_none());
    /// ```
    #[must_use]
    pub fn parse(line: &str, line_index: usize) -> Option<Self> {
        if line.is_empty() {
            return None; // Allow empty lines, just return None
        }

        if !is_meta_line(line) {
            return None;
        }

        debug_assert!(line.starts_with('.'), "Meta line must start with dot");

        // Extract ID first, before any other processing
        let (id, line_without_id) = extract_id_string(line);
        debug_assert!(
            !line_without_id.is_empty(),
            "Line without ID should not be empty"
        );

        // Count leading dots to determine level
        let level = line_without_id.chars().take_while(|&c| c == '.').count();
        // No limit on nesting level - honey badger don't care!

        // Use extract_line_type to handle quoted types properly
        let (type_name, remaining, is_void) =
            extract_line_type(&format!(".{}", &line_without_id[level..]));
        debug_assert!(
            level <= line_without_id.len(),
            "Level should not exceed line length"
        );

        if remaining.is_empty() {
            debug_assert!(
                type_name.len() <= line.len(),
                "Type name should not be longer than input line"
            );
            return Some(MetaLine {
                type_name,
                inline_text: QuotedString::new(String::new(), false),
                level,
                id: Id::from(id),
                line_index: Some(line_index),
                arguments: super::arguments::ParsedArguments {
                    args: Vec::new(),
                    kwargs: Vec::new(),
                },
                is_void,
            });
        }

        // Split the remaining content on ".." to separate inline text and params
        let parts = split_into_parts(&remaining);
        debug_assert!(
            !parts.is_empty(),
            "split_into_parts should never return empty vector"
        );

        match parts.len() {
            1 => {
                // Only one part - this is inline text (no params)
                // Example: .block some inline text
                let inline_text_content = parts[0].clone();
                let quoted = is_quoted_text(&inline_text_content);
                let content = if quoted {
                    // Strip quotes and process escape sequences
                    super::arguments::process_escapes(&strip_quotes(&inline_text_content))
                } else {
                    inline_text_content
                };
                debug_assert!(
                    type_name.len() <= line.len(),
                    "Type name should not be longer than input line"
                );
                Some(MetaLine {
                    type_name,
                    inline_text: QuotedString::new(content, quoted),
                    level,
                    id: Id::from(id),
                    line_index: Some(line_index),
                    arguments: super::arguments::ParsedArguments {
                        args: Vec::new(),
                        kwargs: Vec::new(),
                    },
                    is_void,
                })
            }
            2 => {
                // Two parts - first is inline text, second is raw_params
                // Example: .block some inline text .. a b=c
                // OR: .block .. a b=c (first part empty = no inline text, only params)
                debug_assert_eq!(
                    parts.len(),
                    2,
                    "Parts length must be exactly 2 in this branch"
                );
                let inline_text_content = parts[0].clone();
                let quoted = is_quoted_text(&inline_text_content);
                let content = if quoted {
                    // Strip quotes and process escape sequences
                    super::arguments::process_escapes(&strip_quotes(&inline_text_content))
                } else {
                    inline_text_content
                };
                let raw_params = parts[1].clone();
                let arguments = if raw_params.is_empty() {
                    super::arguments::ParsedArguments {
                        args: Vec::new(),
                        kwargs: Vec::new(),
                    }
                } else {
                    super::arguments::parse_arguments(&raw_params)
                };
                debug_assert!(
                    type_name.len() <= line.len(),
                    "Type name should not be longer than input line"
                );
                Some(MetaLine {
                    type_name,
                    inline_text: QuotedString::new(content, quoted),
                    level,
                    id: Id::from(id),
                    line_index: Some(line_index),
                    arguments,
                    is_void,
                })
            }
            _ => {
                // More than 2 parts - first is inline text, rest combined as raw_params
                // Example: .block inline text .. param1 .. param2
                debug_assert!(
                    parts.len() > 2,
                    "Parts length must be greater than 2 in this branch"
                );
                let inline_text_content = parts[0].clone();
                let quoted = is_quoted_text(&inline_text_content);
                let content = if quoted {
                    // Strip quotes and process escape sequences
                    super::arguments::process_escapes(&strip_quotes(&inline_text_content))
                } else {
                    inline_text_content
                };
                let raw_params = parts[1..].join("..");
                debug_assert!(
                    parts.len() >= 2,
                    "Must have at least 2 parts to slice from index 1"
                );
                let arguments = if raw_params.is_empty() {
                    super::arguments::ParsedArguments {
                        args: Vec::new(),
                        kwargs: Vec::new(),
                    }
                } else {
                    super::arguments::parse_arguments(&raw_params)
                };
                debug_assert!(
                    type_name.len() <= line.len(),
                    "Type name should not be longer than input line"
                );
                Some(MetaLine {
                    type_name,
                    inline_text: QuotedString::new(content, quoted),
                    level,
                    id: Id::from(id),
                    line_index: Some(line_index),
                    arguments,
                    is_void,
                })
            }
        }
    }
}

/// Check if a line is a meta-line (starts with a dot)
///
/// Meta-lines define blocks and their structure in Wasot markup.
/// They start with one or more dots followed by a block type.
///
/// # Examples
/// ```
/// use wasot_compiler::is_meta_line;
///
/// assert!(is_meta_line(".p"));
/// assert!(is_meta_line("..h1"));
/// assert!(is_meta_line("...nested"));
/// assert!(!is_meta_line("regular text"));
/// assert!(!is_meta_line(""));
/// ```
#[must_use]
pub fn is_meta_line(line: &str) -> bool {
    !line.is_empty() && line.starts_with('.')
}

/// Checks if a text string is quoted (starts and ends with quotes)
fn is_quoted_text(text: &str) -> bool {
    let trimmed = text.trim();
    trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"')
}

/// Strips surrounding quotes from text if present
fn strip_quotes(text: &str) -> String {
    let trimmed = text.trim();
    if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
        trimmed[1..trimmed.len() - 1].to_string()
    } else {
        text.to_string()
    }
}

/// Splits text on ".." except when those dots appear inside double quotes
///
/// This function is used to separate inline text from parameters in meta-lines.
/// It properly handles quoted strings and escape sequences.
///
/// # Examples
/// ```
/// use wasot_compiler::split_into_parts;
///
/// // Simple split on ".."
/// let parts = split_into_parts("text .. param=value");
/// assert_eq!(parts, vec!["text ", " param=value"]);
///
/// // Quotes protect ".." from splitting
/// let parts = split_into_parts("\"text .. with dots\" .. param");
/// assert_eq!(parts.len(), 2);
/// assert!(parts[0].contains(".."));
///
/// // Empty parts are preserved
/// let parts = split_into_parts(".. param");
/// assert_eq!(parts, vec!["", " param"]);
/// ```
#[must_use]
pub fn split_into_parts(text: &str) -> Vec<String> {
    let mut result = Vec::new();
    let mut current = String::new();
    let mut in_quotes = false;
    let chars: Vec<char> = text.chars().collect();
    let n = chars.len();

    let mut i = 0;
    while i < n {
        debug_assert!(i < chars.len(), "Index should be within bounds");
        let c = chars[i];

        // Handle escape sequences inside quotes
        if in_quotes && c == '\\' && i + 1 < n {
            debug_assert!(
                i + 1 < chars.len(),
                "Next character index should be within bounds"
            );
            let next_char = chars[i + 1];
            if next_char == '"' || next_char == '\\' {
                // Escaped quote or backslash - add the literal character
                current.push(c);
                current.push(next_char);
                i += 2;
                debug_assert!(
                    i <= n,
                    "Index should not exceed string length after increment"
                );
                continue;
            }
        }

        // Toggle in_quotes when we encounter an unescaped quote character
        if c == '"' {
            in_quotes = !in_quotes;
            current.push(c);
            i += 1;
            continue;
        }

        // If we're not inside quotes, check for ".."
        if !in_quotes && i + 1 < n && c == '.' && chars[i + 1] == '.' {
            debug_assert!(
                i + 1 < chars.len(),
                "Next character index should be within bounds for .. check"
            );
            // We found a ".." delimiter
            result.push(current);
            current = String::new();
            i += 2; // skip both dots
            debug_assert!(
                i <= n,
                "Index should not exceed string length after skipping dots"
            );
            continue;
        }

        // Otherwise, just collect the character
        current.push(c);
        i += 1;
        debug_assert!(
            i <= n,
            "Index should not exceed string length after increment"
        );
    }

    // Append the final piece
    result.push(current);
    debug_assert!(
        !result.is_empty(),
        "Result should never be empty - at minimum contains one part"
    );

    result
}

/// Splits text on ".." and trims whitespace from each part.
#[must_use]
pub fn split_into_parts_and_trim(text: &str) -> Vec<String> {
    split_into_parts(text)
        .into_iter()
        .map(|s| s.trim().to_string())
        .collect()
}

/// Extracts the line type from a line starting with ".".
/// Handles quoted block types like "a block type" with escape sequences.
/// Also detects void elements marked with trailing `:` (e.g., `.hr:`).
/// Returns (type, `remaining_text`, `is_void`).
#[must_use]
pub fn extract_line_type(text: &str) -> (String, String, bool) {
    debug_assert!(!text.is_empty(), "extract_line_type called with empty text");

    if let Some(stripped) = text.strip_prefix('.') {
        debug_assert!(
            text.starts_with('.'),
            "Text should start with dot if strip_prefix succeeded"
        );
        if stripped.is_empty() {
            return (String::new(), String::new(), false);
        }

        // Check if the type is quoted
        if stripped.starts_with('"') {
            // Find the closing quote, handling escape sequences
            let mut type_content = String::new();
            let chars: Vec<char> = stripped.chars().collect();
            let mut i = 1; // skip opening quote
            debug_assert!(
                !chars.is_empty(),
                "Chars should not be empty if starts_with quote"
            );
            debug_assert!(chars[0] == '"', "First character should be quote");

            while i < chars.len() {
                debug_assert!(
                    i < chars.len(),
                    "Index should be within bounds during quote parsing"
                );
                let c = chars[i];

                // Handle escape sequences
                if c == '\\' && i + 1 < chars.len() {
                    let next_char = chars[i + 1];
                    if next_char == '"' || next_char == '\\' {
                        // Escaped quote or backslash - add the literal character
                        type_content.push(next_char);
                        i += 2;
                        continue;
                    }
                }

                // Check for closing quote
                if c == '"' {
                    // Found closing quote - check for void marker after quote
                    let remaining_start = i + 1;
                    let mut is_void = false;
                    let mut actual_remaining_start = remaining_start;

                    // Check if next char is ':' (void marker)
                    if remaining_start < chars.len() && chars[remaining_start] == ':' {
                        is_void = true;
                        actual_remaining_start = remaining_start + 1;
                    }

                    let remaining = if actual_remaining_start < chars.len() {
                        let remaining_text: String =
                            chars[actual_remaining_start..].iter().collect();
                        // Skip leading space if present
                        if let Some(stripped) = remaining_text.strip_prefix(' ') {
                            stripped.to_string()
                        } else {
                            remaining_text
                        }
                    } else {
                        String::new()
                    };

                    return (type_content, remaining, is_void);
                }

                // Regular character
                type_content.push(c);
                i += 1;
            }

            // Unclosed quote, treat as regular parsing
            if let Some((type_part, remaining)) = stripped.split_once(' ') {
                let is_void = type_part.ends_with(':');
                let type_part = if is_void {
                    type_part.trim_end_matches(':').to_string()
                } else {
                    type_part.to_string()
                };
                (type_part, remaining.to_string(), is_void)
            } else {
                let is_void = stripped.ends_with(':');
                let type_name = if is_void {
                    stripped.trim_end_matches(':').to_string()
                } else {
                    stripped.to_string()
                };
                (type_name, String::new(), is_void)
            }
        } else {
            // Regular parsing (no quotes) - check for void marker
            if let Some((type_part, remaining)) = stripped.split_once(' ') {
                let is_void = type_part.ends_with(':');
                let type_part = if is_void {
                    type_part.trim_end_matches(':').to_string()
                } else {
                    type_part.to_string()
                };
                (type_part, remaining.to_string(), is_void)
            } else {
                let is_void = stripped.ends_with(':');
                let type_name = if is_void {
                    stripped.trim_end_matches(':').to_string()
                } else {
                    stripped.to_string()
                };
                (type_name, String::new(), is_void)
            }
        }
    } else {
        (String::new(), text.to_string(), false)
    }
}

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

    #[test]
    fn test_extract_id_string() {
        // Test cases without ID
        assert_eq!(
            extract_id_string(".div"),
            (String::new(), ".div".to_string())
        );
        assert_eq!(
            extract_id_string(".p some text"),
            (String::new(), ".p some text".to_string())
        );

        // Test cases with ID
        assert_eq!(
            extract_id_string(".div :main"),
            ("main".to_string(), ".div".to_string())
        );
        assert_eq!(
            extract_id_string(".p some text :test"),
            ("test".to_string(), ".p some text".to_string())
        );
        assert_eq!(
            extract_id_string(".p some text :test-123"),
            ("test-123".to_string(), ".p some text".to_string())
        );
        // Dots are no longer allowed in IDs (reserved for document references)
        assert_eq!(
            extract_id_string(".p some text :test_sub"),
            ("test_sub".to_string(), ".p some text".to_string())
        );
        assert_eq!(
            extract_id_string(".p some text :test_sub_deep"),
            ("test_sub_deep".to_string(), ".p some text".to_string())
        );

        // Test with trailing spaces
        assert_eq!(
            extract_id_string(".div :main   "),
            ("main".to_string(), ".div".to_string())
        );
        assert_eq!(
            extract_id_string(".p some text  :test-123  "),
            ("test-123".to_string(), ".p some text".to_string())
        );

        // Test with underscores
        assert_eq!(
            extract_id_string(".div :main_section"),
            ("main_section".to_string(), ".div".to_string())
        );
    }

    #[test]
    fn test_meta_line_with_id() {
        // Test basic block with ID
        let meta = MetaLine::parse(".div :main", 0).unwrap();
        assert_eq!(meta.type_name, "div");
        assert_eq!(meta.id.as_str(), "main");
        assert_eq!(meta.level, 1);
        assert_eq!(meta.inline_text.content, "");
        // raw_params field removed from MetaLine

        // Test block with inline text and ID
        let meta = MetaLine::parse(".p some text :test", 0).unwrap();
        assert_eq!(meta.type_name, "p");
        assert_eq!(meta.id.as_str(), "test");
        assert_eq!(meta.level, 1);
        assert_eq!(meta.inline_text.content, "some text");
        // raw_params field removed from MetaLine

        // Test block with params and ID
        let meta = MetaLine::parse(".div .. class=main :section", 0).unwrap();
        assert_eq!(meta.type_name, "div");
        assert_eq!(meta.id.as_str(), "section");
        assert_eq!(meta.level, 1);
        assert_eq!(meta.inline_text.content, "");
        // raw_params field removed from MetaLine

        // Test block with inline text, params, and ID
        let meta = MetaLine::parse(".p some text .. class=main :test-123", 0).unwrap();
        assert_eq!(meta.type_name, "p");
        assert_eq!(meta.id.as_str(), "test-123");
        assert_eq!(meta.level, 1);
        assert_eq!(meta.inline_text.content, "some text ");
        // raw_params field removed from MetaLine

        // Test block with complex ID (underscores, no dots allowed)
        let meta = MetaLine::parse(".div :main_section_subsection", 0).unwrap();
        assert_eq!(meta.type_name, "div");
        assert_eq!(meta.id.as_str(), "main_section_subsection");
        assert_eq!(meta.level, 1);

        // Test block without ID
        let meta = MetaLine::parse(".div", 0).unwrap();
        assert_eq!(meta.type_name, "div");
        assert!(meta.id.is_empty());
        assert_eq!(meta.level, 1);
    }

    #[test]
    fn test_edge_cases_meta_line_parsing() {
        // Empty line should return None
        assert!(MetaLine::parse("", 0).is_none());

        // Non-meta line should return None
        assert!(MetaLine::parse("not a meta line", 0).is_none());

        // Single dot with empty type after extraction - this actually parses successfully
        let meta = MetaLine::parse(".", 0).unwrap();
        assert_eq!(meta.type_name, "");

        // Just dots - this is a heading, but the meta_line has empty type_name
        let meta = MetaLine::parse("...", 0).unwrap();
        assert_eq!(meta.type_name, "");
        assert_eq!(meta.level, 3);

        // Meta line with quoted type but unclosed quote
        let meta = MetaLine::parse(".\"unclosed", 0).unwrap();
        assert_eq!(meta.type_name, "\"unclosed");
    }

    #[test]
    fn test_quoted_type_extraction() {
        // Test quoted type with spaces
        let (type_name, remaining, is_void) = extract_line_type(".\"block type\" remaining text");
        assert_eq!(type_name, "block type");
        assert_eq!(remaining, "remaining text");
        assert!(!is_void);

        // Test quoted type with escaped quotes
        let (type_name, remaining, is_void) = extract_line_type(".\"quote\\\"test\" remaining");
        assert_eq!(type_name, "quote\"test");
        assert_eq!(remaining, "remaining");
        assert!(!is_void);

        // Test quoted type with escaped backslash
        let (type_name, remaining, is_void) = extract_line_type(".\"back\\\\slash\" remaining");
        assert_eq!(type_name, "back\\slash");
        assert_eq!(remaining, "remaining");
        assert!(!is_void);
    }

    #[test]
    fn test_split_parts_edge_cases() {
        // Empty string
        let parts = split_into_parts("");
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0], "");

        // Just dots
        let parts = split_into_parts("..");
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0], "");
        assert_eq!(parts[1], "");

        // Dots inside quotes
        let parts = split_into_parts("\"text .. with dots\" .. param");
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0], "\"text .. with dots\" ");
        assert_eq!(parts[1], " param");

        // Escaped quotes
        let parts = split_into_parts("\"escaped \\\" quote\" .. param");
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0], "\"escaped \\\" quote\" ");
        assert_eq!(parts[1], " param");
    }

    #[test]
    fn test_is_quoted_text() {
        assert!(is_quoted_text("\"quoted\""));
        assert!(is_quoted_text("  \"quoted\"  "));
        assert!(!is_quoted_text("not quoted"));
        assert!(!is_quoted_text("\"only start"));
        assert!(!is_quoted_text("only end\""));
        assert!(!is_quoted_text("\""));
        assert!(!is_quoted_text(""));
    }

    #[test]
    fn test_id_functionality() {
        let id = Id::new("test".to_string());
        assert_eq!(id.as_str(), "test");
        assert!(!id.is_empty());

        let empty_id = Id::empty();
        assert!(empty_id.is_empty());
        assert_eq!(empty_id.as_str(), "");

        let from_str: Id = "test".into();
        assert_eq!(from_str.as_str(), "test");

        let from_string: Id = "test".to_string().into();
        assert_eq!(from_string.as_str(), "test");
    }

    #[test]
    fn test_id_base36_conversion() {
        // Test creating ID from u64
        let id = Id::from_u64(123456);
        assert_eq!(id.as_str(), "2n9c");
        assert_eq!(id.as_u64(), Some(123456));
        assert!(id.is_numeric());

        // Test from trait for u64
        let id2: Id = 789u64.into();
        assert_eq!(id2.as_str(), "lx");
        assert_eq!(id2.as_u64(), Some(789));

        // Test edge cases
        let id_zero = Id::from_u64(0);
        assert_eq!(id_zero.as_str(), "0");
        assert_eq!(id_zero.as_u64(), Some(0));

        let id_max = Id::from_u64(u64::MAX);
        assert_eq!(id_max.as_str(), "3w5e11264sgsf");
        assert_eq!(id_max.as_u64(), Some(u64::MAX));
    }

    #[test]
    fn test_id_non_numeric() {
        // Test non-base36 string IDs
        let id = Id::new("my-custom-id".to_string());
        assert_eq!(id.as_str(), "my-custom-id");
        assert_eq!(id.as_u64(), None);
        assert!(!id.is_numeric());

        // Test string with uppercase (not valid base36)
        let id_upper = Id::new("ABC123".to_string());
        assert_eq!(id_upper.as_str(), "ABC123");
        assert_eq!(id_upper.as_u64(), None);
        assert!(!id_upper.is_numeric());

        // Test valid base36 string
        let id_b36 = Id::new("abc123".to_string());
        assert_eq!(id_b36.as_str(), "abc123");
        assert!(id_b36.as_u64().is_some());
        assert!(id_b36.is_numeric());
    }

    #[test]
    fn test_id_roundtrip() {
        // Test roundtrip conversion
        let test_values = vec![0, 1, 100, 1000, 10000, 123456789, u64::MAX];

        for value in test_values {
            let id = Id::from_u64(value);
            assert_eq!(id.as_u64(), Some(value));

            // Also test that we can create from the string representation
            let id2 = Id::new(id.as_str().to_string());
            assert_eq!(id2.as_u64(), Some(value));
        }
    }

    #[test]
    fn test_malformed_meta_lines() {
        // Meta line with only special characters
        let meta = MetaLine::parse(".@#$%^&*()", 0).unwrap();
        assert_eq!(meta.type_name, "@#$%^&*()");

        // Meta line with unicode characters
        let meta = MetaLine::parse(".блок", 0).unwrap();
        assert_eq!(meta.type_name, "блок");

        // Very long type name
        let long_type = "a".repeat(1000);
        let meta = MetaLine::parse(&format!("..{long_type}"), 0).unwrap();
        assert_eq!(meta.type_name, long_type);
        assert_eq!(meta.level, 2);
    }

    #[test]
    fn test_extract_id_edge_cases() {
        // Multiple colons - now allowed in IDs
        assert_eq!(
            extract_id_string(".div :test:invalid"),
            ("test:invalid".to_string(), ".div".to_string())
        );

        // Uppercase IDs are now valid
        assert_eq!(
            extract_id_string(".div :TEST"),
            ("TEST".to_string(), ".div".to_string())
        );

        // Valid ID with numbers and dashes
        assert_eq!(
            extract_id_string(".div :test-123"),
            ("test-123".to_string(), ".div".to_string())
        );
    }

    // Additional tests to cover missing lines from TODO list

    #[test]
    fn test_quoted_string_new() {
        // Test line 10 in function `new`
        let qs = QuotedString::new("test content".to_string(), true);
        assert_eq!(qs.content, "test content");
        assert!(qs.quoted);
        assert!(qs.links.is_empty());

        // Test line 14 in function `new`
        let qs = QuotedString::new("content".to_string(), false);
        assert!(qs.links.is_empty());
    }

    #[test]
    fn test_quoted_string_with_links() {
        // Test lines 19-22 in function `with_links`
        let qs = QuotedString::with_links("test content".to_string(), true);
        assert_eq!(qs.content, "test content");
        assert!(qs.quoted);
        // Links will be populated by parse_links call
    }

    #[test]
    fn test_quoted_string_parse_links() {
        // Test line 26 in function `parse_links`
        let mut qs = QuotedString::new("Check ((site :example.com)) here".to_string(), false);

        // Test lines 29-30 in function `parse_links`
        qs.parse_links();

        // Test line 32 in function `parse_links`
        // Links should be detected and parsed
        assert!(!qs.links.is_empty());
    }

    #[test]
    fn test_id_new() {
        // Test line 43 in function `new`
        let id = Id::new("test_id".to_string());
        assert_eq!(id.value, "test_id");
    }

    #[test]
    fn test_id_empty() {
        // Test line 47 in function `empty`
        let id = Id::empty();

        // Test line 49 in function `empty`
        assert_eq!(id.value, String::new());
        assert!(id.is_empty());
    }

    #[test]
    fn test_id_is_empty() {
        // Test lines 53-54 in function `is_empty`
        let empty_id = Id::new(String::new());
        assert!(empty_id.is_empty());

        let non_empty_id = Id::new("test".to_string());
        assert!(!non_empty_id.is_empty());
    }

    #[test]
    fn test_id_as_str() {
        // Test lines 57-58 in function `as_str`
        let id = Id::new("test_string".to_string());
        assert_eq!(id.as_str(), "test_string");
    }

    #[test]
    fn test_id_from_u64() {
        // Test line 62 in function `from_u64`
        let id = Id::from_u64(12345);

        // Test line 64 in function `from_u64`
        assert!(!id.value.is_empty());
        assert!(id.as_u64().is_some());
    }

    #[test]
    fn test_id_as_u64() {
        // Test lines 70-72 in function `as_u64`
        let empty_id = Id::new(String::new());
        assert_eq!(empty_id.as_u64(), None);

        // Test line 74 in function `as_u64`
        let numeric_id = Id::from_u64(999);
        assert_eq!(numeric_id.as_u64(), Some(999));
    }

    #[test]
    fn test_id_is_numeric() {
        // Test lines 78-79 in function `is_numeric`
        let numeric_id = Id::from_u64(456);
        assert!(numeric_id.is_numeric());

        let string_id = Id::new("not_numeric".to_string());
        assert!(!string_id.is_numeric());
    }

    #[test]
    fn test_id_from_string() {
        // Test lines 84-85 in function `from`
        let id: Id = "test_string".to_string().into();
        assert_eq!(id.value, "test_string");
    }

    #[test]
    fn test_id_from_str() {
        // Test lines 90-91 in function `from`
        let id: Id = "test_str".into();
        assert_eq!(id.value, "test_str");
    }

    #[test]
    fn test_id_from_u64_trait() {
        // Test lines 96-97 in function `from`
        let id: Id = 12345u64.into();
        assert_eq!(id.as_u64(), Some(12345));
    }

    #[test]
    fn test_extract_id_string_debug_assertions() {
        // Test line 130 in function `extract_id_string`
        // We can't directly test debug_assert, but we can test the function behavior

        // Test line 133 in function `extract_id_string`
        let (id, cleaned) = extract_id_string(".div :test");
        assert_eq!(id, "test");
        assert_eq!(cleaned, ".div");
    }

    #[test]
    fn test_extract_id_string_regex_match() {
        // Test lines 140-141 in function `extract_id_string`
        let (id, _cleaned) = extract_id_string(".block text :myid");
        assert_eq!(id, "myid");

        // Test with underscores and dashes (dots no longer allowed)
        let (id, cleaned) = extract_id_string(".div text :complex_id-with_parts");
        assert_eq!(id, "complex_id-with_parts");
        assert_eq!(cleaned, ".div text");
    }

    #[test]
    fn test_extract_id_string_no_match() {
        // Test line 152 in unknown context
        let (id, cleaned) = extract_id_string(".div no_id_here");
        assert_eq!(id, String::new());
        assert_eq!(cleaned, ".div no_id_here");
    }

    #[test]
    fn test_extract_id_string_replace_logic() {
        // Test lines 156-157 in unknown context
        let (id, cleaned) = extract_id_string(".p content :test_id   ");
        assert_eq!(id, "test_id");
        assert_eq!(cleaned, ".p content");

        // Test line 162 in unknown context
        let (_id, cleaned) = extract_id_string(".div :simple");
        assert_eq!(cleaned, ".div");

        // Test line 164 in unknown context
        let (id, cleaned) = extract_id_string("regular text");
        assert_eq!(id, String::new());
        assert_eq!(cleaned, "regular text");
    }

    #[test]
    fn test_meta_line_parse_empty() {
        // Test lines 207-209 in function `parse`
        let result = MetaLine::parse("", 0);
        assert!(result.is_none());
    }

    #[test]
    fn test_meta_line_parse_not_meta() {
        // Test lines 212-213 in function `parse`
        let result = MetaLine::parse("regular text", 0);
        assert!(result.is_none());
    }

    #[test]
    fn test_meta_line_parse_debug_assert() {
        // Test line 216 in function `parse`
        let meta = MetaLine::parse(".div", 0).unwrap();
        assert_eq!(meta.type_name, "div");
    }

    #[test]
    fn test_meta_line_parse_id_extraction() {
        // Test lines 219-220 in function `parse`
        let meta = MetaLine::parse(".div content :test_id", 0).unwrap();
        assert_eq!(meta.id.as_str(), "test_id");
    }

    #[test]
    fn test_meta_line_level_counting() {
        // Test line 226 in unknown context
        let meta = MetaLine::parse("..div", 0).unwrap();
        assert_eq!(meta.level, 2);

        let meta = MetaLine::parse("...div", 0).unwrap();
        assert_eq!(meta.level, 3);
    }

    #[test]
    fn test_meta_line_extract_line_type() {
        // Test lines 230-231 in unknown context
        let meta = MetaLine::parse(".block", 0).unwrap();
        assert_eq!(meta.type_name, "block");
    }

    #[test]
    fn test_meta_line_empty_remaining() {
        // Test lines 236-237 in unknown context
        let meta = MetaLine::parse(".simple", 0).unwrap();
        assert_eq!(meta.type_name, "simple");
        assert_eq!(meta.inline_text.content, "");

        // Test lines 241-243 in unknown context
        assert_eq!(meta.level, 1);
        assert_eq!(meta.inline_text.content, "");

        // Test lines 245-249 in unknown context
        assert!(meta.id.is_empty());
        assert!(meta.arguments.args.is_empty());
        assert!(meta.arguments.kwargs.is_empty());
    }

    #[test]
    fn test_meta_line_split_parts() {
        // Test lines 255-256 in unknown context
        let meta = MetaLine::parse(".block text .. param=value", 0).unwrap();
        assert_eq!(meta.inline_text.content, "text ");

        // Test line 261 in unknown context
        // This tests the parts.len() check in the match statement
        assert_eq!(meta.type_name, "block");
    }

    #[test]
    fn test_meta_line_one_part() {
        // Test lines 265-267 in unknown context
        let meta = MetaLine::parse(".block inline text", 0).unwrap();
        assert_eq!(meta.inline_text.content, "inline text");

        // Test lines 271-273 in unknown context
        assert_eq!(meta.type_name, "block");
        assert_eq!(meta.level, 1);

        // Test lines 275-279 in unknown context
        assert!(meta.id.is_empty());
        assert!(meta.arguments.args.is_empty());
        assert!(meta.arguments.kwargs.is_empty());
    }

    #[test]
    fn test_meta_line_two_parts() {
        // Test line 287 in unknown context
        let meta = MetaLine::parse(".block text .. param=value", 0).unwrap();

        // Test lines 292-295 in unknown context
        assert_eq!(meta.inline_text.content, "text ");
        let quoted = is_quoted_text(&meta.inline_text.content);
        assert!(!quoted);

        // Test lines 297-298 in unknown context
        assert!(!meta.arguments.args.is_empty() || !meta.arguments.kwargs.is_empty());
    }

    #[test]
    fn test_meta_line_empty_params() {
        // Test line 301 in unknown context
        let meta = MetaLine::parse(".block text .. ", 0).unwrap();

        // Test line 303 in unknown context
        assert!(meta.arguments.args.is_empty());
        assert!(meta.arguments.kwargs.is_empty());

        // Test lines 307-309 in unknown context
        assert_eq!(meta.type_name, "block");
        assert_eq!(meta.inline_text.content, "text ");

        // Test lines 311-313 in unknown context
        assert_eq!(meta.level, 1);
        assert!(meta.id.is_empty());
    }

    #[test]
    fn test_meta_line_multiple_parts() {
        // Test line 319 in unknown context
        let meta = MetaLine::parse(".block text .. param1 .. param2", 0).unwrap();

        // Test lines 323-326 in unknown context
        assert_eq!(meta.inline_text.content, "text ");
        let quoted = is_quoted_text(&meta.inline_text.content);
        assert!(!quoted);

        // Test line 330 in unknown context
        assert!(!meta.arguments.args.is_empty() || !meta.arguments.kwargs.is_empty());

        // Test lines 332-333 in unknown context
        assert_eq!(meta.type_name, "block");

        // Test line 336 in unknown context
        assert_eq!(meta.level, 1);

        // Test line 338 in unknown context
        assert!(meta.id.is_empty());

        // Test lines 342-344 in unknown context
        assert_eq!(meta.type_name, "block");
        assert_eq!(meta.level, 1);

        // Test lines 346-348 in unknown context
        assert!(meta.id.is_empty());
    }

    #[test]
    fn test_is_meta_line_function() {
        // Test lines 371-372 in function `is_meta_line`
        assert!(is_meta_line(".block"));
        assert!(!is_meta_line(""));
        assert!(!is_meta_line("regular text"));
        assert!(is_meta_line("..nested"));
    }

    #[test]
    fn test_is_quoted_text_function() {
        // Test lines 376-378 in function `is_quoted_text`
        let trimmed = "  \"quoted text\"  ".trim();
        assert_eq!(trimmed.len(), 13);
        assert!(trimmed.starts_with('"') && trimmed.ends_with('"'));

        assert!(is_quoted_text("\"quoted\""));
        assert!(!is_quoted_text("not quoted"));
    }

    #[test]
    fn test_split_into_parts_basic() {
        // Test lines 404-409 in function `split_into_parts`
        let _result: Vec<String> = Vec::new();
        let _current = String::new();
        let _in_quotes = false;
        let chars: Vec<char> = "text .. param".chars().collect();
        let _n = chars.len();

        let parts = split_into_parts("text .. param");
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0], "text ");
        assert_eq!(parts[1], " param");
    }

    #[test]
    fn test_split_into_parts_loop() {
        // Test lines 411-414 in function `split_into_parts`
        let parts = split_into_parts("a .. b .. c");
        assert_eq!(parts.len(), 3);
        assert_eq!(parts[0], "a ");
        assert_eq!(parts[1], " b ");
        assert_eq!(parts[2], " c");
    }

    #[test]
    fn test_split_into_parts_escape_sequences() {
        // Test lines 417-418 in function `split_into_parts`
        let parts = split_into_parts("\"escaped \\\" quote\" .. param");
        assert_eq!(parts.len(), 2);

        // Test lines 422-423 in function `split_into_parts`
        let parts = split_into_parts("\"backslash \\\\\" .. param");
        assert_eq!(parts.len(), 2);
    }

    #[test]
    fn test_split_into_parts_continue_logic() {
        // Test lines 425-428 in unknown context
        let parts = split_into_parts("\"text with \\\" escaped quote\" .. param");
        assert_eq!(parts.len(), 2);
        assert!(parts[0].contains("\\\" "));
    }

    #[test]
    fn test_split_into_parts_quotes_logic() {
        // Test lines 437-440 in unknown context
        let parts = split_into_parts("\"quoted .. text\" .. param");
        assert_eq!(parts.len(), 2);
        assert!(parts[0].contains(".."));
    }

    #[test]
    fn test_split_into_parts_delimiter_check() {
        // Test lines 445-446 in unknown context
        let parts = split_into_parts("no dots here");
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0], "no dots here");
    }

    #[test]
    fn test_split_into_parts_delimiter_found() {
        // Test lines 451-454 in unknown context
        let parts = split_into_parts("text .. param");
        assert_eq!(parts.len(), 2);
        assert_eq!(parts[0], "text ");
        assert_eq!(parts[1], " param");
    }

    #[test]
    fn test_split_into_parts_char_collection() {
        // Test lines 462-464 in unknown context
        let parts = split_into_parts("single");
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0], "single");
    }

    #[test]
    fn test_split_into_parts_final_push() {
        // Test lines 471-472 in unknown context
        let parts = split_into_parts("final part");
        assert_eq!(parts.len(), 1);
        assert_eq!(parts[0], "final part");
    }

    #[test]
    fn test_split_into_parts_result_not_empty() {
        // Test line 477 in unknown context
        let parts = split_into_parts("");
        assert!(!parts.is_empty());
        assert_eq!(parts.len(), 1);
    }

    #[test]
    fn test_split_into_parts_and_trim() {
        // Test lines 482-483 in function `split_into_parts_and_trim`
        let parts = split_into_parts_and_trim("  text  ..  param  ");

        // Test line 485 in function `split_into_parts_and_trim`
        assert_eq!(parts[0], "text");
        assert_eq!(parts[1], "param");
    }

    #[test]
    fn test_extract_line_type_basic() {
        // Test lines 493-494 in function `extract_line_type`
        let (type_name, remaining, is_void) = extract_line_type(".block remaining");
        assert_eq!(type_name, "block");
        assert_eq!(remaining, "remaining");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_strip_prefix() {
        // Test lines 496-497 in function `extract_line_type`
        let (type_name, remaining, is_void) = extract_line_type(".test");
        assert_eq!(type_name, "test");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_empty_after_dot() {
        // Test lines 501-502 in function `extract_line_type`
        let (type_name, remaining, is_void) = extract_line_type(".");
        assert_eq!(type_name, "");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_no_dot_prefix() {
        // Test line 506 in function `extract_line_type`
        let (type_name, remaining, is_void) = extract_line_type("no_dot");
        assert_eq!(type_name, "");
        assert_eq!(remaining, "no_dot");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_quoted() {
        // Test lines 508-511 in function `extract_line_type`
        let (type_name, remaining, is_void) = extract_line_type(".\"quoted type\" remaining");
        assert_eq!(type_name, "quoted type");
        assert_eq!(remaining, "remaining");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_quote_parsing() {
        // Test line 515 in unknown context
        let (type_name, _remaining, is_void) = extract_line_type(".\"complex\"");
        assert_eq!(type_name, "complex");
        assert!(!is_void);

        // Test lines 517-518 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"test\" remaining");
        assert_eq!(type_name, "test");
        assert_eq!(remaining, "remaining");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_escape_handling() {
        // Test line 522 in unknown context
        let (type_name, _remaining, is_void) = extract_line_type(".\"escaped \\\" quote\"");
        assert_eq!(type_name, "escaped \" quote");
        assert!(!is_void);

        // Test lines 525-527 in unknown context
        let (type_name, _remaining, is_void) = extract_line_type(".\"backslash \\\\\"");
        assert_eq!(type_name, "backslash \\");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_escape_continue() {
        // Test lines 529-530 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"with\\\"quote\" rest");
        assert_eq!(type_name, "with\"quote");
        assert_eq!(remaining, "rest");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_closing_quote() {
        // Test line 536 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"closed\"");
        assert_eq!(type_name, "closed");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_remaining_calculation() {
        // Test lines 538-540 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"test\" with remaining");
        assert_eq!(type_name, "test");
        assert_eq!(remaining, "with remaining");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_strip_space() {
        // Test lines 542-543 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"test\"  space");
        assert_eq!(type_name, "test");
        // Leading space should be stripped
        assert_eq!(remaining, " space");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_no_remaining() {
        // Test line 545 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"test\"");
        assert_eq!(type_name, "test");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_return_values() {
        // Test line 548 in unknown context
        let (type_name, _remaining, is_void) = extract_line_type(".\"simple\"");
        assert_eq!(type_name, "simple");
        assert!(!is_void);

        // Test line 551 in unknown context
        let (_type_name, remaining, is_void) = extract_line_type(".\"test\" text");
        assert_eq!(remaining, "text");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_char_push() {
        // Test lines 555-556 in unknown context
        let (type_name, _remaining, is_void) = extract_line_type(".\"complex type name\"");
        assert_eq!(type_name, "complex type name");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_unclosed_quote() {
        // Test lines 560-561 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"unclosed quote text");
        // Should fall back to regular parsing
        assert_eq!(type_name, "\"unclosed");
        assert_eq!(remaining, "quote text");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_unclosed_quote_no_space() {
        // Test line 563 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".\"unclosedquote");
        // Should fall back to regular parsing with no space
        assert_eq!(type_name, "\"unclosedquote");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_regular_parsing() {
        // Test lines 567-568 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".block some arguments");
        assert_eq!(type_name, "block");
        assert_eq!(remaining, "some arguments");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_regular_parsing_no_space() {
        // Test line 570 in unknown context
        let (type_name, remaining, is_void) = extract_line_type(".singleblock");
        assert_eq!(type_name, "singleblock");
        assert_eq!(remaining, "");
        assert!(!is_void);
    }

    #[test]
    fn test_extract_line_type_no_dot_prefix_fallback() {
        // Test line 574 in unknown context
        let (type_name, remaining, is_void) = extract_line_type("no dot prefix text");
        assert_eq!(type_name, "");
        assert_eq!(remaining, "no dot prefix text");
        assert!(!is_void);
    }

    // ==============================================================================
    // Void Element Tests
    // ==============================================================================

    #[test]
    fn test_void_element_basic() {
        // Basic void element
        let (type_name, remaining, is_void) = extract_line_type(".hr:");
        assert_eq!(type_name, "hr");
        assert_eq!(remaining, "");
        assert!(is_void);
    }

    #[test]
    fn test_void_element_with_remaining() {
        // Void element with remaining text (inline text)
        let (type_name, remaining, is_void) = extract_line_type(".img: alt text here");
        assert_eq!(type_name, "img");
        assert_eq!(remaining, "alt text here");
        assert!(is_void);
    }

    #[test]
    fn test_void_element_quoted_type() {
        // Void element with quoted type
        let (type_name, remaining, is_void) = extract_line_type(".\"page break\": with text");
        assert_eq!(type_name, "page break");
        assert_eq!(remaining, "with text");
        assert!(is_void);
    }

    #[test]
    fn test_void_element_quoted_type_no_remaining() {
        // Void element with quoted type, no remaining
        let (type_name, remaining, is_void) = extract_line_type(".\"void type\":");
        assert_eq!(type_name, "void type");
        assert_eq!(remaining, "");
        assert!(is_void);
    }

    #[test]
    fn test_meta_line_parse_void_element() {
        // Test parsing a void element via MetaLine::parse
        let meta = MetaLine::parse(".hr:", 0).unwrap();
        assert_eq!(meta.type_name, "hr");
        assert!(meta.is_void);
        assert_eq!(meta.level, 1);

        // With inline text
        let meta = MetaLine::parse(".img: alt text", 0).unwrap();
        assert_eq!(meta.type_name, "img");
        assert!(meta.is_void);
        assert_eq!(meta.inline_text.content, "alt text");

        // With parameters
        let meta = MetaLine::parse(".img: alt .. src=\"photo.jpg\"", 0).unwrap();
        assert_eq!(meta.type_name, "img");
        assert!(meta.is_void);
        assert_eq!(meta.inline_text.content, "alt ");

        // With ID
        let meta = MetaLine::parse(".hr: :divider1", 0).unwrap();
        assert_eq!(meta.type_name, "hr");
        assert!(meta.is_void);
        assert_eq!(meta.id.as_str(), "divider1");

        // Nested void element
        let meta = MetaLine::parse("..br:", 0).unwrap();
        assert_eq!(meta.type_name, "br");
        assert!(meta.is_void);
        assert_eq!(meta.level, 2);
    }

    #[test]
    fn test_meta_line_parse_not_void() {
        // Regular block (not void)
        let meta = MetaLine::parse(".paragraph", 0).unwrap();
        assert_eq!(meta.type_name, "paragraph");
        assert!(!meta.is_void);

        // Block with content
        let meta = MetaLine::parse(".div content", 0).unwrap();
        assert_eq!(meta.type_name, "div");
        assert!(!meta.is_void);
    }

    #[test]
    fn test_document_id_reference_parse() {
        // Basic parsing
        let ref_ = ExternalIdReference::parse("doc-id.local-id").unwrap();
        assert_eq!(ref_.document_id.as_str(), "doc-id");
        assert_eq!(ref_.local_id.as_str(), "local-id");
        assert!(ref_.document_path.is_none());

        // With underscores and dashes
        let ref_ = ExternalIdReference::parse("my_doc.section_1").unwrap();
        assert_eq!(ref_.document_id.as_str(), "my_doc");
        assert_eq!(ref_.local_id.as_str(), "section_1");

        // Local ID can still contain dashes
        let ref_ = ExternalIdReference::parse("readme.heading-intro").unwrap();
        assert_eq!(ref_.document_id.as_str(), "readme");
        assert_eq!(ref_.local_id.as_str(), "heading-intro");
    }

    #[test]
    fn test_document_id_reference_parse_invalid() {
        // No dot - returns None
        assert!(ExternalIdReference::parse("nodot").is_none());

        // Empty document_id
        assert!(ExternalIdReference::parse(".local-id").is_none());

        // Empty local_id
        assert!(ExternalIdReference::parse("doc-id.").is_none());

        // Just a dot
        assert!(ExternalIdReference::parse(".").is_none());
    }

    #[test]
    fn test_document_id_reference_new() {
        let ref_ =
            ExternalIdReference::new(Id::new("doc".to_string()), Id::new("local".to_string()));
        assert_eq!(ref_.document_id.as_str(), "doc");
        assert_eq!(ref_.local_id.as_str(), "local");
        assert!(ref_.document_path.is_none());
    }

    #[test]
    fn test_document_id_reference_with_path() {
        let ref_ = ExternalIdReference::with_path(
            Id::new("doc".to_string()),
            Id::new("local".to_string()),
            "/path/to/doc.wasot".to_string(),
        );
        assert_eq!(ref_.document_id.as_str(), "doc");
        assert_eq!(ref_.local_id.as_str(), "local");
        assert_eq!(ref_.document_path, Some("/path/to/doc.wasot".to_string()));
    }

    #[test]
    fn test_document_id_reference_to_qualified_string() {
        let ref_ = ExternalIdReference::new(
            Id::new("my-doc".to_string()),
            Id::new("section-1".to_string()),
        );
        assert_eq!(ref_.to_qualified_string(), "my-doc.section-1");
    }

    #[test]
    fn test_document_id_reference_roundtrip() {
        let original = "doc-id.local-id";
        let ref_ = ExternalIdReference::parse(original).unwrap();
        assert_eq!(ref_.to_qualified_string(), original);
    }

    #[test]
    fn test_target_type_id_in_document() {
        let doc_ref = ExternalIdReference::parse("readme.intro").unwrap();
        let target_type = TargetType::IdInDocument(doc_ref);

        match target_type {
            TargetType::IdInDocument(ref_) => {
                assert_eq!(ref_.document_id.as_str(), "readme");
                assert_eq!(ref_.local_id.as_str(), "intro");
            }
            _ => panic!("Expected IdInDocument variant"),
        }
    }

    #[test]
    fn test_ids_no_longer_allow_dots() {
        // IDs with dots should NOT be extracted (dots reserved for document references)
        let (id, cleaned) = extract_id_string(".div text :has.dot");
        // The regex won't match because dots aren't allowed
        assert_eq!(id, "");
        assert_eq!(cleaned, ".div text :has.dot");
    }
}
