class HtmlElement:
    def get_content(self, tab_level:int = 0):
        raise NotImplementedError()
    
    def __str__(self) -> str:
        return self.get_content(0)

class HtmlTextNode(HtmlElement):
    def __init__(self, content:str) -> None:
        super().__init__()
        self.content = content

    def get_content(self, tab_level: int = 0):
        return f'{self.content}'
    
class HtmlNode(HtmlElement):
    def __init__(self, tag_name:str, children:list[HtmlElement] = None, attributes:dict[str, str] = None) -> None:
        super().__init__()
        self.tag_name = tag_name
        self.attributes = attributes or dict()
        self.children = children or []
    
    def with_attr(self, name:str, value:str) -> "HtmlNode":
        self.attributes[name] = value
        return self

    def get_content(self, tab_level: int = 0):
        attrs = ' '.join([f'{i}="{self.attributes[i]}"' for i in self.attributes])
        tabs = '\t'*tab_level
        result = f'\n{tabs}<{self.tag_name} {attrs}'
        if len(self.children) > 0:
            result += '>'
            for child in self.children:
                result += child.get_content(tab_level+1)
            result += f'{tabs}</{self.tag_name}>\n'
        else:
            result += '/>'
        return result
    
def Node(tag:str, *children:list[HtmlElement], **attrs:dict[str, str]) -> HtmlNode:
    return HtmlNode(tag, children, attrs)

def Text(content:str) -> HtmlTextNode:
    return HtmlTextNode(content)

def Card(title:str, text:str) -> HtmlTextNode:
    return Node(
        "div",
        Node(
            "div",
            Node(
                "h5",
                Text(title),
                 **{"class": "card-title"}   
            ),
            Node(
                "div",
                Text(text),
                 **{"class": "card-text"}
            ),
            **{"class": "card-body"}
        ),
        **{"class": "card"}
    )