from typing import Any, Iterable, List, Optional, Sized

from graphql import Node


def _node_tree_recursive(
    obj: Any,
    *,
    indent: int = 0,
    ignored_keys: List,
) -> str:

    assert ignored_keys is not None

    results = []

    if hasattr(obj, "__slots__") or isinstance(obj, Node):

        results.append("  " * indent + f"{type(obj).__name__}")

        try:
            keys = sorted(obj.keys)
        except AttributeError:
            # If the object has no keys attribute, print its repr and return.
            results.append("  " * (indent + 1) + repr(obj))
        else:
            for key in keys:
                if key in ignored_keys:
                    continue
                attr_value = getattr(obj, key, None)
                results.append("  " * (indent + 1) + f"{key}:")
                if isinstance(attr_value, Iterable) and not isinstance(
                    attr_value, (str, bytes)
                ):
                    if isinstance(attr_value, Sized) and len(attr_value) == 0:
                        results.append(
                            "  " * (indent + 2) + f"empty {type(attr_value).__name__}"
                        )
                    else:
                        for item in attr_value:
                            results.append(
                                _node_tree_recursive(
                                    item,
                                    indent=indent + 2,
                                    ignored_keys=ignored_keys,
                                )
                            )
                else:
                    results.append(
                        _node_tree_recursive(
                            attr_value,
                            indent=indent + 2,
                            ignored_keys=ignored_keys,
                        )
                    )
    else:
        results.append("  " * indent + repr(obj))

    return "\n".join(results)


def node_tree(
    obj: Node,
    *,
    ignore_loc: bool = True,
    ignore_block: bool = True,
    ignored_keys: Optional[List] = None,
) -> str:
    """Method which returns a tree of Node elements as a String.

    Useful to debug deep DocumentNode instances created by gql or dsl_gql.

    NOTE: from gql version 3.6.0b4 the elements of each node are sorted to ignore
          small changes in graphql-core

    WARNING: the output of this method is not guaranteed and may change without notice.
    """

    assert isinstance(obj, Node)

    if ignored_keys is None:
        ignored_keys = []

    if ignore_loc:
        # We are ignoring loc attributes by default
        ignored_keys.append("loc")

    if ignore_block:
        # We are ignoring block attributes by default (in StringValueNode)
        ignored_keys.append("block")

    # Ignore new field added in graphql-core 3.3.0a12 to keep output compatible
    ignored_keys.append("nullability_assertion")

    return _node_tree_recursive(obj, ignored_keys=ignored_keys)
