from typing import Any, Callable, overload, TypeVar

T = TypeVar("T", bound=object)
Filters = list[Callable[[Any], bool]] | None
Wrapped = Callable[[type[T]], type[T]]

@overload
def dynrepr(*args: str, filters: Filters | None = None) -> Wrapped: pass

@overload
def dynrepr(*args: type[T], filters: Filters | None = None) -> type[T]: pass

def dynrepr(*args: type[T] | str, filters: Filters | None = None) -> Wrapped:
    """Class decorator factory that auto-generates a simple `__repr__`

    Parameters
    ----------
    `*args` : `str`, optional
        the class attributes to include in the `__repr__`
            defaults to the `__slots__` or `__dict__`
    `filters` : `list[Callable[[Any], bool]]`, optional
        a list of callables that attrs get passed to determine
            whether to include them in the `__repr__` or not

    Example Usage
    -------------
    ```py
        @dynrepr
        class A: ...

        @dynrepr("x", "y")
        class B: ...

        @dynrepr(filters=[lambda v: v is not None])
        class C: ...
    ```"""
    # we want all attrs by default
    if filters is None: filters = [lambda _: True]
    def drepr(cls: type[T]) -> type[T]:
        if not isinstance(cls, type):
            # you shouldn't be wrapping things that aren't a class
            raise TypeError(f"Expected class not {type(cls).__name__!r}!")
        def __repr__(self) -> str:
            keys = (
                [a for a in args if isinstance(a, str)] or
                getattr(self, '__slots__', []) or
                getattr(self, '__dict__', [])
            )
            items = ", ".join([
                    f"{k}={v!r}"
                    for k in keys
                    if any(f(v := getattr(self, k, None)) for f in filters)
            ])
            return f"{type(self).__name__}({items})"
        __repr__.__qualname__ = f"{cls.__name__}.__repr__"
        cls.__repr__ = __repr__
        return cls
    if len(args) == 1 and (
        isinstance(args[0], type) or not isinstance(args[0], str)
    ): return drepr(args[0])
    return drepr

