"""Base builder for ``<ObjectType>`` elements shared by HOL and LOL DMSQuery XML."""
import warnings
import xml.etree.ElementTree as ET
from typing import Optional, cast

from ecmind_blue_client.ecm.builder._common import (
    DmsQueryConditionFieldOperatorsT,
    DmsQueryConditionGroupOperatorsT,
    DmsQueryFieldSchemaT,
    DmsQueryFieldValueT,
    DmsQueryObjectIdentTypeT,
    find_node_or_create,
    write_field_value,
)
from ecmind_blue_client.ecm.builder._dms_query_condition_object import (
    DmsQueryConditionObject,
)
from ecmind_blue_client.ecm.builder._dms_query_fields import DmsQueryFields
from ecmind_blue_client.ecm.builder._dms_query_table_condition import (
    DmsQueryTableCondition,
)


class DmsQueryRefObjectType:
    """Abstract base for sub/parent/child object types (``ExternalObjectType``, ``ChildObjectType``,
    ``ParentObjectType``) that support ``<Fields>`` and ``<SubConditions>``."""

    def __init__(self, parent: ET.Element, tag_name: str, internal_name_or_type_id: str | int):
        """
        Args:
            parent (ET.Element): Parent XML element.
            tag_name (str): XML tag name for the element (e.g. ``"ChildObjectType"``).
            internal_name_or_type_id (str | int): Internal name or numeric type ID.
        """
        self._parent_node = parent
        self._node = find_node_or_create(self._parent_node, tag_name, internal_name_or_type_id)
        self._sub_conditions_node: ET.Element | None = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def fields(self, field_schema: Optional[DmsQueryFieldSchemaT] = None):
        """
        Configure Result Fields

        Args:
            field_schema (Optional[DmsQueryFieldSchemaT], optional): The schema of the fields to be included in the result.
              If not provided, no fields are included.

        Returns:
            DmsQueryFields: An instance of DmsQueryFields to configure result fields.
        """
        return DmsQueryFields(self._node, field_schema)

    def field_condition(
        self,
        internal_name: str,
        value: DmsQueryFieldValueT,
        operator: DmsQueryConditionFieldOperatorsT = "=",
        bool_op: DmsQueryConditionGroupOperatorsT = "AND",
        dbname: str | None = None,
        name: str | None = None,
    ):
        """Adds a ``<FieldCondition>`` to the ``<SubConditions>`` of this object type.

        Args:
            internal_name (str): Internal name of the field to filter on.
            value (DmsQueryFieldValueT): The comparison value.
            operator (DmsQueryConditionFieldOperatorsT): Comparison operator. Defaults to ``"="``.
            bool_op (DmsQueryConditionGroupOperatorsT): Logical operator linking this condition to the
                previous one (``"AND"`` or ``"OR"``). XSD default is ``"AND"``.
            dbname (str | None): Database column name — alternative identifier to ``internal_name``.
                Prefer ``internal_name`` whenever possible. Defaults to ``None``.
            name (str | None): Display name — alternative identifier to ``internal_name``.
                Prefer ``internal_name`` whenever possible, as display names may be ambiguous.
                Defaults to ``None``.
        """
        if self._sub_conditions_node is None:
            self._sub_conditions_node = ET.SubElement(self._node, "SubConditions")

        attribs = {"internal_name": internal_name, "operator": operator}
        if bool_op != "AND":
            attribs["bool_op"] = bool_op
        if dbname is not None:
            attribs["dbname"] = dbname
        if name is not None:
            attribs["name"] = name

        field_condition_node = ET.SubElement(self._sub_conditions_node, "FieldCondition", attribs)
        write_field_value(field_condition_node, value)

    def table_condition(self, internal_name: str, row: int = 0):
        """Adds a ``<TableCondition>`` to the ``<SubConditions>`` of this object type.

        Args:
            internal_name (str): Internal name of the table dialog field.
            row (int): Row index to restrict the condition to. 0 means no row restriction.
                Defaults to 0.

        Returns:
            DmsQueryTableCondition: A table condition builder for this field.
        """
        if self._sub_conditions_node is None:
            self._sub_conditions_node = ET.SubElement(self._node, "SubConditions")

        return DmsQueryTableCondition(self._sub_conditions_node, internal_name, row)


