# ==============================================================================
# NODE CLASSES - These represent different types of elements in our tree
# ==============================================================================

class PawserNode:
    """Represents a tag element (like h1, ol, li, etc.)"""
    def __init__(self, typeName, attrs=None):
        self.type = typeName           # The tag name (e.g., "h1", "ol", "li")
        self.attrs = attrs or {}       # Attributes for the tag (currently unused)
        self.children = []             # Child nodes under this tag


class PawserTextNode:
    """Represents plain text content"""
    def __init__(self, content):
        self.type = "text"             # Always "text" for text nodes
        self.content = content         # The actual text
        self.children = []             # Text nodes can have children too


# ==============================================================================
# TAG PARSING FUNCTIONS - These convert lines of text into tree nodes
# ==============================================================================

def parseGeneralTag(line, lineNo):
    """
    Parse standard tags like: #h1 My Title, #p Some text, #ol, etc.
    
    These tags can have optional text content after the tag name.
    Example: "#h1 Welcome" becomes an h1 node with "Welcome" as text child
    """
    # Remove the # and split into tag name and rest of line
    lineWithoutHash = line[1:]                    # "#h1 Welcome" -> "h1 Welcome"
    parts = lineWithoutHash.split(" ", 1)         # Split at first space
    
    tagName = parts[0]                            # "h1"
    textContent = parts[1] if len(parts) > 1 else None  # "Welcome" or None
    
    # Create the tag node
    node = PawserNode(tagName)
    
    # If there's text content, add it as a child text node
    if textContent:
        node.children.append(PawserTextNode(textContent))
    
    return node


def parseSpecialTag_li(line, lineNo):
    """
    Parse #li tag - a simple list item marker with no text
    
    Example: "#li" becomes just an li node (children added later)
    """
    node = PawserNode("li")
    return node


def parseSpecialTag_it(line, lineNo):
    """
    Parse #it tag - a list item with text content
    
    Example: "#it buy eggs" becomes an it node with "buy eggs" as text child
    """
    # Remove the # to get the tag and content
    lineWithoutHash = line[1:]                    # "#it buy eggs" -> "it buy eggs"
    
    # Split into tag name and text content
    parts = lineWithoutHash.split(" ", 1)         # ["it", "buy eggs"]
    tagName = parts[0]                            # "it"
    textContent = parts[1] if len(parts) > 1 else None  # "buy eggs" or None
    
    # Create the item node
    node = PawserNode("it")
    
    # Add text as a child if there is any
    if textContent:
        node.children.append(PawserTextNode(textContent))
    
    return node


def parseSpecialTag_ol(line, lineNo):
    """
    Parse #ol tag - an open link with URL and link text
    
    Example: "#ol https://website.com Open Link!" 
    Creates: ol -> URL_node -> "Open Link!" (text)
    """
    # Remove the # to get the tag and content
    lineWithoutHash = line[1:]                    # "#ol https://..." -> "ol https://..."
    
    # Split into tag name and the rest
    parts = lineWithoutHash.split(" ", 1)         # ["ol", "https://website.com Open Link!"]
    tagName = parts[0]                            # "ol"
    restOfLine = parts[1] if len(parts) > 1 else None
    
    # Create the ol node
    node = PawserNode("ol")
    
    # If there's content, split into URL and link text
    if restOfLine:
        # Split the URL from the link text
        urlParts = restOfLine.split(" ", 1)       # ["https://website.com", "Open Link!"]
        url = urlParts[0]                          # "https://website.com"
        linkText = urlParts[1] if len(urlParts) > 1 else None  # "Open Link!" or None
        
        # Create URL node as child of ol
        urlNode = PawserNode(url)
        node.children.append(urlNode)
        
        # Add link text as child of URL node
        if linkText:
            urlNode.children.append(PawserTextNode(linkText))
    
    return node


# ==============================================================================
# MAIN PARSING FUNCTION - Reads the file and builds the tree
# ==============================================================================

