"""Specification classes for HOL child and parent object types."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload

from ecmind_blue_client.ecm.builder._common import DmsQueryFieldSchemaT
from ecmind_blue_client.ecm.model._ecm_model_field import ECMCondition, ECMConditionGroup

if TYPE_CHECKING:
    from ecmind_blue_client.ecm.model._ecm_model import _ECMModelBase

_AnyModelT = TypeVar("_AnyModelT", bound="_ECMModelBase")


class ECMChildSpec(Generic[_AnyModelT]):
    """Specification for a child object type in a HOL query.

    Pass instances to ``with_children()`` on ``ECMModelQueryHolSync`` /
    ``ECMModelQueryHolAsync`` to include typed child objects in the result.

    When ``model`` is a typed model class, ``ECMHolResult.children_of(spec)``
    returns a ``list`` typed as that class. When ``model`` is a string (internal name)
    or integer (type ID), the result is a generic ``list[_ECMModelBase]``.

    Use ``where()`` to add ``<SubConditions>`` that filter which child objects are
    returned by the server.

    Example::

        # Typed child spec — children_of(spec) returns list[RegisterModel]
        child_reg = ECMChildSpec(RegisterModel)

        # With sub-conditions — only children whose Name == "Subreg1" are returned
        child_reg = ECMChildSpec(RegisterModel).where(RegisterModel.Name == "Subreg1")

        # Untyped child spec — children_of("Register") returns list[_ECMModelBase]
        child_reg = ECMChildSpec("Register")

    Args:
        model: Model class, internal name, or type ID of the child object type.
        field_schema: Field schema to request for child field values. Defaults to ``"ALL"``.
    """

    @overload
    def __new__(cls, model: "type[_AnyModelT]", field_schema: DmsQueryFieldSchemaT = ...) -> "ECMChildSpec[_AnyModelT]": ...

    @overload
    def __new__(cls, model: str | int, field_schema: DmsQueryFieldSchemaT = ...) -> "ECMChildSpec[_ECMModelBase]": ...

    def __new__(cls, *_args: Any, **_kwargs: Any) -> "ECMChildSpec[Any]":
        return super().__new__(cls)

    def __init__(self, model: Any, field_schema: DmsQueryFieldSchemaT = "ALL") -> None:
        self.model = model
        self.field_schema = field_schema
        self._conditions: list[ECMCondition | ECMConditionGroup] = []

    def where(self, *conditions: ECMCondition | ECMConditionGroup) -> "ECMChildSpec[_AnyModelT]":
        """Add sub-conditions to restrict which child objects are returned.

        Generates ``<SubConditions>`` elements inside the ``<ChildObjectType>``
        in the request XML.

        Args:
            *conditions: ``ECMCondition`` or ``ECMConditionGroup`` instances.

        Returns:
            ``self`` for method chaining.
        """
        self._conditions.extend(conditions)
        return self

    def identifier(self) -> str | int:
        """Return the ECM identifier for this spec's model (internal name or type ID).

        Returns:
            The internal name (``str``) or type ID (``int``) of the child object type.
        """
        if isinstance(self.model, (str, int)):
            return self.model
        return self.model._ecm_identifier()  # pylint: disable=protected-access

    def __repr__(self) -> str:
        model_label = self.model if isinstance(self.model, (str, int)) else getattr(self.model, "__name__", repr(self.model))
        return f"<ECMChildSpec model={model_label!r} field_schema={self.field_schema!r}>"


class ECMParentSpec(Generic[_AnyModelT]):
    """Specification for a parent object type in a HOL query.

    Pass instances to ``with_parents()`` on ``ECMModelQueryHolSync`` /
    ``ECMModelQueryHolAsync`` to include typed parent objects in the result.

    Parent specs are ordered from **outermost to innermost** parent (e.g. Folder first,
    then intermediate registers). The queried object type itself is the implicit innermost
    element (mapped to the ``<SubObjectType>`` in the request XML).

    When ``model`` is a typed model class, ``ECMHolResult.parent_of(spec)`` returns
    that class (or ``None``). When ``model`` is a string or integer, the result is
    ``_ECMModelBase | None``.

    Example::

        # Typed parent spec — parent_of(spec) returns FolderModel | None
        parent_folder = ECMParentSpec(FolderModel)

        # Untyped parent spec — parent_of("Folder") returns _ECMModelBase | None
        parent_folder = ECMParentSpec("Folder")

    Args:
        model: Model class, internal name, or type ID of the parent object type.
        field_schema: Field schema to request for parent field values. Defaults to ``"ALL"``.
    """

    @overload
    def __new__(cls, model: "type[_AnyModelT]", field_schema: DmsQueryFieldSchemaT = ...) -> "ECMParentSpec[_AnyModelT]": ...

    @overload
    def __new__(cls, model: str | int, field_schema: DmsQueryFieldSchemaT = ...) -> "ECMParentSpec[_ECMModelBase]": ...

    def __new__(cls, *_args: Any, **_kwargs: Any) -> "ECMParentSpec[Any]":
        return super().__new__(cls)

    def __init__(self, model: Any, field_schema: DmsQueryFieldSchemaT = "ALL") -> None:
        self.model = model
        self.field_schema = field_schema

    def identifier(self) -> str | int:
        """Return the ECM identifier for this spec's model (internal name or type ID).

        Returns:
            The internal name (``str``) or type ID (``int``) of the parent object type.
        """
        if isinstance(self.model, (str, int)):
            return self.model
        return self.model._ecm_identifier()  # pylint: disable=protected-access

    def __repr__(self) -> str:
        model_label = self.model if isinstance(self.model, (str, int)) else getattr(self.model, "__name__", repr(self.model))
        return f"<ECMParentSpec model={model_label!r} field_schema={self.field_schema!r}>"
