from __future__ import annotations

import dataclasses
from collections.abc import Callable
from typing import Self, overload


@dataclasses.dataclass(frozen=True, slots=True, weakref_slot=True)
class Group[T]:
    factory: Callable[[], T]
    name: str = dataclasses.field(init=False)

    @overload
    def __get__(self, instance: None, owner: type | None = None) -> Self: ...
    @overload
    def __get__(self, instance: object, owner: type | None = None) -> T: ...
    def __get__(self, instance: object | None, owner: type | None = None) -> Self | T:
        if instance is None:
            return self
        if self.name not in instance.__dict__:
            instance.__dict__[self.name] = self.factory()
        return instance.__dict__[self.name]

    def __set_name__(self, owner: type, name: str) -> None:
        object.__setattr__(self, "name", name)


@overload
def group[T](factory: type[T]) -> Group[T]: ...
@overload
def group[T](factory: Callable[[], T]) -> Group[T]: ...
def group[T](factory: Callable[[], T]) -> Group[T]:
    return Group(factory)
