"""HOL ``<ObjectType>`` builder with child and parent object support."""
import warnings
import xml.etree.ElementTree as ET
from typing import Optional, cast

from ecmind_blue_client.ecm.builder._common import DmsQueryFieldSchemaT, DmsQueryObjectIdentTypeT
from ecmind_blue_client.ecm.builder._dms_query_base_params import DmsQueryObjectSchemaT
from ecmind_blue_client.ecm.builder._dms_query_object_type import (
    DmsQueryObjectType,
    DmsQueryRefObjectType,
)


class DmsQuerySubObjectType(DmsQueryRefObjectType):
    """Represents a ``<SubObjectType>`` element inside ``<ParentObjects>`` or ``<ChildObjects>``.

    Used as an alternative to ``<ParentObjectType>`` / ``<ChildObjectType>`` to identify the
    target object within the parent path or child hierarchy.
    """

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int) -> None:
        super().__init__(parent, "SubObjectType", internal_name_or_type_id)

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

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


class DmsQueryExternalObjectType(DmsQueryRefObjectType):
    """Represents an ``<ExternalObjectType>`` element inside an ``<ExternalArchive>``."""

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int) -> None:
        super().__init__(parent, "ExternalObjectType", internal_name_or_type_id)


class DmsQueryExternalArchive:
    """
    Represents an <ExternalArchive> element within <ExternalObjects>.

    Each ExternalArchive links to a specific cabinet identified by one of the archive_ident attributes.
    It can contain exactly one ExternalObjectType.

    Args:
        parent (ET.Element): The parent <ExternalObjects> element.
        archive_id (Optional[int]): Archive ID.
        archive_internal_name (Optional[str]): Archive internal name.
        archive_osguid (Optional[str]): Archive OS GUID.
        archive_name (Optional[str]): Deprecated. Archive display name. Use archive_internal_name,
            archive_osguid, or archive_id instead. Prefer ``archive_internal_name`` whenever possible.
    """

    def __init__(
        self,
        parent: ET.Element,
        archive_id: Optional[int] = None,
        archive_internal_name: Optional[str] = None,
        archive_osguid: Optional[str] = None,
        archive_name: Optional[str] = None,
    ):
        if archive_name is not None:
            warnings.warn(
                "archive_name is deprecated. Use archive_internal_name, archive_osguid, or archive_id instead.",
                DeprecationWarning,
                stacklevel=2,
            )
        attrib: dict[str, str] = {}
        if archive_id is not None:
            attrib["id"] = str(archive_id)
        if archive_internal_name is not None:
            attrib["internal_name"] = archive_internal_name
        if archive_osguid is not None:
            attrib["osguid"] = archive_osguid
        if archive_name is not None:
            attrib["name"] = archive_name
        self._node = ET.SubElement(parent, "ExternalArchive", attrib)

    def __enter__(self):
        return self

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

    def external_object_type(self, internal_name_or_type_id: str | int) -> DmsQueryExternalObjectType:
        """
        Adds an ExternalObjectType element to this external archive.

        Per XSD, each ExternalArchive may contain at most one ExternalObjectType.

        Args:
            internal_name_or_type_id (str | int): The internal name or type ID of the external object type.

        Returns:
            DmsQueryExternalObjectType: The configured external object type instance.
        """
        return DmsQueryExternalObjectType(self._node, internal_name_or_type_id)


class DmsQueryParentObjectType(DmsQueryRefObjectType):
    """Represents a ``<ParentObjectType>`` element inside ``<ParentObjects>``."""

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int) -> None:
        super().__init__(parent, "ParentObjectType", internal_name_or_type_id)


class DmsQueryChildObjectType(DmsQueryRefObjectType):
    """Represents a ``<ChildObjectType>`` element inside ``<ChildObjects>``."""

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int) -> None:
        super().__init__(parent, "ChildObjectType", internal_name_or_type_id)

    @property
    def alias(self) -> str | None:
        """Alias name returned in the result hit list for this child object type."""
        return self._node.attrib.get("alias", None)

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


