from textnode import TextType, TextNode
from htmlnode import ParentNode, LeafNode
import re

# ====Markdown Parsing and Block Handling====

def markdown_to_blocks(markdown):
    lines = markdown.split('\n')
    blocks = []
    current_block = []
    for line in lines:
        stripped = line.strip()
        if stripped == "":
            if current_block == []:
                continue
            else:
                blocks.append("\n".join(current_block).strip())
                current_block = []
        else:
            current_block.append(line)
    if current_block != []:
        blocks.append("\n".join(current_block).strip())
        current_block = []
    return blocks 

def block_to_block_type(markdown):
    markdown = markdown.strip()  # Remove leading/trailing whitespace

    if markdown.startswith("#"):
        # Count the number of # characters
        num_hashes = len(markdown.split(" ", 1)[0])

        # Ensure the number of # is between 1 and 6 and followed by a space
        if 1 <= num_hashes <= 6 and markdown[num_hashes:].startswith(" "):
            return "heading"
        return "paragraph"  # Invalid heading (wrong number of # or no space after #)

    elif markdown.startswith("```"):
        if markdown.endswith("```") and len(markdown.strip("`").strip()) > 0:
            return "code"
        return "paragraph"  # Invalid code block (empty or not closed properly)

    elif markdown.startswith(">"):
        split_lines = markdown.split("\n")
        if all(line.startswith("> ") and line.strip("> ").strip() for line in split_lines):
            return "quote"
        return "paragraph"  # Invalid quote (empty or missing space after >)

    elif markdown.startswith(("*", "-")):
        split_lines = markdown.split("\n")
        startswith = "*" if markdown.startswith("*") else "-"
        if all(line.startswith(f"{startswith} ") and line.strip(f"{startswith} ").strip() for line in split_lines):
            return "unordered_list"
        return "paragraph"  # Invalid unordered list (empty or missing space)

    elif markdown.startswith("1.") or markdown.startswith("2.") or markdown.startswith("3.") or markdown.startswith("4.") or markdown.startswith("5.") or markdown.startswith("6.") or markdown.startswith("7.") or markdown.startswith("8.") or markdown.startswith("9."):
        split_lines = markdown.split("\n")
        list_counter = 1

        for line in split_lines:
            expected_prefix = f"{list_counter}. "
            if not line.startswith(expected_prefix) or not line[len(expected_prefix):].strip():
                return "paragraph"  # Invalid ordered list (incorrect numbering or empty item)
            list_counter += 1

        return "ordered_list"

    else:
        return "paragraph"