def parsePawml(filePath):
    """
    Main function to parse a Pawml file into a tree structure
    
    Returns: Root PawserNode representing the entire document
    Raises: ValueError if file format is invalid
    """
    # Read all lines from the file
    with open(filePath, "r", encoding="utf-8") as f:
        lines = [line.rstrip("\n") for line in f]
    
    # --- Step 1: Validate file structure ---
    # Find first non-empty line - it must be "#pawml"
    firstLine = next((line.strip() for line in lines if line.strip()), None)
    if firstLine != "#pawml":
        raise ValueError("Error: File must start with #pawml")
    
    # Find last non-empty line - it must also be "#pawml"
    lastLine = next((line.strip() for line in reversed(lines) if line.strip()), None)
    if lastLine != "#pawml":
        raise ValueError("Error: File must end with #pawml")
    
    # --- Step 2: Set up the root node and parsing stack ---
    root = PawserNode("pawml")
    # Stack tracks where we are in the tree: [(node, indentation_level), ...]
    stack = [(root, -1)]
    
    # Define which tags we support
    generalTags = ["h1", "h2", "h3", "p"]
    specialTags = {
        "li": parseSpecialTag_li,
        "it": parseSpecialTag_it,
        "ol": parseSpecialTag_ol
    }
    
    # --- Step 3: Process each line (skip first and last #pawml lines) ---
    for lineNo, line in enumerate(lines[1:-1], start=2):
        # Skip empty lines
        if not line.strip():
            continue
        
        # Count how many tabs indent this line
        indent = 0
        while line.startswith("\t"):
            indent += 1
            line = line[1:]  # Remove the tab
        
        # Stop if we hit closing #pawml early
        if line == "#pawml":
            break
        
        # --- Parse different types of content ---
        
        # METADATA: Lines like "#mdata title="My Page""
        if line.startswith("#mdata"):
            try:
                # Extract the key=value part
                metadataContent = line[6:].strip()        # Remove "#mdata"
                parts = metadataContent.split("=", 1)     # Split at = sign
                
                if len(parts) != 2:
                    raise ValueError
                
                key = parts[0].strip()                    # e.g., "title"
                value = parts[1].strip().strip('"')       # e.g., "My Page"
                
                if not value:
                    raise ValueError
                
                # Build metadata node structure: mdata -> key -> text
                node = PawserNode("mdata")
                node.children.append(PawserNode(key))
                node.children[-1].children.append(PawserTextNode(value))
                
            except:
                raise ValueError(f"Error: Invalid mdata on line {lineNo}: '{line}'")
        
        # TAGS: Lines starting with # (like #h1, #li, #it)
        elif line.startswith("#"):
            # Extract the tag name
            tagName = line[1:].split(" ", 1)[0]
            
            # Use the appropriate parser
            if tagName in generalTags:
                node = parseGeneralTag(line, lineNo)
            elif tagName in specialTags:
                node = specialTags[tagName](line, lineNo)
            else:
                raise ValueError(f"Error: Unknown tag '{tagName}' on line {lineNo}")
        
        # PLAIN TEXT: Any line not starting with #
        else:
            node = PawserTextNode(line)
        
        # --- Step 4: Add node to tree at correct position ---
        # Pop stack until we find the right parent based on indentation
        while stack and stack[-1][1] >= indent:
            stack.pop()
        
        if not stack:
            raise ValueError(f"Error: Invalid indentation on line {lineNo}")
        
        # Add this node as a child of the current parent
        parentNode = stack[-1][0]
        parentNode.children.append(node)
        
        # Push this node onto the stack for future children
        stack.append((node, indent))
    
    return root


# ==============================================================================
# UTILITY FUNCTIONS - For displaying and using the parsed tree
# ==============================================================================

def printTree(node, indent=0):
    """
    Print the tree structure in a readable format
    
    Shows the hierarchy with indentation
    """
    indentSpace = "    " * indent
    
    # Text nodes display their content in quotes
    if node.type == "text":
        print(indentSpace + f'"{node.content}"')
        return
    
    # Regular nodes display their type and attributes
    attrs = f" {node.attrs}" if node.attrs else ""
    print(indentSpace + node.type + attrs)
    
    # Recursively print all children
    for child in node.children:
        printTree(child, indent + 1)


def pawml2domtree(filePath):
    """
    Main entry point - parse a Pawml file and return the tree
    
    Returns: Root node if successful, None if there was an error
    """
    try:
        return parsePawml(filePath)
    except Exception as e:
        print(e)
        return None