from collections.abc import Callable

from .Terminal.color import foreground
from ._check_params_decorator import check_params


RESET = foreground.RESET.value
numberColor = RESET + foreground.YELLOW.value
boolColor = RESET + foreground.LBLUE.value
stringColor = RESET + foreground.LGREEN.value
quotesColor = RESET + foreground.BOLD.value + foreground.CYAN.value

type Iterable = dict | list | tuple | set


def _f(x: object):
    if isinstance(x, str):
        quotes = '"' if "\n" not in x else '"""'
        return (f"{quotesColor}{quotes}{stringColor}{x.replace("\"", "\\\"")}"
                f"{quotesColor}{quotes}{RESET}")
    elif isinstance(x, (bool, type(None))):
        return f"{boolColor}{x}{RESET}"
    elif isinstance(x, (int, float)):
        return f"{numberColor}{x}{RESET}"
    return x


def _dictOut(_dict: dict, indentationLevel: int, indentationString: str) -> str:
    indent = indentationString * indentationLevel
    output = ""
    for k, v in _dict.items():
        if isinstance(v, (dict, list, tuple, set)):
            output += f"{indent}{_f(k)}: "
            output += _iterableOut(
                v, indentationLevel, indentationString,
                indentBefore=False
            )
        else:
            output += f"{indent}{_f(k)}: {_f(v)}\n"
    return output


def _listOut(_list: list, indentationLevel: int, indentationString: str) -> str:
    indent = indentationString * indentationLevel
    output = ""
    for item in _list:
        if isinstance(item, (dict, list, tuple, set)):
            output += _iterableOut(
                item, indentationLevel, indentationString
            )
        else:
            output += f"{indent}{_f(item)}\n"
    return output


def _tupleOut(_tuple: tuple, indentationLevel: int, indentationString: str) -> str:
    return _listOut(
        list(_tuple), indentationLevel, indentationString
    )


def _setOut(_set: set, indentationLevel: int, indentationString: str) -> str:
    return _listOut(
        list(_set), indentationLevel, indentationString
    )


def _callFunc(
        brackets: str,
        func: Callable[..., str],
        iterable: Iterable,
        indentationLevel: int,
        indentationString: str,
        indentBefore: bool) -> str:
    indent = indentationString * indentationLevel
    return (f"{indent if indentBefore else ""}{brackets[0]}\n"
            f"{func(iterable, indentationLevel + 1, indentationString)}{indent}{brackets[1]}\n")


def _iterableOut(iterable: Iterable, indentationLevel: int, indentationString: str, *,
                 indentBefore: bool = True) -> str:
    output = ""
    if isinstance(iterable, dict):
        output += _callFunc(
            "{}", _dictOut, iterable, indentationLevel,
            indentationString, indentBefore
        )
    elif isinstance(iterable, list):
        output += _callFunc(
            "[]", _listOut, iterable, indentationLevel,
            indentationString, indentBefore
        )
    elif isinstance(iterable, tuple):
        output += _callFunc(
            "()", _tupleOut, iterable, indentationLevel,
            indentationString, indentBefore
        )
    elif isinstance(iterable, set):
        output += _callFunc(
            "{}", _setOut, iterable, indentationLevel,
            indentationString, indentBefore
        )
    else:
        raise ValueError(
            f"Invalid value given for 'iterable'!\nReceived type '{type(iterable)}', expected one "
            f"of the following:\n[dict, list, tuple, set]")
    return output


@check_params
def repr_iter(
        iterable: Iterable, *,
        indentationLevel: int = 0,
        indentationString: str = "  "
) -> str:
    r"""
    Turns iterables into a formatted string for printing.
    :param iterable:
    :param indentationLevel:
    :param indentationString:
    :return: The representation of the given ``iterable``
    """

    try:
        output = _iterableOut(
            iterable,
            indentationLevel,
            indentationString
        )
        return output
    except RecursionError as e:
        return ""
