# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_core.ipynb.

# %% auto 0
__all__ = ['getr', 'qual', 'name', 'make', 'onto']

# %% ../nbs/02_core.ipynb 4
from functools import wraps
from typing import TypeGuard
from typing import Callable, Optional

# %% ../nbs/02_core.ipynb 5
from chck import iscall, isnone, notnone, isiter
from nlit import GET, __DOC__, __NAME__, __QUALNAME__, __MODULE__, __ANNOTATIONS__

# %% ../nbs/02_core.ipynb 6
from .atyp import R

# %% ../nbs/02_core.ipynb 8
def _get(
    obj: object, val: str, default = None, 
    check: bool = True, guard: Optional[Callable] = notnone, retself: bool = False
):
    if all((check, iscall(guard))) and guard(val): return val
    if all((retself, check, iscall(guard))) and guard(obj): return obj
    return default

# %% ../nbs/02_core.ipynb 9
def getr(
    obj: object, attr: str, 
    check: bool = True, guard: Optional[Callable] = notnone, 
    default = None, otype: Optional[Callable] = None, retself: bool = False
):
    '''Get a specified attribute from an object with an optional value check before defaulting.

    This function retrievesan object's attribute with the option to validate 
    the attribute's value using a guard function. The guard function can be any callable 
    that takes a single argument and returns a boolean. If the attribute is not present, 
    a default value can be considered for the guard check.

    Parameters
    ----------
    obj : object
        The object to check for the attribute.

    attr : str
        The name of the attribute to check for.

    check : bool, optional
        If True, the function performs a guard check on the attribute's value, by default True.

    guard : Optional[Callable], default: `notnone`.
        The guard function used to validate the attribute's value, by default notnone.

    default : Any, optional
        The default value to use if the attribute is not found, by default None.

    otype : Optional[Callable], optional
        The type of the object to check for, by default None.
        If specified, the function will check if the object is of the specified type 
        before checking for the attribute. If this fails, the function will return `default`.

    retself : bool, optional
        If True, the function will return the object if the guard check passes, by default False.

    Returns
    -------
    attribute : Any
        Returns the attribute if the object has `attr` (i.e. `obj.attr` and passes the guard check if `check` is True),
            otherwise `default`.
        

    See Also
    --------
    chck.notnone : A guard function that checks if a value is not None i.e. `val is not None`.
    '''
    if all((check, notnone(otype), iscall(otype))) and not otype(obj): return default
    try: 
        val = getattr(obj, attr, default)
        if isnone(val): val = default        
    except: val = default
    return _get(obj, val, default, check, guard, retself)
    if all((check, iscall(guard))) and guard(val): return val
    if all((retself, check, iscall(guard))) and guard(obj): return obj
    return default # if not check else fallback

# %% ../nbs/02_core.ipynb 10
def qual(attr: str, base: str = GET, prefix: str = '', suffix: str = '') -> str:
    '''Produce a '__qualname__' for a function based on an attribute name.'''
    if isnone(prefix): prefix = ''
    if isnone(suffix): suffix = ''
    return f'{prefix}{base}{attr}{suffix}'
    
def name(fn: Callable, attr: str, base: str = GET, prefix: str = None, suffix: str = None) -> Callable:
    for a in (__NAME__, __QUALNAME__): setattr(fn, a, qual(attr, base, prefix, suffix))
    return fn

# %% ../nbs/02_core.ipynb 11
def make(
    attr: str, check: bool = True, guard: Optional[Callable] = notnone, default = None, 
    otype: Optional[Callable] = None, retself: bool = False
) -> Callable[[R], R]:
    '''Create a decorator for generating attribute-checking functions.

    This decorator factory returns a decorator that, when applied to a function,
    creates a new function which retrieves a specified attribute from object has a specified attribute (named `attr`).
    Additionally, it can validate the attribute's value using a guard function `guard`.
    The decorator updates the original function's docstring to include the new parameters.

    Parameters
    ----------
    attr : str
        The name of the attribute to check for in the decorated function.

    check : bool, default: True
        If True, the decorated function performs a guard check on the attribute's value.

    guard : Optional[Callable], default: notnone
        The guard function used to validate the attribute's value in the decorated function. 
        Defaults to `notnone`, which checks that the value is not None.

    default : Any, optional
        The default value to use if the attribute is not found in the decorated function.

    otype : Optional[Callable], optional
        The type of the object to check for, by default None.
        If specified, the function will check if the object is of the specified type 
        before checking for the attribute. If this fails, the function will return `default`.

    retself : bool, optional
        If True, the function will return the object if the guard check passes, by default False.

    Returns
    -------
    Callable[[R], R]
        A decorator that, when applied to a function, creates a new attribute-retrieving function.
        This new function includes additional parameters and updated docstring.    
    '''
    def deco(fn: Callable[..., TypeGuard[object]]):
        fndoc = getattr(fn, __DOC__, None)
        if isnone(fndoc): fndoc = f'Get {attr!r} i.e. `obj.{attr}` from an object.'

        doc = f'''
            {fndoc}

            Parameters
            ----------
            obj : object
                The object to get {attr!r} from i.e. `obj.{attr}`.
                
            check : bool, default: {check}
                If True, the function performs a typeguard check on the `obj.{attr}` value.
        
            guard : Optional[Callable], default: `notnone`.
                The guard function used to validate `obj.{attr}`'s value if `check == True`.
        
            default : Any, optional
                The default value to use if `obj` does not have {attr!r}.

            otype : Optional[Callable], default: {otype}
                The type of the object to check for, by default None.
                If specified, the function will check if the object is of the specified type 
                before checking for the attribute. If this fails, the function will return `default`.

            retself : bool, default: {retself}
                If True, the function will return the object if the guard check passes, by default False.
        
            Returns
            -------
            attribute : Any
                Returns the attribute if the object has {attr!r} (i.e. `obj.{attr}` and passes the guard check if `check` is True),
                otherwise `{default}`.
        
            See Also
            --------
            chck.notnone : A guard function that checks if a value is not None i.e. `val is not None`.
            '''
        
        @wraps(fn, assigned=(__MODULE__, __NAME__, __QUALNAME__, ))
        def wrap(
            obj: object, check: bool = check, guard: Optional[Callable] = guard, default = default, otype = otype,
            retself: bool = retself
        ) -> TypeGuard[object]:
            return getr(obj, attr, check=check, guard=guard, default=default, otype=otype, retself=retself)
        wrap.__doc__ = doc
        wrap = name(wrap, attr)        
        return wrap
    return deco

# %% ../nbs/02_core.ipynb 12
def onto(
    getter,    
    check = True, 
    guard: Optional[Callable] = notnone, 
    default = None, 
    otype: Optional[Callable] = None, 
    retself: bool = False
): 
    def deco(fn: Callable[..., TypeGuard[object]]):
        @wraps(fn, assigned=(__MODULE__, __NAME__, __QUALNAME__, ))
        def wrap(obj: object, *args, **kwargs) -> TypeGuard[object]:
            val = getter(obj, *args, **kwargs)
            if all((check, notnone(otype), iscall(otype))) and not otype(val): 
                val = obj
            res = fn(val, *args, **kwargs)
            res = _get(val, res, default, check, guard, retself)
            eqs = res == default
            alltrue = isiter(eqs) and all(eqs)
            onetrue = not isiter(eqs) and eqs
            if (alltrue) or (onetrue):
            # if res == default:
                res = fn(obj, *args, **kwargs)
                res = _get(obj, res, default, check, guard, retself)
            return res
        return wrap
    return deco
