from typing import Optional
from typing import List
from typing import Tuple


def dict_flatten(
    attrs: dict,
    delim: Optional[str] = None,
    indent_char: Optional[str] = None,
    bullet_char: Optional[str] = None,
) -> List[Tuple[str, str, str]]:
    """Recursively flattens dictionary to its atomic elements.

    Flattens a dictionary to its atomic state and performs parsing operations,
    separating each child key with a `delim` relative to its parent key.

    This flattening enables the parsing of a nested dictionary into a valid
    string of markdown containing correspondingly nested bullets.

    Args:
        attrs (dict):
            Dictionary of attributes; most likely the :attr:`attrs_parsed`
            from :class:`Statement`.
        delim (str):
            Delimiter to use for separating nested keys; defaults to '~';
        indent_char (str):
            Character to use for indents; defaults to a tab ('\t).
        bullet_char (str):
            Character to use for bullets/sub-bullets; defaults to '-'.

    Returns (List[Tuple[str, str, str]]):
        A list of tuples containing:
            1.  A string of the indentation to use; for 1st-level attributes,
                this will just be the `bullet_char`.
            2.  The fully stratified key, including parents; for 1st-level
                attributes this will mirror the original key that was provided.
            3.  The value of the associated key; this will always mirror the
                value that was provided.

    """
    flattened = list()
    delim = delim or "~"
    c = indent_char or "\t"
    bullet_char = bullet_char or "-"

    def recurse(t, parent_key=""):
        if isinstance(t, dict):
            for k, v in t.items():
                sub_key = f"{parent_key}{delim}{k}"
                recurse(v, sub_key if parent_key else k)
        else:
            depth = len(parent_key.split(delim)) - 1
            indent = f"{c * depth}{bullet_char}" if depth else ""
            flattened.append((indent, parent_key, t))

    recurse(attrs)

    return flattened