# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_meta.ipynb.

# %% auto 0
__all__ = ['ProtocolBaseMeta', 'ProtocolMeta', 'ProtoType', 'AliasMeta', 'Alias', 'NotMeta', 'Not', 'OptionalMeta', 'Opt']

# %% ../nbs/03_meta.ipynb 6
import os, inspect
from abc import abstractmethod
from functools import wraps, singledispatch

# %% ../nbs/03_meta.ipynb 8
from types import NoneType 
from typing import (Type, TypeGuard, Protocol, _ProtocolMeta, runtime_checkable, overload, )

# %% ../nbs/03_meta.ipynb 10
#| export


# %% ../nbs/03_meta.ipynb 12
#| export


# %% ../nbs/03_meta.ipynb 14
from .type import (T, Types, Guard, StrBool)
from .util import (classname, qualname, settypes)
from .grds import (aliascheck)

# %% ../nbs/03_meta.ipynb 17
class ProtocolBaseMeta(_ProtocolMeta):
    types: Types = tuple()
    attrs: dict = dict()

    @classmethod
    @abstractmethod
    def getbtype(cls): return None
    
    def check(cls, obj) -> Guard: 
        return aliascheck(obj, cls.types, cls.attrs)    
    
    @classmethod
    def makeguard(mcls) -> Guard:         
        return classmethod(lambda cls, obj: mcls.check(cls, obj))
        # return classmethod(lambda cls, obj: aliascheck(obj, cls.types, cls.attrs))
        return classmethod(lambda cls, obj: cls.check(obj))
    
    @classmethod
    def makebases(mcls, cls):
        btype = mcls.getbtype()
        bases = (cls, btype) if btype not in cls.__bases__ else (cls,)
        return bases

    @classmethod
    def default_kwds(cls, **kwargs) -> dict:
        kwds = dict(
            clsname  = kwargs.get('clsname', True),
            prefix   = kwargs.get('prefix', None),
            suffix   = kwargs.get('suffix', None),
            dropnone = kwargs.get('dropnone', 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', getattr(mcls, 'types', tuple())))
        dct['attrs'] = (attrs := kwargs.pop('attrs', getattr(mcls, 'attrs', dict())))        
        dct['btype'] = (btype := kwargs.pop('btype', getattr(mcls, 'btype', None)))
        # dct['check'] = classmethod(lambda cls, obj: aliascheck(obj, types, attrs))
        dct['check'] = mcls.makeguard()        
        new = super().__new__(mcls, name, bases, dct)
        new.__qualname__ = new.qualname(*types, **kwds)
        new.settypes(*types, **kwds)
        return new
    
    def __instancecheck__(self, ins) -> Guard: 
        return self.check(ins)
    
    def classname(cls, clsname: StrBool = True) -> StrBool:
        return classname(cls, clsname)
        
    def qualname(cls, *types: Types, clsname: StrBool = True, prefix: str = None, suffix: str = None, dropnone: bool = True) -> str:
        return qualname(cls, *types, clsname=clsname, prefix=prefix, suffix=suffix)
    
    def settypes(cls, *types: Types, clsname: StrBool = True, prefix: str = None, suffix: str = None, dropnone: bool = True):
        settypes(cls, *types, clsname=clsname, prefix=prefix, suffix=suffix, dropnone=dropnone)
        
    def setattrs(cls, attrs: dict = dict()):
        setattr(cls, 'attrs', attrs)

    def setbtype(cls, btype: Type = None):
        setattr(cls, 'btype', btype)

    @overload
    def __call__(cls: 'ProtocolBaseMeta', base: type, *dtypes: Types, **kwargs) -> TypeGuard[T]: ...
    @overload
    def __call__(cls: type, instance: object) -> TypeGuard[T]: ...
    @overload
    def __call__(cls: type, *instances: tuple[object, ...]) -> TypeGuard[T]: ...
    @overload
    def __call__(cls: type, *args, **kwargs) -> TypeGuard[T]: ...    
    def __call__(cls: type, *args, **kwargs) -> TypeGuard[T]:
        if len(args) > 0 and not isinstance(args[0], type) or args[0] is NoneType:
            if len(args) == 1: return cls.check(args[0])
            if all(not inspect.isclass(i) for i in args):
                return tuple(cls.check(i) for i in args)
            return cls.check(args[0])
        
        dtypes = args
        def decorator(kls):
            '''Decorator to apply negation to a class.'''
            clsname = kwargs.get('clsname', kls.__name__)
            bases = cls.makebases(kls)
            dct = dict()
            new = cls.__class__(clsname, bases, dct, types=dtypes, **kwargs)
            return new
        return decorator

# %% ../nbs/03_meta.ipynb 20
class ProtocolMeta(ProtocolBaseMeta):
    @classmethod
    def getbtype(cls): return ProtoType

@runtime_checkable
class ProtoType(Protocol):
    @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)
        cls.setbtype(cls)

# %% ../nbs/03_meta.ipynb 22
class AliasMeta(ProtocolBaseMeta):    
    @classmethod
    def getbtype(cls): return Alias
    

@runtime_checkable
class Alias(ProtoType, Protocol, metaclass=AliasMeta): 
    ...

# %% ../nbs/03_meta.ipynb 24
class NotMeta(ProtocolBaseMeta):
    @classmethod
    def getbtype(cls): return Not

    @classmethod
    def default_kwds(cls, **kwargs) -> dict:
        kwds = super().default_kwds(**kwargs)
        kwds['prefix'] = '~'        
        return kwds
    
    def check(self, obj) -> Guard:     
        return not aliascheck(obj, self.types, self.attrs)
    
@runtime_checkable
class Not(ProtoType, Protocol, metaclass=NotMeta): 
    ...

# %% ../nbs/03_meta.ipynb 26
class OptionalMeta(ProtocolBaseMeta):
    @classmethod
    def getbtype(cls): return Opt

    @classmethod
    def default_kwds(cls, **kwargs) -> dict:
        kwds = super().default_kwds(**kwargs)
        kwds['prefix'] = '?'        
        return kwds
    
    def __new__(mcls, name, bases, dct, **kwargs):
        '''Create a new class instance.'''
        types = kwargs.pop('types', tuple())
        for ntype in (None, ):
            if ntype not in types:
                types += (ntype, )
        dct['types'] = types        
        new = super().__new__(mcls, name, bases, dct, types = types, **kwargs)
        return new
    
@runtime_checkable
class Opt(ProtoType, Protocol, metaclass=OptionalMeta): 
    ...
