"""
KeeMeta provides the metaclass for the 'worktoy.keenum' module.
"""
#  AGPL-3.0 license
#  Copyright (c) 2025-2026 Asger Jon Vistisen
from __future__ import annotations

from typing import TYPE_CHECKING

from ._kee_desc import Base, MRO, Members, IsRoot
from ..core import Object
from ..desc import Field
from ..mcls import BaseMeta
from ..waitaminute import TypeException, attributeErrorFactory
from ..waitaminute.keenum import KeeNameError, KeeIndexError
from ..waitaminute.keenum import KeeMemberError, KeeValueError
from . import KeeSpace as KSpace, Kee

if TYPE_CHECKING:  # pragma: no cover
  from typing import Any, TypeAlias, Self, Iterator

  from . import KeeNum

  Bases: TypeAlias = tuple[type, ...]


class KeeMeta(BaseMeta):
  """
  KeeMeta provides the metaclass for the 'worktoy.keenum' module.
  """

  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  #  NAMESPACE  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

  #  Private Variables
  __allow_instantiation__ = False
  __num_members__ = None
  __is_root__ = None

  #  Public Variables
  isRoot = IsRoot()
  base = Base()
  mroNum = MRO()
  members = Members()
  valueType = Field()

  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  #  GETTERS  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

  @valueType.GET
  def _getValueType(cls, ) -> type:
    """Gets the value type of the enumeration. """
    type_ = None
    for member in cls.__num_members__:
      valType = type(member.value)
      if type_ is None:
        type_ = valType
    return type_

  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  #  Python API   # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

  @classmethod
  def __prepare__(mcls, name: str, bases: Bases, **kw: Any) -> KSpace:
    """Prepares the namespace for the class."""
    bases = (*[b for b in bases if b.__name__ != '_InitSub'],)
    return KSpace(mcls, name, bases, **kw)

  def __new__(mcls, name: str, bases: Bases, space: KSpace, **kw) -> Self:
    """
    Creates a new instance of the class.
    This method is called when the class is created.
    """
    cls = super().__new__(mcls, name, bases, space, **kw)
    cls.__num_members__ = []
    if not [b for b in bases if b is not Object]:
      return cls
    cls.__allow_instantiation__ = True

    for i, (key, kee) in enumerate(space.__enumeration_members__.items()):
      self = None
      for base in bases:
        self = getattr(base, key, None)
      if self is None:
        self = cls(kee, )
      setattr(cls, key, self)
      cls.__num_members__.append(self)
    cls.__allow_instantiation__ = False
    return cls

  def __call__(cls, *args, **kwargs) -> Any:
    """
    Prevents instantiation of the class.
    This method is called when the class is instantiated.
    """
    if cls.__allow_instantiation__:
      return super().__call__(*args, **kwargs)
    return cls._resolveMember(args[0])

  def __getitem__(cls, identifier: Any) -> Any:
    """
    Gets a member of the enumeration by index or key.
    This method is used to get a member of the enumeration by index or key.
    """
    return cls._resolveMember(identifier)

  def __getattr__(cls, name: str) -> Any:
    """
    Gets a member of the enumeration by name.
    This method is used to get a member of the enumeration by name.
    """
    try:
      self = cls._resolveKey(name)
    except KeeNameError as keeNameError:
      error = attributeErrorFactory(cls, name)
      raise error from keeNameError
    else:
      return self

  def __iter__(cls, ) -> Iterator[Self]:
    """
    Iterates over the members of the enumeration.
    This method is used to iterate over the members of the enumeration.
    """
    yield from cls.members

  def __len__(cls, ) -> int:
    """
    Returns the number of members in the enumeration.
    This method is used to get the number of members in the enumeration.
    """
    return sum(1 for _ in cls)

  def __contains__(cls, identifier: Any) -> bool:
    """
    Checks if the enumeration contains a member by index or key.
    This method is used to check if the enumeration contains a member.
    """
    try:
      _ = cls._resolveMember(identifier)
    except (IndexError, KeyError):
      return False
    else:
      return True

  def __instancecheck__(cls, instance: Any) -> bool:
    """
    Checks if the instance is a member of the enumeration.
    This method is used to check if the instance is a member of the
    enumeration.
    """
    for member in cls:
      if member == instance:
        return True
    if issubclass(type(instance), cls):
      return True
    return False

  def __subclasscheck__(cls, subclass: type) -> bool:
    """
    Checks if the subclass is a subclass of the enumeration.
    This method is used to check if the subclass is a subclass of the
    enumeration.
    """
    try:
      _ = issubclass(subclass, object)
    except Exception as exception:
      raise exception
    else:
      for item in subclass.__mro__:
        if item is cls:
          return True
      return False

  def __str__(cls) -> str:
    """
    Returns a string representation of the enumeration.
    This method is used to get a string representation of the enumeration.
    """
    infoSpec = """<KeeNum '%s': %d members>"""
    clsName = cls.__name__
    n = len(cls.__num_members__)
    info = infoSpec % (clsName, n)
    return info

  __repr__ = __str__

  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  #  DOMAIN SPECIFIC  # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

  def _resolveMember(cls, identifier: Any) -> Any:
    """
    Resolves a member of the enumeration.
    This method is used to resolve a member of the enumeration.
    """
    if isinstance(identifier, int):
      return cls._resolveIndex(identifier)
    if isinstance(identifier, str):
      return cls._resolveKey(identifier)
    if type(type(identifier)) is type(cls):
      for member in cls:
        if member is identifier:
          return member
      raise KeeMemberError(cls, identifier)
    try:
      member = cls.fromValue(identifier)
    except KeeValueError:
      from . import KeeNum
      raise TypeException('identifier', identifier, int, str, KeeNum)
    else:
      return member

  def _resolveIndex(cls, index: int) -> Kee:
    """
    Resolves a member of the enumeration by index.
    This method is used to resolve a member of the enumeration by index.
    """
    if index < 0:
      return cls[len(cls) + index]
    if index < len(cls):
      return cls.__num_members__[index]
    raise KeeIndexError(cls, index)

  def _resolveKey(cls, key: str) -> Kee:
    """
    Resolves a member of the enumeration by key.
    This method is used to resolve a member of the enumeration by key.
    """
    for member in cls.__num_members__:
      if member.name.lower() == key.lower():
        return member
    raise KeeNameError(cls, key)

  def fromValue(cls, value: Any) -> KeeNum:
    """
    Resolves a member of the enumeration by value.
    This method is used to resolve a member of the enumeration by value.
    """
    for member in cls.__num_members__:
      if member.value == value:
        return member
    raise KeeValueError(cls, value)
