# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_atyp.ipynb.

# %% auto 0
__all__ = ['isliteral', 'eqliteral', 'aliascheck', 'AliasMeta', 'Alias', 'ATyp']

# %% ../nbs/00_atyp.ipynb 6
import os, inspect
from functools import wraps

# %% ../nbs/00_atyp.ipynb 8
from typing import (
    Any, Type, Tuple, Union, Callable, Iterable, TypeGuard, Protocol, _ProtocolMeta,
    runtime_checkable, TypeVar, ParamSpec, TypeAlias, Self, Optional,
    Literal, get_args
)

# %% ../nbs/00_atyp.ipynb 10
#| export


# %% ../nbs/00_atyp.ipynb 12
from ntyp import notiterstr, alldtype, Types, Guard, guardtype

# %% ../nbs/00_atyp.ipynb 14
#| export

# %% ../nbs/00_atyp.ipynb 16
def isliteral(obj):
    return getattr(obj, '__name__', None) == 'Literal'
    
def eqliteral(val, obj):
    if not isliteral(obj): return False
    try: return val == get_args(obj)[0]            
    except: return False

# %% ../nbs/00_atyp.ipynb 17
def aliascheck(obj, types, attrs: dict = dict()):
    found = False
    for base in types:
        try:
            if isinstance(obj, base):
                found = True
                break
        except TypeError:
            try:
                if eqliteral(obj, base) or obj == base:
                    found = True
                    break       
            except: ...                

    if not found:
        return False        
    
    for attr, val in attrs.items():
        if attr == 'dtype' and notiterstr(obj) and alldtype(obj, val): continue                        
        if getattr(obj, attr, None) == val: continue
        return False
        raise ValueError(f'Expected {attr} to be {val}, got {getattr(obj, attr, None)}')
    return True

# %% ../nbs/00_atyp.ipynb 18
class AliasMeta(_ProtocolMeta):
    types: Types = tuple()
    attrs: dict = dict()

    @classmethod
    def default_kwds(cls, **kwargs) -> dict:
        kwds = dict(clsname = kwargs.get('clsname', True))
        return kwds

    def __new__(mcls, name, bases, dct, **kwargs):
        '''Create a new class instance.'''        
        kwds = mcls.default_kwds(**kwargs)
        dct['types'] = (types := kwargs.pop('types', tuple()))
        dct['attrs'] = (attrs := kwargs.pop('attrs', dict()))
        dct['check'] = classmethod(lambda cls, obj: aliascheck(obj, types, attrs))        
        new = super().__new__(mcls, name, bases, dct)
        new.__qualname__ = new.getqualname(*types, **kwds)
        new.settypes(*types, **kwds)
        return new

    def __call__(cls, *dtypes: Types, **kwargs):
        '''Allows using Not as a decorator with types to negate.'''        
        def decorator(kls):
            '''Decorator to apply negation to a class.'''
            clsname = kwargs.get('clsname', kls.__name__)
            bases = (kls, Alias) if Alias not in kls.__bases__ else (kls,)
            dct = dict(check = classmethod(lambda cls, obj: aliascheck(obj, cls.types, cls.attrs)))
            new = cls.__class__(clsname, bases, dct, types=dtypes, **kwargs)
            return new
        return decorator

    def __instancecheck__(self, ins) -> bool: 
        return self.check(ins)
    
    def check(self, obj) -> Guard:     
        return aliascheck(obj, self.types, self.attrs)
        return isinstance(obj, self.types)

    def getclsname(cls, clsname: Union[str, bool] = True) -> Union[str, bool]:
        if clsname == False: return clsname
        if inspect.isclass(clsname): return clsname.__name__
        elif isinstance(clsname, str): return clsname
        else: return cls.__name__
        
    def getqualname(cls, *types: Types, clsname: Union[str, bool] = True) -> str:
        tname = ', '.join([str(getattr(arg, '__name__', arg)) for arg in types or tuple()])
        qname = f'{{{tname}}}'        
        if (cname := cls.getclsname(clsname)): qname = f'{cname}({qname})'
        return qname
    
    def settypes(cls, *types: Types, clsname: str | bool = True,):
        qname = cls.getqualname(*types, clsname=clsname)
        setattr(cls, '__qualname__', qname)
        annots = getattr(getattr(cls, 'check', None), '__annotations__', {})
        annots['return'] = guardtype(*types)
        setattr(cls, 'types', types)

    def setattrs(cls, attrs: dict = dict()):        
        setattr(cls, 'attrs', attrs)

# %% ../nbs/00_atyp.ipynb 19
@runtime_checkable
class Alias(Protocol, metaclass=AliasMeta):
    @classmethod
    def __init_subclass__(cls, *args, types: Types = tuple(), attrs: dict = dict(), **kwargs) -> None:
        super().__init_subclass__()
        kwds = cls.default_kwds(**attrs)
        cls.settypes(*types, **kwds)
        cls.setattrs(attrs)

@wraps(Alias, updated=())
class ATyp: ...