class DmsQueryObjectType:
    """
    Class representing the ObjectType lement in the DMSQuery XML.

    Args:
        parent (ET.Element): The parent element <Archive> of the ObjectType node.
        internal_name_or_type_id (str | int): The internal name or type ID of the object type.
    """

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int):
        self._parent_node = parent
        self._internal_name_or_type_id = internal_name_or_type_id
        self._conditions_node: ET.Element | None = None
        self._external_objects_node: ET.Element | None = None

        self._node = find_node_or_create(self._parent_node, "ObjectType", internal_name_or_type_id)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    @property
    def alias(self) -> str | None:
        """Arbitrary alias name written to the result hit list for fast value access."""
        return self._node.attrib.get("alias", None)

    @alias.setter
    def alias(self, value: str):
        self._node.attrib["alias"] = value

    @property
    def table(self) -> str | None:
        """Database table name used to identify the object type (alternative to ``internal_name``).

        Prefer ``internal_name`` whenever possible.
        """
        return self._node.attrib.get("table", None)

    @table.setter
    def table(self, value: str):
        self._node.attrib["table"] = value

    @property
    def object_type(self) -> DmsQueryObjectIdentTypeT | None:
        """DMS object type discriminator (``FOLDER``, ``REGISTER``, ``DOCUMENT``).

        Should be set when the display name is used to identify the type to avoid ambiguity.
        """
        return cast(DmsQueryObjectIdentTypeT | None, self._node.attrib.get("type", None))

    @object_type.setter
    def object_type(self, value: DmsQueryObjectIdentTypeT):
        self._node.attrib["type"] = value

    @property
    def osguid(self) -> str | None:
        """OS GUID used to identify the object type."""
        return self._node.attrib.get("osguid", None)

    @osguid.setter
    def osguid(self, value: str):
        self._node.attrib["osguid"] = value

    @property
    def name(self) -> str | None:
        """
        .. deprecated::
            Use ``internal_name_or_type_id`` (passed to the constructor), ``osguid``, or ``table`` instead.
            The display name is ambiguous when multiple object types share the same name.
            Prefer ``internal_name_or_type_id`` whenever possible.
        """
        return self._node.attrib.get("name", None)

    @name.setter
    def name(self, value: str):
        warnings.warn(
            "DmsQueryObjectType.name is deprecated. Use internal_name_or_type_id, osguid, or table instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        self._node.attrib["name"] = value

    def fields(self, field_schema: Optional[DmsQueryFieldSchemaT] = None):
        """
        Configure Result Fields

        Args:
            field_schema (Optional[DmsQueryFieldSchemaT], optional):
              The schema of the fields to be included in the result. If not provided, no fields are included.

        Returns:
            DmsQueryFields: An instance of DmsQueryFields to configure result fields.
        """
        return DmsQueryFields(self._node, field_schema)

    def condition_object(self, internal_name_or_type_id: Optional[str | int] = None) -> DmsQueryConditionObject:
        """
        Returns a DmsQueryConditionObject instance of the DMSQuery xml tree representing query conditions for the given object type.

        Args:
            internal_name_or_type_id (str | int, optional): The internal name or type ID of the object type to query for.
              If None, the current object type is used.

        Returns:
            DmsQueryConditionObject: A DmsQueryConditionObject instance representing query conditions for the given object type.
        """
        if self._conditions_node is None:
            self._conditions_node = ET.SubElement(self._node, "Conditions")

        return DmsQueryConditionObject(
            self._conditions_node, self._internal_name_or_type_id if internal_name_or_type_id is None else internal_name_or_type_id
        )

    def object_ids(self, *ids: int):
        """
        Adds ObjectID conditions as an alternative to condition_object().

        Each ID identifies a specific instance of the queried object type. Per XSD, ObjectID and
        ConditionObject are mutually exclusive within <Conditions>.

        Args:
            *ids (int): One or more object IDs to filter by.
        """
        if self._conditions_node is None:
            self._conditions_node = ET.SubElement(self._node, "Conditions")
        for obj_id in ids:
            ET.SubElement(self._conditions_node, "ObjectID").text = str(obj_id)