class DmsQueryObjectTypeHol(DmsQueryObjectType):
    """HOL-specific ``<ObjectType>`` element builder; adds child and parent object nesting."""

    def child_objects(
        self, child_schema: DmsQueryObjectSchemaT = "DEF", export_depth: int = 0, field_schema: Optional[DmsQueryFieldSchemaT] = None
    ):
        """Adds a ``<ChildObjects>`` element to include child objects in HOL results.

        Args:
            child_schema (DmsQueryObjectSchemaT): Object schema for child objects. Defaults to ``"DEF"``.
            export_depth (int): Hierarchy depth to export. 0 means current level only. Defaults to 0.
            field_schema (Optional[DmsQueryFieldSchemaT]): Field schema for child types. Defaults to ``None``.

        Returns:
            DmsQueryChildObjects: The child objects builder.
        """
        from ecmind_blue_client.ecm.builder._dms_query_child_objects import (
            DmsQueryChildObjects,
        )

        return DmsQueryChildObjects(self._node, child_schema, export_depth, field_schema)

    def parent_objects(self, parent_schema: DmsQueryObjectSchemaT = "DEF", field_schema: Optional[DmsQueryFieldSchemaT] = None):
        """Adds a ``<ParentObjects>`` element to include parent location info in the result.

        Args:
            parent_schema (DmsQueryObjectSchemaT): Object schema for parent objects. Defaults to ``"DEF"``.
            field_schema (Optional[DmsQueryFieldSchemaT]): Field schema for parent types. Defaults to ``None``.

        Returns:
            DmsQueryParentObjects: The parent objects builder.
        """
        from ecmind_blue_client.ecm.builder._dms_query_parent_objects import (
            DmsQueryParentObjects,
        )

        return DmsQueryParentObjects(self._node, parent_schema, field_schema)

    def external_object_type(self, internal_name_or_type_id: str | int) -> DmsQueryExternalObjectType:
        """
        Adds an ExternalObjectType to a single unnamed ExternalArchive.

        Creates the <ExternalObjects><ExternalArchive> structure if it does not exist yet and
        returns an ExternalObjectType for the given internal name or type ID.

        Args:
            internal_name_or_type_id (str | int): The internal name or type ID of the external object type.

        Returns:
            DmsQueryExternalObjectType: The configured external object type instance.
        """
        if self._external_objects_node is None:
            self._external_objects_node = ET.SubElement(self._node, "ExternalObjects")
        archive_node = next(
            (n for n in self._external_objects_node if n.tag == "ExternalArchive" and not n.attrib),
            None,
        )
        if archive_node is None:
            archive_node = ET.SubElement(self._external_objects_node, "ExternalArchive")
        return DmsQueryExternalObjectType(archive_node, internal_name_or_type_id)

    def external_archive(
        self,
        archive_id: Optional[int] = None,
        archive_internal_name: Optional[str] = None,
        archive_osguid: Optional[str] = None,
        archive_name: Optional[str] = None,
    ) -> DmsQueryExternalArchive:
        """
        Adds an ExternalArchive element for linking to objects in another cabinet.

        Multiple ExternalArchive elements can be added; each can be identified by one of the
        archive_ident attributes (id, name, internal_name, osguid).

        Args:
            archive_id (Optional[int]): Archive ID.
            archive_internal_name (Optional[str]): Archive internal name.
            archive_osguid (Optional[str]): Archive OS GUID.
            archive_name (Optional[str]): Deprecated. Archive display name. Use archive_internal_name,
                archive_osguid, or archive_id instead.

        Returns:
            DmsQueryExternalArchive: A DmsQueryExternalArchive instance to configure the external archive.
        """
        if self._external_objects_node is None:
            self._external_objects_node = ET.SubElement(self._node, "ExternalObjects")
        return DmsQueryExternalArchive(
            self._external_objects_node,
            archive_id=archive_id,
            archive_internal_name=archive_internal_name,
            archive_osguid=archive_osguid,
            archive_name=archive_name,
        )
