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

# %% auto 0
__all__ = ['unionify', 'getorigintypes', 'opttypes', 'guardtype', 'filtkwds', 'getname', 'argname', 'argsname', 'classname',
           'qualname']

# %% ../nbs/05_util.ipynb 6
import inspect
from inspect import signature

# %% ../nbs/05_util.ipynb 8
import typing
from typing import (Any, Dict, Union, TypeGuard, Callable, Optional, get_args, )

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


# %% ../nbs/05_util.ipynb 12
from nchr import NIL
from nlit import __NAME__

# %% ../nbs/05_util.ipynb 14
from .cons import dunname
from .duck import NotSpecial
from .atyp import T, Types, Guard
from .grds import isnotspecial

# %% ../nbs/05_util.ipynb 16
def unionify(*t: Types) -> Union[None, type, Types]:
    '''Create a union type from given types, handling `None` and single type cases.'''
    return None if (n := len(t)) == 0 else t[0] if n == 1 else Union[t[0], *t[1:]]

def getorigintypes() -> list[NotSpecial]:
    '''Retrieve a list of non-special types from the `typing` module.'''
    return [a for e in dir(typing) if isnotspecial(a := getattr(typing, e))]

def opttypes(types: Types, dropnone: bool = True) -> Types:
    '''Optionally wrap types in a `typing.Optional` type, excluding `None` if specified.

    Parameters
    ----------
    types : Types
        A tuple of types to be optionally wrapped in `typing.Optional`.
        
    dropnone : bool, default True
        Whether to exclude `None` type from the resulting tuple.

    Returns
    -------
    Types
        A tuple of types, optionally wrapped in `typing.Optional`.
    '''
    return types if not dropnone else tuple(Optional[t] for t in types if t is not None)

def guardtype(*types: Types) -> Guard:
    '''Create a TypeGuard for a union of the given types.

    Parameters
    ----------
    *types : Types
        A variadic number of type variables to be included in the class's qualified name.
        
    Returns
    -------
    Guard
        A TypeGuard for the union of the given types.
    '''
    return TypeGuard[unionify(*opttypes(types))]

# %% ../nbs/05_util.ipynb 18
def filtkwds(fn: Callable, **kwargs) -> Dict[str, Any]:
    '''Filter keyword arguments to only include those that are valid for a given function's signature.

    Parameters
    ----------
    fn : Callable
        The function whose signature is to be considered for filtering.
        
    **kwargs : dict
        Keyword arguments to be filtered based on the function's signature.

    Returns
    -------
    Dict[str, Any]
        A dictionary of keyword arguments that are valid for the function's signature.
    '''
    sig = signature(fn).parameters
    return {k: v for k, v in kwargs.items() if k in sig}

def getname(o: object) -> str:
    '''Get the name of an object or its string representation if the name is not directly accessible.

    Parameters
    ----------
    o : object
        The object whose name is to be retrieved.

    Returns
    -------
    str
        The name of the object or its string representation.
    '''
    return str(dunname(o))

def argname(a: T) -> str:
    '''Retrieve the name of the first argument type for a given type variable.

    Parameters
    ----------
    a : T
        A type variable whose first argument type's name is to be retrieved.

    Returns
    -------
    str
        The name of the first argument type of the type variable.
    '''
    return getname((get_args(a) or (a, ))[0])

def argsname(*args: Types, sep: str = ', ') -> str:
    '''Concatenate the names of the argument types for a list of type variables.

    Parameters
    ----------
    *args : Types
        A variadic number of type variables.
        
    sep : str, default: ', '
        The separator to use when concatenating the names of the argument types.

    Returns
    -------
    str
        A comma-separated string of the names of the argument types.
    '''
    return sep.join(list(map(argname, args)))

def classname(cls: type, alt: Optional[str] = None) -> str:
    '''Retrieve the name of a class, or an alternate name if provided.

    Parameters
    ----------
    cls : type
        The class whose name is to be retrieved.
        
    alt : Optional[str], optional
        An alternate name to return instead of the class's name.

    Returns
    -------
    str
        The name of the class or the alternate name if provided.
    '''
    if inspect.isclass(alt): return getname(alt)
    if isinstance(alt, str): return alt
    return getname(cls)

def qualname(
    cls: type, 
    *types: Types, 
    prefix: str = NIL, 
    suffix: str = NIL, 
    dropnone: bool = True, 
    alt: Optional[str] = None,
) -> str:
    '''Generate a qualified name for a class with specific type parameters and optional prefix/suffix.

    Parameters
    ----------
    cls : type
        The class for which the qualified name is to be generated.
        
    *types : Types
        A variadic number of type variables to be included in the class's qualified name.
        
    prefix : str, optional
        A prefix to be added to the qualified name.
        
    suffix : str, optional
        A suffix to be added to the qualified name.
        
    dropnone : bool, default True
        Whether to exclude `None` type from the type variables.
        
    alt : Optional[str], optional
        An alternative name to use instead of the class's name.

    Returns
    -------
    str
        The generated qualified name of the class.
    '''
    types = opttypes(types, dropnone)
    cname = classname(cls, alt)
    qname = f'{prefix}{{{argsname(*types)}}}{suffix}'    
    qname = f'{cname}({qname})'
    return qname
