# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_util.ipynb.

# %% auto 0
__all__ = ['optargname', 'args2strs', 'typescsv', 'typesusv', 'types2opts', 'guardtype', 'guardname', 'guarddocs', 'guardanns',
           'setguardattrs', 'classname', 'qualname', 'settypes']

# %% ../nbs/01_util.ipynb 6
import os, inspect
from functools import wraps

# %% ../nbs/01_util.ipynb 8
from typing import (Union, Callable, Optional, TypeGuard,)

# %% ../nbs/01_util.ipynb 10
#| export


# %% ../nbs/01_util.ipynb 12
#| export


# %% ../nbs/01_util.ipynb 14
from .type import (T, P, Types, Guard, GuardFunc, StrFactory, StrBool)

# %% ../nbs/01_util.ipynb 17
def optargname(arg: T) -> str:
    '''Gets the name of the (possibly `Optional`) argument.'''
    name = str(getattr(arg, '__name__', arg))
    if name == 'Optional' and len(args := getattr(arg, '__args__', None)) > 0:
        name = str(getattr(args[0], '__name__', args[0]))
    return name

def args2strs(*args) -> list[str]:
    '''Converts a sequence of arguments into a list of their type names as strings.'''
    astrs = [str(optargname(arg)) for arg in args or tuple()]
    return astrs

def typescsv(*types: Types) -> str:
    '''Generates a comma-separated string of type names.'''
    return ', '.join(args2strs(*types))

def typesusv(*types: Types) -> str:
    '''Generates an underscore-separated string of type names.'''
    return '_'.join(args2strs(*types))

# %% ../nbs/01_util.ipynb 19
def types2opts(*types: Types) -> Types:
    '''Converts a tuple of types into a tuple of `Optional` types, if `None` is included.'''
    if None in types: 
        types = tuple(Optional[t] for t in types if t is not None) 
    return types


def guardtype(*types: Types) -> Guard:
    '''Creates a TypeGuard based on the provided types.
    
    Parameters
    ----------
    types : Types
        A tuple of types to be included in the TypeGuard.
    
    Returns
    -------
    Guard
        A TypeGuard that represents either None (if no types are provided),
        a single type (if one type is provided), or a Union of the provided types.
    '''
    types = types2opts(*types)
    if len(types) == 0:  return TypeGuard[None]
    elif len(types) == 1: return TypeGuard[types[0]]
    else: return TypeGuard[Union[types[0], *types[1:]]]


# %% ../nbs/01_util.ipynb 21
def guardname(*types: Types, negate: bool = False) -> str:
    '''Generates a name for a guard function based on the provided types and negation.'''
    isstr = 'is' if not negate else 'not'
    return f'{isstr}{typesusv(*types).lower().replace("_", "")}'

def guarddocs(*types: Types, negate: bool = False) -> str: 
    '''Generates a docstring for a guard function based on the provided types and negation.'''
    isstr = 'is' if not negate else 'is not'
    adj = 'one of: ' if len(types) > 1 else ''
    return f'Check if `x` {isstr} {adj}{typescsv(*types)}.'

def guardanns(*types: Types, negate: bool = None) -> dict:
    '''Generates a dictionary of annotations for a guard function.'''
    return {'x': 'typing.Any', 'return': guardtype(*types)}

def setguardattrs(
    call: GuardFunc, 
    *types: Types,
    docsfn: StrFactory = guarddocs,
    namefn: StrFactory = guardname,
    annsfn: StrFactory = guardanns,
    negate: bool = False,
) -> Callable[P, TypeGuard[T]]:
    '''Sets the guard function's attributes including docstring, name, and annotations.

    Parameters
    ----------
    call : GuardFunc
        The guard function to be modified.
    types : Types
        A tuple of types for the guard.
    docsfn : StrFactory, optional
        The function to generate the docstring, by default guarddocs.
    namefn : StrFactory, optional
        The function to generate the name, by default guardname.
    annsfn : StrFactory, optional
        The function to generate the annotations, by default guardanns.
    negate : bool, optional
        A flag to indicate if the guard should represent a negation, by default False.

    Returns
    -------
    Callable[P, TypeGuard[T]]
        The modified guard function.
    '''
    # Update docstring, name, and annotations
    for fn, at in zip((docsfn, namefn, annsfn), ('__doc__', '__name__', '__annotations__')):
        attr = fn(*types, negate=negate)
        setattr(call, at, getattr(call, at, attr))
    return call

# %% ../nbs/01_util.ipynb 23
def classname(cls: type, clsname: StrBool = True) -> StrBool:
    '''Retrieves an optional classname for the given `cls`. 
    
    Notes
    -----
    - if `clsname == False`, then `False` is returned.
    - if `clsname` is a `str`, then that string is returned.
    - if `clsname` is a class itself, then the name of the class is returned.
    - if `clsname == True` or otherwise, then the name of the `cls` is returned. 
    '''
    if clsname == False: return clsname
    if inspect.isclass(clsname): return clsname.__name__
    elif isinstance(clsname, str): return clsname
    else: return cls.__name__
    
def qualname(
    cls, *types: Types, clsname: StrBool = True, 
    prefix: Optional[str] = None, suffix: Optional[str] = None, dropnone: bool = True
) -> str:
    '''Generates a qualified name for a type or a set of types.

    Parameters
    ----------
    cls : type
        The class for which to generate the name.
    types : Types
        A tuple of types.
    clsname : StrBool, optional
        Determines the format of the class name returned, by default True.
    prefix : Optional[str], optional
        An optional prefix for the name, by default None.
    suffix : Optional[str], optional
        An optional suffix for the name, by default None.
    dropnone : bool, optional
        Flag to drop None from types, by default True.

    Returns
    -------
    str
        The qualified name generated.
    '''
    if dropnone:
        types = tuple(Optional[t] for t in types if t is not None) 
    tname = typescsv(*types)
    qname = f'{{{tname}}}'
    if prefix is not None: qname = f'{prefix}{qname}'
    if suffix is not None: qname = f'{qname}{suffix}'
    if (cname := classname(cls, clsname)): qname = f'{cname}({qname})'
    return qname

def settypes(
    cls, *types: Types, clsname: StrBool = True,
    prefix: Optional[str] = None, suffix: Optional[str] = None, dropnone: bool = True,
    **kwargs
):
    '''Sets type-related attributes on a class.

    Parameters
    ----------
    cls : type
        The class to modify.
    types : Types
        A tuple of types.
    clsname : StrBool, optional
        Determines the format of the class name returned, by default True.
    prefix : Optional[str], optional
        An optional prefix for the name, by default None.
    suffix : Optional[str], optional
        An optional suffix for the name, by default None.
    dropnone : bool, optional
        Flag to drop None from types, by default True.

    Returns
    -------
    None
    '''
    qname = qualname(cls, *types, clsname=clsname, prefix=prefix, suffix=suffix, dropnone=dropnone)
    setattr(cls, '__qualname__', qname)
    annots = getattr(getattr(cls, 'check', None), '__annotations__', {})    
    annots['return'] = guardtype(*types)
    setattr(cls, 'types', types)
