from functools import wraps
from time import perf_counter, sleep
from typing import Self


def as_call(args, kwargs):
    passed = ['self']
    for arg in args[1:]:
        passed.append(repr(arg))
    for key, value in kwargs.items():
        passed.append(f"key={value!r}")
    return "(" + ", ".join(passed) + ")"


def introspected_method(clsname, ctx, name, method):
    def _(*args, **kwargs):
        callstr = f"{clsname}.{name}{as_call(args, kwargs)}"
        print(f"{'|   ' * ctx['depth']}Calling {callstr}...")
        ctx["depth"] += 1
        before = perf_counter()
        result = method(*args, **kwargs)
        after = perf_counter()
        ctx["depth"] -= 1
        callstr = f"{clsname}.{name}{as_call(args, kwargs)}"
        print(
            f"{'|   ' * ctx['depth']}Returning from {callstr} after {after-before:.2f}s with {result}"
        )
        return result

    return _


class Introspected(type):
    def __new__(cls, name, bases, ns, **kwds):
        ctx = {"depth": 0}
        for key, value in ns.items():
            if callable(value) and key != "__repr__":
                ns[key] = introspected_method(name, ctx, key, value)
        return super().__new__(cls, name, bases, ns, **kwds)


class Point(metaclass=Introspected):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def dist(self, other: Self) -> float:
        return ((other.x - self.x) ** 2 + (other.y - self.y) ** 2) ** 0.5

    def __repr__(self):
        return f"Point(x={self.x!r}, y={self.y!r})"

    def modulus(self):
        return self.dist(Point(0, 0))

    def recursive_call(self, value):
        sleep(0.1)
        if value:
            return self.recursive_call(value - 1)
        else:
            return self.mean()


if __name__ == "__main__":
    p = Point(10, 10)
    print(p.modulus())
