from __future__ import annotations

from collections.abc import Iterable

from mypy_extensions import trait

from mypy.types import (
    AnyType,
    CallableArgument,
    CallableType,
    DeletedType,
    EllipsisType,
    ErasedType,
    Instance,
    LiteralType,
    NoneType,
    Overloaded,
    Parameters,
    ParamSpecType,
    PartialType,
    PlaceholderType,
    RawExpressionType,
    SyntheticTypeVisitor,
    TupleType,
    Type,
    TypeAliasType,
    TypedDictType,
    TypeList,
    TypeType,
    TypeVarTupleType,
    TypeVarType,
    UnboundType,
    UninhabitedType,
    UnionType,
    UnpackType,
)


@trait
class TypeTraverserVisitor(SyntheticTypeVisitor[None]):
    """Visitor that traverses all components of a type"""

    # Atomic types

    def visit_any(self, t: AnyType, /) -> None:
        pass

    def visit_uninhabited_type(self, t: UninhabitedType, /) -> None:
        pass

    def visit_none_type(self, t: NoneType, /) -> None:
        pass

    def visit_erased_type(self, t: ErasedType, /) -> None:
        pass

    def visit_deleted_type(self, t: DeletedType, /) -> None:
        pass

    def visit_type_var(self, t: TypeVarType, /) -> None:
        # Note that type variable values and upper bound aren't treated as
        # components, since they are components of the type variable
        # definition. We want to traverse everything just once.
        t.default.accept(self)

    def visit_param_spec(self, t: ParamSpecType, /) -> None:
        # TODO: do we need to traverse prefix here?
        t.default.accept(self)

    def visit_parameters(self, t: Parameters, /) -> None:
        self.traverse_type_list(t.arg_types)

    def visit_type_var_tuple(self, t: TypeVarTupleType, /) -> None:
        t.default.accept(self)

    def visit_literal_type(self, t: LiteralType, /) -> None:
        t.fallback.accept(self)

    # Composite types

    def visit_instance(self, t: Instance, /) -> None:
        self.traverse_type_tuple(t.args)

    def visit_callable_type(self, t: CallableType, /) -> None:
        # FIX generics
        self.traverse_type_list(t.arg_types)
        t.ret_type.accept(self)
        t.fallback.accept(self)

        if t.type_guard is not None:
            t.type_guard.accept(self)

        if t.type_is is not None:
            t.type_is.accept(self)

    def visit_tuple_type(self, t: TupleType, /) -> None:
        self.traverse_type_list(t.items)
        t.partial_fallback.accept(self)

    def visit_typeddict_type(self, t: TypedDictType, /) -> None:
        self.traverse_types(t.items.values())
        t.fallback.accept(self)

    def visit_union_type(self, t: UnionType, /) -> None:
        self.traverse_type_list(t.items)

    def visit_overloaded(self, t: Overloaded, /) -> None:
        self.traverse_types(t.items)

    def visit_type_type(self, t: TypeType, /) -> None:
        t.item.accept(self)

    # Special types (not real types)

    def visit_callable_argument(self, t: CallableArgument, /) -> None:
        t.typ.accept(self)

    def visit_unbound_type(self, t: UnboundType, /) -> None:
        self.traverse_type_tuple(t.args)

    def visit_type_list(self, t: TypeList, /) -> None:
        self.traverse_type_list(t.items)

    def visit_ellipsis_type(self, t: EllipsisType, /) -> None:
        pass

    def visit_placeholder_type(self, t: PlaceholderType, /) -> None:
        self.traverse_type_list(t.args)

    def visit_partial_type(self, t: PartialType, /) -> None:
        pass

    def visit_raw_expression_type(self, t: RawExpressionType, /) -> None:
        pass

    def visit_type_alias_type(self, t: TypeAliasType, /) -> None:
        # TODO: sometimes we want to traverse target as well
        # We need to find a way to indicate explicitly the intent,
        # maybe make this method abstract (like for TypeTranslator)?
        self.traverse_type_list(t.args)

    def visit_unpack_type(self, t: UnpackType, /) -> None:
        t.type.accept(self)

    # Helpers

    def traverse_types(self, types: Iterable[Type], /) -> None:
        for typ in types:
            typ.accept(self)

    def traverse_type_list(self, types: list[Type], /) -> None:
        # Micro-optimization: Specialized for lists
        for typ in types:
            typ.accept(self)

    def traverse_type_tuple(self, types: tuple[Type, ...], /) -> None:
        # Micro-optimization: Specialized for tuples
        for typ in types:
            typ.accept(self)
