import textwrap
from collections import Counter, defaultdict
from collections.abc import Callable, Hashable, Iterable
from pathlib import Path

from .color import color, nullcolor
from .fp import identity

__all__ = ("objtree", "print_tree", "wrap_line")


def freq(
    things: Iterable[Hashable],
    min: int = 1,
    max: int | None = None,
    sep: str = "  ",
    use_color: bool = True,
) -> None:
    c = Counter(things)
    magenta = (color if use_color else nullcolor).magenta
    max_ = max or c.total()
    places = len(str(max_))
    for thing, count in c.most_common():
        if min <= count <= max_:
            print(f"{magenta(str(count)):>{places}}{sep}{thing!s}")


def print_tree(strings):
    def tree() -> defaultdict:
        return defaultdict(tree)

    root = tree()

    for _string in strings:
        parts = _string.split(".")
        current_level = root
        for part in parts:
            current_level = current_level[part]

    def print_subtree(node, prefix=""):
        children = list(node.keys())
        for i, child in enumerate(children):
            is_last = i == len(children) - 1
            if is_last:
                print(prefix + "└─ " + child)
                new_prefix = prefix + "   "
            else:
                print(prefix + "├─ " + child)
                new_prefix = prefix + "│  "
            print_subtree(node[child], new_prefix)

    print_subtree(root)


def wrap_line(line: str, length: int, formatter: Callable[[str], str] = identity) -> str:
    wrapped = textwrap.wrap(line, width=length)
    return "\n".join(formatter(line) for line in wrapped)


_LEAVES: tuple[type, ...] = (str, int, float, bool, type(None), bytes, Path)


def build_objtree(
    obj,
    name=None,
    max_depth: int = 4,
    indent: str = "",
    _depth: int = 0,
    _seen: set[int] | None = None,
    _lines: list[str] | None = None,
) -> list[str]:
    if _seen is None:
        _seen = set()
    if _lines is None:
        _lines = []

    name = name or getattr(obj, "__name__", None) or f"<{type(obj).__name__}>"

    leader = "    " if _depth > 0 else ""  # formerly " └─ "
    type_name = color.blue(type(obj).__name__)
    _lines.append(f"{indent}{leader}{color.magenta(name)} ({type_name})")

    obj_id = id(obj)
    if _depth >= max_depth or obj_id in _seen or isinstance(obj, _LEAVES):
        return _lines

    _seen.add(obj_id)

    # Get attributes, ignoring dunders and private-ish members
    attrs = sorted([a for a in dir(obj) if not a.startswith("__")])

    for attr in attrs:
        try:
            val = getattr(obj, attr)
            build_objtree(
                val,
                name=attr,
                max_depth=max_depth,
                indent=indent + ("    " if _depth > 0 else ""),
                _depth=_depth + 1,
                _seen=_seen,
                _lines=_lines,
            )
        except Exception:  # pragma: no cover
            continue

    return _lines


# def build_objtree(
#     obj,
#     name=None,
#     max_depth: int = 4,
#     indent: str = "",
#     leader: str = "    ",
#     _depth: int = 0,
#     _seen: set[int] | None = None,
#     _lines: list[str] | None = None,
# ) -> list[str]:
#     if _seen is None:
#         _seen = set()
#     if _lines is None:
#         _lines = []

#     _leader = leader if _depth else ""
#     if not name:
#         if hasattr(obj, "__name"):
#             name = obj.__name__
#         elif hasattr(obj, "__repr__"):
#             name = repr(obj)
#         else:
#             name = "<root>"

#     type_name = color.blue(type(obj).__name__)
#     _lines.append(f"{indent}{_leader}{color.magenta(name)} ({type_name})")

#     obj_id = id(obj)
#     if (
#         _depth >= max_depth
#         or obj_id in _seen
#         or isinstance(obj, (str, int, float, bool, type(None)))
#     ):
#         return _lines

#     _seen.add(obj_id)
#     attrs = [a for a in dir(obj) if not a.startswith("__")]

#     for attr in attrs:
#         try:
#             val = getattr(obj, attr)
#             build_objtree(
#                 val,
#                 name=attr,
#                 max_depth=max_depth,
#                 indent=indent + "    ",
#                 leader=_leader,
#                 _depth=_depth + 1,
#                 _seen=_seen,
#                 _lines=_lines,
#             )
#         except Exception:
#             continue

#     return _lines


def objtree(ob: object) -> str:
    return "\n".join(build_objtree(ob))


def otree(ob: object) -> None:
    print(objtree(ob))  # pragma: no cover
