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

# %% auto 0
__all__ = ['nymstr']

# %% ../nbs/00_core.ipynb 5
from atyp import AnyQ, SetQ, BoolQ, FloatQ, StrLikeQ, ClsStrQ, ClsBool, ClsFloat
from indoc import indoc
from fuzstr import fuzstr
from pstr.snek import snek

# %% ../nbs/00_core.ipynb 7
@indoc
class nymstr(fuzstr):   
    '''nymstr (Name-able String Class)

    Parameters
    ----------
    name : str
        The string representing the name of the item being named.

    icase : bool, default: False
        Whether to ignore case when matching, by default False.

    ispace : bool, default: False
        Whether to ignore spaces when matching, by default False.

    iunder : bool, default: False
        Whether to ignore underscores when matching, by default False.
        
    cutoff : float, default: 1
        The cutoff for fuzzy matching, by default 1.

    itemattr : ClsStrQ, default: 'itm'
        The attribute which holds the item being named, by default 'itm'.

    fullrepr : ClsBool, default:  False
        Whether or not to show the full representation, by default False.

    Attributes
    ----------
    raw : str
        The raw string.

    canon : str
        The canonical string (i.e. the raw string))
    
    known : set
        The set of known strings. Meant to be overridden by subclasses if needed.
    
    snake : str
        The snake-cased string.

    item : str
        The item which was named.

    aspec : list | tuple | dict, default: ('icase', 'ispace', 'iunder', 'cutoff', 'itemattr', 'fullrepr', )
        The attribute specification. Must be either an iterable of strings 
        or a dictionary of `str: Any`. Defaults to an empty tuple.
    
    dspec : list | tuple | dict, default: ('itemattr', )
        The dynamic attribute specification. Must be either an iterable of strings 
        or a dictionary of `str: str`. Defaults to an empty tuple.

    specs: {('aspec', ), ('aspec', 'dspec', ), ('aspec', 'dspec), (), }, default: (Spec.aspec.value, Spec.dspec.value)
        The attribute specifications to use.
        
    __readonly__ : tuple, default: (Spec.aspec.value, Spec.dspec.value)
        Attributes that cannot be set.

    Methods
    -------
    iscanon(other: str) -> bool:
        Check if `other` is the instance's canonical name.
        
    isknown(other: str, extra: SetQ = None) -> bool:
        Check if the fuzzified `other` matches any of the known (and extra) strings.

    isitem(other: AnyQ) -> bool:
        Check if `other` is the instance's item.

    tostr() -> str:
        Convert the instance to a string.

    prep(s: StrLikeQ = None) -> str:
        Prepare a string by applying case and space insensitivity rules.

    find(strseq: Iterable[StrLike], **kwargs) -> List[str]:
        Find the closest matches (same-ish) in the provided sequence using fuzzy matching.

    iseq(other: str) -> bool:
        Check if the provided string is an alias of this entity using fuzzy matching.

    __eq__(other):
        Check if the provided value is an alias of this entity using fuzzy matching.

    __str__():
        Return the raw string.

    __hash__():
        Return the hash of the raw string.

    __setattr__(name, value):
        Set the class attribute if it's not read-only.

    getattrkeys(spec: str, dyn: bool = False) -> tuple[str, ...]:
        Return attribute keys stored in `spec`.

    getattrvals(self, spec: str, dyn: bool = False) -> tuple[Any, ...]:
        Return default attribute values stored in `spec`.
    
    skeys() -> tuple[str, ...]:
        Return all attribute keys stored for each spec stored in `specs`.

    svals() -> tuple[str, ...]:
        Return default attribute values stored for each spec stored in `specs`.
    
    getattrs(**kwargs):
        Get instance parameters with optional overrides.
        
    makesame(*args, **kwargs):
        Call class constructor with the same attributes as the current instance.
    
    isinst(other):
        Check if the provided value is an instance of this class.
        
    sameattrs(other):
        Check if the provided value is an instance of this class with the same attributes.
        
    diffattrs(other):
        Check if the provided value is an instance of this class with different attributes.

    getattrname(dattr: str) -> str:
        Get the name of the dynamic attribute.
    
    getdattr(dattr: str, default: Any = None) -> Any:
        Get the value of the dynamic attribute.

    setdattr(dattr: str, value: Any = None):
        Set the value of the dynamic attribute.

    update_attrname(dattr: str, **kwargs):
        Update the name of the dynamic attribute.
    
    update_dattrval(aname: str, **kwargs):
        Update the value of the dynamic attribute.
    
    update_dattr(dattr: str, **kwargs):
        Update the name and value of the dynamic attribute.

    update_aspec(**kwargs):
        Update the attribute specification.

    update_dspec(**kwargs):
        Update the dynamic attribute specification.

    update_specs(**kwargs):
        Update the specifications in `specs` e.g. `aspec` and / or `dspec`.

    getclsattr(attr: str, default: Any = None) -> Any:
        Get the value of the class attribute.

    setclsattr(attr: str, val: Any = None):
        Set the value of the class attribute.

    copy():
        Return a shallow copy of the instance.
    
    deepcopy():
        Return a deep copy of the instance.
    '''
    icase:  ClsBool  = False # Whether to ignore case when matching
    ispace: ClsBool  = False # Whether to ignore spaces when matching
    iunder: ClsBool  = False # Whether to ignore underscores when matching
    cutoff: ClsFloat = 1     # The cutoff for fuzzy matching

    itemattr: ClsStrQ = 'itm'
    fullrepr: ClsBool = False

    aspec = ('icase', 'ispace', 'iunder', 'cutoff', 'itemattr', 'fullrepr', )
    dspec = ('itemattr', )
    
    def __new__(
        cls, name: str,
        icase:  BoolQ  = False,     # Case insensitive
        ispace: BoolQ  = False,     # Space insensitive
        iunder: BoolQ  = False,     # Underscore insensitive
        cutoff: FloatQ = 1.0,       # Fuzzy cutoff
        itemattr: StrLikeQ = 'itm', # Dynamic item attribute
        fullrepr: BoolQ = False,    # Full representation
        *args, **kwargs
    ):
        # Put all variables in a dictionary for easy access
        kwargs.update(
            icase=icase, ispace=ispace, iunder=iunder, cutoff=cutoff,
            itemattr=itemattr, fullrepr=fullrepr,
        )
        # Create the original "str" (fuzstr)        
        # kws, sup = aggopt(cls, **kwargs), clssupkws(cls)
        # opt = aggoptadd(cls, **kwargs)
        obj = super().__new__(cls, name, *args, **kwargs)
        return obj
    
    def __init__(
        self, name: str,
        icase:  BoolQ  = False,     # Case insensitive
        ispace: BoolQ  = False,     # Space insensitive
        iunder: BoolQ  = False,     # Underscore insensitive
        cutoff: FloatQ = 1.0,       # Fuzzy cutoff
        itemattr: StrLikeQ = 'itm', # Dynamic item attribute
        fullrepr: BoolQ = False,    # Full representation
        *args, **kwargs
    ):
        # Put all variables in a dictionary for easy access
        kwargs.update(
            icase=icase, ispace=ispace, iunder=iunder, cutoff=cutoff,
            itemattr=itemattr, fullrepr=fullrepr,
        )
        super().__init__(name, *args, **kwargs)

    def __repr__(self):
        if self.getclsattr('fullrepr') or self.fullrepr:
            return f'{self.clsname}({str(self)}={self.item})'
        return str(self)
    
    @property
    def canon(self) -> str:
        '''The canonical string (i.e. the raw string))'''
        return self.raw
    
    @property
    def known(self) -> set: 
        '''The set of known strings. Meant to be overridden by subclasses if needed.'''
        return {self.canon, }
    
    def iscanon(self, other: str) -> bool:
        '''Check if `other` is the instance's canonical name'''
        fuzzy = fuzstr(other, self.icase, self.ispace, self.iunder, self.cutoff)
        return fuzzy == self.canon
    
    def isknown(self, other: str, extra: SetQ = None):
        '''Check if the fuzzified `other` matches any of the known (and extra) strings'''
        known = self.known | set(extra or {}) 
        fuzzy = fuzstr(other, self.icase, self.ispace, self.iunder, self.cutoff)
        return any(fuzzy == opt for opt in known)
    
    @property
    def snake(self) -> str:
        return snek(self)

    @property
    def item(self) -> str:
        '''The dynamic `itemattr` i.e. `getattr(self, self.itemattr, None)`'''
        return self.getdattr('itemattr', None)
    
    def isitem(self, other: AnyQ) -> bool:
        '''Check if `other` is the instance's item'''        
        if isinstance(other, type(self)) or hasattr(other, 'item'):
            return self.item == other.item
        return self.item == other

    def __eq__(self, other):
        if isinstance(other, (type(self))):
            return super().__eq__(other) and self.isitem(other)
        return super().__eq__(other)
    
    def __hash__(self):
        return super().__hash__()
