from typing import Callable
import inspect


def rule_helper(func: Callable) -> Callable:
    """Decorator which marks a function as a "rule helper".

    Functions marked as rule helpers are allowed to be called by rules and other rule helpers
    and can `await Get/MultiGet`. The rule parser adds these functions' awaitables to the rule's
    awaitables.

    There are a few restrictions:
        1. Rule helpers must be "private". I.e. start with an underscore
        2. Rule hlpers must be `async`
        3. Rule helpers can't be rules
        4. Rule helpers must be accessed by attributes chained from a module variable (see below)

    To explain restriction 4, consider the following:
    ```
        from some_mod import helper_function, attribute

        ...

        some_instance = AClass()

        @rule
        async def my_rule(arg: RequestType) -> ReturnType
            await helper_function()  # OK
            await attribute.helper()  # OK (assuming `helper` is a @rule_helper)
            await attribute.otherattr.helper()  # OK (assuming `helper` is a @rule_helper)
            await some_instance.helper()  # OK (assuming `helper` is a @rule_helper)

            await AClass().helper()  # Not OK, won't collect awaitables from `helper`

            func_var = AClass()
            await func_var.helper()  # Not OK, won't collect awaitables from `helper`
            await arg.helper()  # Not OK, won't collect awaitables from `helper`
    ```
    """
    if not func.__name__.startswith("_"):
        raise ValueError("@rule_helpers must be private. I.e. start with an underscore.")

    if hasattr(func, "rule"):
        raise ValueError("Cannot use both @rule and @rule_helper.")

    if not inspect.iscoroutinefunction(func):
        raise ValueError("@rule_helpers must be async.")

    setattr(func, "rule_helper", func)
    return func