import os
from typing import Callable

from block import BlockType, block_to_block_type
from extract import extract_markdown_images, extract_markdown_links
from htmlnode import HtmlNode
from parentnode import ParentNode
from textnode import TextNode, TextType, text_node_to_html_node


def split_nodes_delimiter(
    old_nodes: list[TextNode], delimiter: str, text_type: TextType
) -> list[TextNode]:
    result = []

    for node in old_nodes:
        if node.text_type != TextType.PLAIN:
            result.append(node)
            continue

        parts = node.text.split(delimiter)

        if len(parts) % 2 == 0:
            raise RuntimeError("No matching delimiter found")

        for i in range(len(parts)):
            current_text = parts[i]

            if i % 2 == 0:
                result.append(TextNode(current_text, TextType.PLAIN))
            else:
                result.append(TextNode(current_text, text_type))

    return result


def split_nodes_image(old_nodes: list[TextNode]) -> list[TextNode]:
    return split_nodes(
        old_nodes,
        extracter=extract_markdown_images,
        pattern_builder=lambda t: f"![{t[0]}]({t[1]})",
        node_creator=lambda t: TextNode(t[0], TextType.IMAGE, t[1]),
    )


def split_nodes_link(old_nodes: list[TextNode]) -> list[TextNode]:
    return split_nodes(
        old_nodes,
        extracter=extract_markdown_links,
        pattern_builder=lambda t: f"[{t[0]}]({t[1]})",
        node_creator=lambda t: TextNode(t[0], TextType.LINK, t[1]),
    )


def split_nodes(
    old_nodes: list[TextNode],
    extracter: Callable[[str], list[tuple[str, str]]],
    pattern_builder: Callable[[tuple[str, str]], str],
    node_creator: Callable[[tuple[str, str]], TextNode],
) -> list[TextNode]:
    result = []

    for node in old_nodes:
        if node.text_type != TextType.PLAIN:
            result.append(node)
            continue

        extracted = extracter(node.text)

        node_text = node.text

        for extract in extracted:
            pattern = pattern_builder(extract)
            parts = node_text.split(pattern, maxsplit=1)

            result.append(TextNode(parts[0], TextType.PLAIN))

            node = node_creator(extract)
            result.append(node)

            node_text = parts[1]

        if len(node_text) > 0:
            result.append(TextNode(node_text, TextType.PLAIN))

    return result


def text_to_text_node(text: str) -> list[TextNode]:
    old_nodes = [TextNode(text, TextType.PLAIN)]
    old_nodes = split_nodes_delimiter(old_nodes, "**", TextType.BOLD)
    old_nodes = split_nodes_delimiter(old_nodes, "_", TextType.ITALIC)
    old_nodes = split_nodes_delimiter(old_nodes, "`", TextType.CODE)
    old_nodes = split_nodes_image(old_nodes)
    old_nodes = split_nodes_link(old_nodes)
    return old_nodes


def markdown_to_blocks(markdown: str) -> list[str]:
    blocks = markdown.split(
        "\n\n",
    )
    trimmed = [block.strip() for block in blocks]
    non_empty = [block for block in trimmed if block != ""]
    return non_empty


def markdown_to_html_node(markdown: str) -> HtmlNode:
    children = []
    blocks = markdown_to_blocks(markdown)

    for block in blocks:
        block_type = block_to_block_type(block)

        match block_type:
            case BlockType.PARAGRAPH:
                no_newlines = block.replace(os.linesep, " ")
                paragraph_children = text_to_children(no_newlines)
                paragraph_node = ParentNode("p", children=paragraph_children)
                children.append(paragraph_node)
            case BlockType.HEADING:
                heading_number = 0

                for c in block:
                    if c != "#":
                        break

                    heading_number += 1

                heading_children = text_to_children(block.lstrip(f"{"#" * heading_number} "))
                heading_node = ParentNode(
                    f"h{heading_number}", children=heading_children
                )
                children.append(heading_node)
            case BlockType.CODE:
                removed = block.lstrip("```\n").rstrip("```")
                text_node = TextNode(removed, TextType.CODE)
                code_node = text_node_to_html_node(text_node)
                pre_children: list[HtmlNode] = [code_node]
                pre_node = ParentNode("pre", children=pre_children)
                children.append(pre_node)
            case BlockType.QUOTE:
                replaced_blocks = []

                lines = block.splitlines()
                
                for line in lines:
                    replaced = line.lstrip("> ").lstrip(">")
                    replaced_blocks.append(replaced)

                replaced_block = os.linesep.join(replaced_blocks)
                quote_children = text_to_children(replaced_block)
                block_node = ParentNode("blockquote", children=quote_children)
                children.append(block_node)
            case BlockType.ORDERED_LIST:
                ordered_list_children = []
                ordered_list_items = block.splitlines()

                for i in range(len(ordered_list_items)):
                    ordered_item = ordered_list_items[i]
                    ordered_item_children_nodes = text_to_children(ordered_item.lstrip(f"{i + 1}. "))
                    ordered_item_node = ParentNode(
                        "li", children=ordered_item_children_nodes
                    )
                    ordered_list_children.append(ordered_item_node)

                ordered_list_node = ParentNode("ol", children=ordered_list_children)
                children.append(ordered_list_node)
            case BlockType.UNORDERED_LIST:
                unordered_list_children = []
                unordered_list_items = block.splitlines()

                for unordered_item in unordered_list_items:
                    unordered_item_children_nodes = text_to_children(unordered_item.lstrip("- "))
                    unordered_item_node = ParentNode(
                        "li", children=unordered_item_children_nodes
                    )
                    unordered_list_children.append(unordered_item_node)

                unordered_list_node = ParentNode("ul", children=unordered_list_children)
                children.append(unordered_list_node)
            case _:
                raise NotImplemented("Unknown block type")

    return ParentNode("div", children=children)


def text_to_children(markdown: str) -> list[HtmlNode]:
    children = []

    text_nodes = text_to_text_node(markdown)

    for text_node in text_nodes:
        html_node = text_node_to_html_node(text_node)
        children.append(html_node)

    return children
