"""
BlockMD Parser Library - AST-based parser for structured Markdown.

This module provides the core parsing functionality and query API for BlockMD format.
"""

import re
from typing import List, Dict, Optional, Any, Callable
from dataclasses import dataclass, field
from abc import ABC, abstractmethod


# ============================================================================
# AST Node Definition
# ============================================================================

@dataclass
class SourceLocation:
    """Source location information for AST nodes."""
    line_start: int
    line_end: int
    col_start: int = 0
    col_end: int = 0


@dataclass
class Block:
    """
    AST node representing a BlockMD block.
    
    Attributes:
        title: Block header text (without # prefix)
        level: Header level (1-6, corresponding to #-######)
        properties: Metadata key-value pairs
        content: Markdown content as list of lines
        children: Nested child blocks
        parent: Reference to parent block (None for root blocks)
        location: Source code location information
    """
    title: str
    level: int
    properties: Dict[str, str] = field(default_factory=dict)
    content: List[str] = field(default_factory=list)
    children: List['Block'] = field(default_factory=list)
    parent: Optional['Block'] = None
    location: Optional[SourceLocation] = None
    
    def get_content_text(self) -> str:
        """Get block content as single string."""
        return "\n".join(self.content).strip()
    
    def get_property(self, key: str, default: Any = None) -> Any:
        """Get property value with optional default."""
        return self.properties.get(key, default)
    
    def has_property(self, key: str) -> bool:
        """Check if property exists."""
        return key in self.properties
    
    def get_path(self) -> List[str]:
        """Get path from root to this block as list of titles."""
        path = []
        current = self
        while current:
            path.insert(0, current.title)
            current = current.parent
        return path
    
    def get_path_string(self, separator: str = " > ") -> str:
        """Get path as formatted string."""
        return separator.join(self.get_path())
    
    def is_root(self) -> bool:
        """Check if this is a root block."""
        return self.parent is None
    
    def is_leaf(self) -> bool:
        """Check if this block has no children."""
        return len(self.children) == 0
    
    def get_depth(self) -> int:
        """Get depth of this block (root = 0)."""
        depth = 0
        current = self.parent
        while current:
            depth += 1
            current = current.parent
        return depth
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert block to dictionary representation."""
        return {
            "title": self.title,
            "level": self.level,
            "properties": self.properties,
            "content": self.get_content_text(),
            "children": [child.to_dict() for child in self.children],
            "path": self.get_path_string()
        }


# ============================================================================
# Visitor Pattern for Tree Traversal
# ============================================================================

class BlockVisitor(ABC):
    """Abstract visitor for traversing Block AST."""
    
    @abstractmethod
    def visit_block(self, block: Block) -> Any:
        """Visit a block node."""
        pass


class BlockTraverser:
    """Utility for traversing block trees with visitors."""
    
    @staticmethod
    def traverse_preorder(block: Block, visitor: BlockVisitor) -> None:
        """Traverse tree in pre-order (parent before children)."""
        visitor.visit_block(block)
        for child in block.children:
            BlockTraverser.traverse_preorder(child, visitor)
    
    @staticmethod
    def traverse_postorder(block: Block, visitor: BlockVisitor) -> None:
        """Traverse tree in post-order (children before parent)."""
        for child in block.children:
            BlockTraverser.traverse_postorder(child, visitor)
        visitor.visit_block(block)
    
    @staticmethod
    def traverse_level_order(roots: List[Block], visitor: BlockVisitor) -> None:
        """Traverse tree in level order (breadth-first)."""
        queue = list(roots)
        while queue:
            block = queue.pop(0)
            visitor.visit_block(block)
            queue.extend(block.children)


# ============================================================================
# Parser Implementation
# ============================================================================

class BlockMDParser:
    """
    AST-based parser for BlockMD format.
    
    Architecture:
    - Tokenizer: Identifies headers, properties, and content
    - AST Builder: Constructs Block tree with parent-child relationships
    - Property Resolver: Handles inheritance with `key*` syntax
    - Location Tracker: Records source positions for error reporting
    """
    
    def __init__(self):
        # Regex patterns for token recognition
        self.header_pattern = re.compile(r'^(#+)\s+(.*)')
        self.property_pattern = re.compile(r'^`([\w-]+)(\*)?`:\s*(.*)$')
        self.code_fence_pattern = re.compile(r'^```')
        
    def parse(self, text: str) -> List[Block]:
        """
        Parse BlockMD text into AST.
        
        Returns:
            List of root Block nodes
        """
        lines = text.splitlines()
        
        roots: List[Block] = []
        stack: List[Block] = []
        
        current_block: Optional[Block] = None
        parsing_properties = False
        in_code_block = False
        line_num = 0

        for line_num, line in enumerate(lines, start=1):
            # Track code block state
            if self.code_fence_pattern.match(line.strip()):
                in_code_block = not in_code_block
            
            # Inside code blocks, everything is content
            if in_code_block:
                if current_block:
                    current_block.content.append(line)
                    parsing_properties = False
                continue

            # Try to match header
            header_match = self.header_pattern.match(line)
            
            if header_match:
                # Create new block from header
                level = len(header_match.group(1))
                title = header_match.group(2).strip()
                new_block = Block(
                    title=title,
                    level=level,
                    location=SourceLocation(line_start=line_num, line_end=line_num)
                )
                
                # Resolve parent-child relationship using stack
                while stack and stack[-1].level >= level:
                    completed_block = stack.pop()
                    if completed_block.location:
                        completed_block.location.line_end = line_num - 1
                
                # Set parent relationship
                if stack:
                    parent_block = stack[-1]
                    parent_block.children.append(new_block)
                    new_block.parent = parent_block
                else:
                    # No parent - this is a root block
                    roots.append(new_block)
                
                # Update state
                stack.append(new_block)
                current_block = new_block
                parsing_properties = True
                continue
                
            # Process properties or content
            if current_block:
                if parsing_properties:
                    prop_match = self.property_pattern.match(line)
                    if prop_match:
                        # Parse property
                        key = prop_match.group(1)
                        inherit = bool(prop_match.group(2))
                        value = prop_match.group(3).strip()
                        
                        # Handle property inheritance - traverse up ancestor chain
                        if inherit:
                            ancestor = current_block.parent
                            while ancestor:
                                parent_value = ancestor.properties.get(key)
                                if parent_value:
                                    value = f"{parent_value}/{value}"
                                    break
                                ancestor = ancestor.parent
                        
                        current_block.properties[key] = value
                        continue
                    elif line.strip() == "":
                        # Empty line between properties and content
                        continue
                    else:
                        # Non-property line - switch to content mode
                        parsing_properties = False
                
                # Add to content
                if not parsing_properties:
                    current_block.content.append(line)
        
        # Finalize location info for remaining blocks
        for block in stack:
            if block.location:
                block.location.line_end = line_num
        
        return roots
    
    def parse_file(self, filepath: str) -> List[Block]:
        """Parse BlockMD file."""
        with open(filepath, 'r', encoding='utf-8') as f:
            return self.parse(f.read())


def parse_blockmd(text: str) -> List[Block]:
    """
    Convenience function to parse BlockMD text.
    
    Args:
        text: BlockMD formatted text
        
    Returns:
        List of root Block nodes
    """
    parser = BlockMDParser()
    return parser.parse(text)