def markdown_to_html_node(markdown):
    blocks = markdown_to_blocks(markdown)
    for block in blocks:
        print(f"Processing block: {block[:50]}...")  # Print first 50 chars
        block_type = block_to_block_type(block)
        print(f"Block type: {block_type}")
    nodes = []
    for block in blocks:
        block_type = block_to_block_type(block)
        match block_type:
            case "paragraph":
                nodes.append(ParentNode("p",text_to_children(block)))
            case "heading":
                nodes.append(ParentNode(f"h{block.count("#")}",text_to_children(block.strip("#"*block.count("#")+" "))))
            case "quote":
                nodes.append(ParentNode("blockquote",text_to_children(block.strip("> "))))
            case "code":
                tnodes = text_to_textnodes(block.strip("```").strip("\n"))
                hnodes = []
                for node in tnodes:
                    hnodes.append(text_node_to_html_node(node))
                nodes.append(ParentNode("pre",[ParentNode("code",hnodes)]))
            case "ordered_list":
                items = block.split("\n")
                trimmed = []
                for item in items:
                    item = re.sub(r'^\d+\. ', '', item)
                    trimmed.append(ParentNode("li",text_to_children(item)))
                nodes.append(ParentNode("ol",trimmed))
            case "unordered_list":
                items = block.split("\n")
                trimmed = []
                for item in items:
                    item = item.lstrip("* ")
                    item = item.lstrip("- ")
                    trimmed.append(ParentNode("li",text_to_children(item)))
                nodes.append(ParentNode("ul",trimmed))
    return ParentNode("div",nodes)
# ====Markdown to HTML Helpers====
def text_to_children(text):
    text_nodes = text_to_textnodes(text)
    html_nodes = []
    for node in text_nodes:
        html_nodes.append(text_node_to_html_node(node))
    return html_nodes

def text_node_to_html_node(text_node):
    match text_node.text_type:
        case TextType.NORMAL_TEXT:
            return LeafNode(value=text_node.text)
        case TextType.BOLD_TEXT:
            return LeafNode(value=text_node.text, tag="strong")
        case TextType.ITALIC_TEXT:
            return LeafNode(value=text_node.text, tag="em")
        case TextType.CODE_TEXT:
            return LeafNode(value=text_node.text, tag="code")
        case TextType.LINK_TEXT:
            node = LeafNode(value=text_node.text, tag="a")
            node.props = {"href": text_node.url}
            return node
        case TextType.IMAGE_TEXT:
            node = LeafNode(value="", tag="img")
            node.props = {"src": text_node.url, "alt": text_node.text}
            return node
        case _:
            raise Exception("NOT_A_VALID_TEXT_TYPE")


# ====Node Splitting and Text Processing====

def split_nodes_delimiter(old_nodes, delimiter, text_type):
    new_nodes = []

    for node in old_nodes:
        # Skip nodes that are not NORMAL_TEXT
        if node.text_type != TextType.NORMAL_TEXT:
            new_nodes.append(node)
            continue

        # If no delimiter is found in the node's text
        if delimiter not in node.text:
            new_nodes.append(node)
            continue

        # Process text while delimiters exist in the string
        text = node.text
        while delimiter in text:
            # Find the first and second delimiters
            first_delim = text.find(delimiter)
            if first_delim > 0:
                prefix = text[:first_delim]  # Clean prefix from whitespace
                if prefix:
                    new_nodes.append(TextNode(prefix, TextType.NORMAL_TEXT))
            
            # Find the next delimiter
            last_delim = text.find(delimiter, first_delim + len(delimiter))
            if last_delim == -1:
                raise Exception("Invalid Markdown: only one delimiter")

            # Extract and append middle section, ensuring validity
            middle = text[first_delim + len(delimiter):last_delim].strip()
            if middle:  # Only add non-blank nodes for the styled text
                new_nodes.append(TextNode(middle, text_type))

            # Update text to the remaining suffix after the second delimiter
            text = text[last_delim + len(delimiter):]

        # Once all delimiters are processed, handle the remaining suffix
        suffix = text  # Ensure suffix is stripped
        if suffix:
            new_nodes.append(TextNode(suffix, TextType.NORMAL_TEXT))

    return new_nodes


def split_nodes_image(old_nodes):
    new_nodes = []
    for node in old_nodes:
        if node.text_type != TextType.NORMAL_TEXT:
            new_nodes.append(node)
            continue
        
        # Start with the node's full text
        text = node.text
        
        # Keep processing while we can find images
        while True:
            images = extract_markdown_images(text)
            if not images:
                # No more images in text
                if text:  # only append if not empty
                    new_nodes.append(TextNode(text, TextType.NORMAL_TEXT))
                break
            
            # Process the first image found
            image = images[0]
            text_parts = text.split(f"![{image[0]}]({image[1]})", 1)
            
            # Add prefix text if not empty
            if text_parts[0]:
                new_nodes.append(TextNode(text_parts[0], TextType.NORMAL_TEXT))
            
            # Add the image node
            new_nodes.append(TextNode(image[0], TextType.IMAGE_TEXT, image[1]))
            
            # Update text to remaining portion
            text = text_parts[1]
    
    return new_nodes


def split_nodes_link(old_nodes):
    new_nodes = []
    for node in old_nodes:
        if node.text_type != TextType.NORMAL_TEXT:
            new_nodes.append(node)
            continue
        
        # Start with the node's full text
        text = node.text
        
        # Keep processing while we can find images
        while True:
            links = extract_markdown_links(text)
            if not links:
                # No more images in text
                if text:  # only append if not empty
                    new_nodes.append(TextNode(text, TextType.NORMAL_TEXT))
                break
            
            link = links[0]
            text_parts = text.split(f"[{link[0]}]({link[1]})", 1)
            
            if text_parts[0]:
                new_nodes.append(TextNode(text_parts[0], TextType.NORMAL_TEXT))
            
            new_nodes.append(TextNode(link[0], TextType.LINK_TEXT, link[1]))
            
            text = text_parts[1]
    
    return new_nodes


# ====Text-to-Node Conversion====

def text_to_textnodes(text):
    nodes = [TextNode(text, TextType.NORMAL_TEXT)]

    nodes = split_nodes_delimiter(nodes, "**", TextType.BOLD_TEXT)
    nodes = split_nodes_delimiter(nodes, "*", TextType.ITALIC_TEXT)
    nodes = split_nodes_image(nodes)
    nodes = split_nodes_link(nodes)
    nodes = split_nodes_delimiter(nodes, "`", TextType.CODE_TEXT)

    # Remove any empty or whitespace-only nodes
    nodes = [node for node in nodes if node.text.strip()]
    return nodes


# ====Markdown Extraction Functions====

def extract_markdown_images(text):
    pattern = r"!\[(.*?)\]\((.*?\.(?:png|jpg|jpeg|gif|svg|webp|bmp|tiff|ico)[^)]*)\)"
    matches = re.findall(pattern, text)
    return matches


def extract_markdown_links(text):
    pattern = r"\[(.*?)\]\((.*?)\)"
    matches = re.findall(pattern, text)
    return matches

