"""Builder for a single ``<Object>`` element in DMSData XML."""
import xml.etree.ElementTree as ET
from typing import Optional

from ecmind_blue_client import MainTypeId, SystemFields
from ecmind_blue_client.ecm import DmsObjectFieldValueTypes
from ecmind_blue_client.ecm._common import dms_object_field_value_to_str
from ecmind_blue_client.ecm.builder import DmsDataRemarkActionT, DmsDataRemarkColorT
from ecmind_blue_client.ecm.builder._common import (
    DmsDataFieldFunction,
    DmsQueryObjectIdentTypeT,
    find_node_or_create,
)
from ecmind_blue_client.ecm.builder._dms_data_table_field import DmsDataTableField


class DmsDataObject:
    """Represents a single ``<Object>`` element within a ``<ObjectType>`` in the DMSData XML.

    Provides methods to set index fields, multi-value fields, table dialog fields, and remark
    objects. Methods prefixed with ``search_`` write into the ``<Search>`` sub-element used
    for identifying the object; methods without the prefix write into the main ``<Object>``
    body for field values.

    ``<ObjectType>`` attributes (``object_type_*`` properties) identify the object type in the
    archive schema. ``<Object>`` attributes (all other properties) describe the concrete object
    instance.
    """

    def __init__(self, parent: ET.Element, internal_name_or_type_id: str | int, object_id: Optional[int] = None):
        """
        Args:
            parent (ET.Element): The parent ``<Archive>`` element.
            internal_name_or_type_id (str | int): Internal name or numeric type ID of the object type.
            object_id (Optional[int]): Object ID for update operations. ``None`` for new objects.
        """
        self._parent_node = parent
        self._internal_name_or_type_id = internal_name_or_type_id

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

        self._node = ET.SubElement(self._object_type_node, "Object", {})
        if object_id:
            self._node.attrib["object_id"] = f"{object_id}"

    # ------------------------------------------------------------------
    # <ObjectType> attributes
    # ------------------------------------------------------------------

    @property
    def object_type_table(self) -> str | None:
        """Database table name of the object type."""
        return self._object_type_node.attrib.get("table", None)

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

    @property
    def object_type_name(self) -> str | None:
        """Display name of the object type."""
        return self._object_type_node.attrib.get("name", None)

    @object_type_name.setter
    def object_type_name(self, value: str):
        self._object_type_node.attrib["name"] = value

    @property
    def object_type_osguid(self) -> str | None:
        """OS GUID of the object type."""
        return self._object_type_node.attrib.get("osguid", None)

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

    @property
    def object_type_type(self) -> DmsQueryObjectIdentTypeT | None:
        """Base type of the object type: ``FOLDER``, ``REGISTER``, or ``DOCUMENT``."""
        return self._object_type_node.attrib.get("type", None)  # type: ignore[return-value]

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

    @property
    def object_type_maintype(self) -> int | None:
        """Numeric main type ID of the object type."""
        maintype = self._object_type_node.attrib.get("maintype", None)
        return int(maintype) if maintype else None

    @object_type_maintype.setter
    def object_type_maintype(self, value: int):
        self._object_type_node.attrib["maintype"] = str(value)

    @property
    def object_type_cotype(self) -> int | None:
        """Numeric co-type ID of the object type."""
        cotype = self._object_type_node.attrib.get("cotype", None)
        return int(cotype) if cotype else None

    @object_type_cotype.setter
    def object_type_cotype(self, value: int):
        self._object_type_node.attrib["cotype"] = str(value)

    # ------------------------------------------------------------------
    # <Object> attributes
    # ------------------------------------------------------------------

    @property
    def object_id(self) -> int | None:
        """Unique object ID of this instance."""
        object_id = self._node.attrib.get("object_id", None)
        return int(object_id) if object_id else None

    @object_id.setter
    def id(self, value: int):
        self._node.attrib["object_id"] = f"{value}"

    @property
    def folder_id(self) -> int | None:
        """ID of the folder this object is filed in."""
        folder_id = self._node.attrib.get("folder_id", None)
        return int(folder_id) if folder_id else None

    @folder_id.setter
    def folder_id(self, value: int):
        self._node.attrib["folder_id"] = f"{value}"

    @property
    def folder_type(self) -> int | None:
        """Type ID of the folder this object is filed in."""
        folder_type = self._node.attrib.get("folder_type", None)
        return int(folder_type) if folder_type else None

    @folder_type.setter
    def folder_type(self, value: int):
        self._node.attrib["folder_type"] = f"{value}"

    @property
    def register_id(self) -> int | None:
        """ID of the register this object is filed in."""
        register_id = self._node.attrib.get("register_id", None)
        return int(register_id) if register_id else None

    @register_id.setter
    def register_id(self, value: int):
        self._node.attrib["register_id"] = f"{value}"

    @property
    def register_type(self) -> int | None:
        """Type ID of the register this object is filed in."""
        register_type = self._node.attrib.get("register_type", None)
        return int(register_type) if register_type else None

    @register_type.setter
    def register_type(self, value: int):
        self._node.attrib["register_type"] = f"{value}"

    @property
    def variantparent_id(self) -> int | None:
        """ID of the parent document when creating a document variant."""
        variantparent_id = self._node.attrib.get("variantparent_id", None)
        return int(variantparent_id) if variantparent_id else None

    @variantparent_id.setter
    def variantparent_id(self, value: int):
        self._node.attrib["variantparent_id"] = f"{value}"

    @property
    def concurrency_timestamp(self) -> int | None:
        """Concurrency timestamp used for optimistic locking on update operations."""
        concurrency_timestamp = self._node.attrib.get("concurrency_timestamp", None)
        return int(concurrency_timestamp) if concurrency_timestamp else None

    @concurrency_timestamp.setter
    def concurrency_timestamp(self, value: int):
        self._node.attrib["concurrency_timestamp"] = f"{value}"

    @property
    def maintype(self) -> MainTypeId | None:
        """Main type (folder, register, document) of this object."""
        maintype = self._node.attrib.get("maintype", None)
        return MainTypeId(int(maintype)) if maintype else None

    @maintype.setter
    def maintype(self, main_type_id: MainTypeId):
        self._node.attrib["maintype"] = f"{main_type_id.value}"

    @property
    def sourceparent_id(self) -> int | None:
        """Source parent ID used when copying or moving objects."""
        sourceparent_id = self._node.attrib.get("sourceparent_id", None)
        return int(sourceparent_id) if sourceparent_id else None

    @sourceparent_id.setter
    def sourceparent_id(self, value: int):
        self._node.attrib["sourceparent_id"] = f"{value}"

    @property
    def target_type(self) -> int | None:
        """Target type ID used when moving or filing an object into a different type."""
        target_type = self._node.attrib.get("target_type", None)
        return int(target_type) if target_type else None

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

    # ------------------------------------------------------------------
    # <Search> section
    # ------------------------------------------------------------------

    def search_field(self, internal_name: str | SystemFields, value: DmsObjectFieldValueTypes | DmsDataFieldFunction):
        """Adds a ``<Field>`` to the ``<Search>`` section for object identification.

        Args:
            internal_name (str | SystemFields): Internal field name or ``SystemFields`` member.
            value (DmsObjectFieldValueTypes | DmsDataFieldFunction): Field value or function placeholder.
        """
        self._create_field(self._search_node, "Fields", "Field", internal_name, value)

    def search_multi_field(self, internal_name: str | SystemFields, value: DmsObjectFieldValueTypes | DmsDataFieldFunction):
        """Adds a ``<Field>`` to the ``<Search>/<MultiFields>`` section for object identification.

        The XSD defines ``<Search>/<MultiFields>`` as a plain list of ``<Field>`` elements,
        unlike the body ``<MultiFields>`` which uses a ``<MultiField>/<Page>/<Value>`` structure.

        Args:
            internal_name (str | SystemFields): Internal field name or ``SystemFields`` member.
            value (DmsObjectFieldValueTypes | DmsDataFieldFunction): Field value or function placeholder.
        """
        self._create_field(self._search_node, "MultiFields", "Field", internal_name, value)

    def search_table_field(self, internal_name: str) -> DmsDataTableField:
        """Returns a ``DmsDataTableField`` builder in the ``<Search>`` section.

        Args:
            internal_name (str): Internal name of the table dialog field.

        Returns:
            DmsDataTableField: Builder for adding rows to this table field.
        """
        list_node = self._search_node.find("TableFields")
        if list_node is None:
            list_node = ET.SubElement(self._search_node, "TableFields")
        return DmsDataTableField(list_node, internal_name)

    # ------------------------------------------------------------------
    # Main body fields
    # ------------------------------------------------------------------

    def field(self, internal_name: str | SystemFields, value: DmsObjectFieldValueTypes | DmsDataFieldFunction):
        """Adds a ``<Field>`` to the object's ``<Fields>`` section.

        Args:
            internal_name (str | SystemFields): Internal field name or ``SystemFields`` member.
            value (DmsObjectFieldValueTypes | DmsDataFieldFunction): Field value or function placeholder.
        """
        self._create_field(self._node, "Fields", "Field", internal_name, value)

    def multi_field(
        self,
        internal_name: str | SystemFields,
        page_id: int,
        values: list[DmsObjectFieldValueTypes | DmsDataFieldFunction],
    ):
        """Adds a ``<Page>`` to the matching ``<MultiField>`` in the object's ``<MultiFields>`` section.

        The XSD structure is ``<MultiFields>/<MultiField>/<Page id="…">/<Value>``.
        Call this method once per page. Multiple calls with the same ``internal_name``
        append additional ``<Page>`` elements to the existing ``<MultiField>`` element.

        Args:
            internal_name (str | SystemFields): Internal name of the multi-value field.
            page_id (int): Page ID (1-based) for this set of values.
            values (list[DmsObjectFieldValueTypes | DmsDataFieldFunction]): Values for this page.
        """
        if isinstance(internal_name, SystemFields):
            internal_name = internal_name.name

        multi_fields_node = self._node.find("MultiFields")
        if multi_fields_node is None:
            multi_fields_node = ET.SubElement(self._node, "MultiFields")

        multi_field_node = next(
            (n for n in multi_fields_node if n.tag == "MultiField" and n.attrib.get("internal_name") == internal_name),
            None,
        )
        if multi_field_node is None:
            multi_field_node = ET.SubElement(multi_fields_node, "MultiField", {"internal_name": internal_name})

        page_node = ET.SubElement(multi_field_node, "Page", {"id": str(page_id)})
        for value in values:
            value_node = ET.SubElement(page_node, "Value")
            if isinstance(value, DmsDataFieldFunction):
                value_node.attrib["field_function"] = value.value
            else:
                value_node.text = dms_object_field_value_to_str(value)

    def table_field(self, internal_name: str) -> DmsDataTableField:
        """Returns a ``DmsDataTableField`` builder in the object's ``<TableFields>`` section.

        Args:
            internal_name (str): Internal name of the table dialog field.

        Returns:
            DmsDataTableField: Builder for adding rows to this table field.
        """
        list_node = self._node.find("TableFields")
        if list_node is None:
            list_node = ET.SubElement(self._node, "TableFields")
        return DmsDataTableField(list_node, internal_name)

    # ------------------------------------------------------------------
    # Remarks
    # ------------------------------------------------------------------

    def remark_object(
        self,
        object_id: int,
        object_type_id: int,
        action: Optional[DmsDataRemarkActionT] = None,
        text: Optional[str] = None,
        color: Optional[DmsDataRemarkColorT] = None,
    ):
        """Adds a ``<RemarkObject>`` to the object's ``<Remarks>`` section, identified by object ID.

        Args:
            object_id (int): ID of the remark object.
            object_type_id (int): Type ID of the remark object.
            action (Optional[DmsDataRemarkActionT]): Remark action (INSERT, UPDATE, DELETE, …).
            text (Optional[str]): Remark text content.
            color (Optional[DmsDataRemarkColorT]): Background colour of the remark.
        """
        remarks_node = self._remarks_node

        remark_object_node = ET.SubElement(remarks_node, "RemarkObject", {"object_id": str(object_id), "object_type": str(object_type_id)})

        if text is not None:
            remark_object_node.text = text

        if action is not None:
            remark_object_node.attrib["action"] = action

        if color is not None:
            remark_object_node.attrib["color"] = color

    def remark_text(
        self,
        remark_id: int,
        action: Optional[DmsDataRemarkActionT] = None,
        text: Optional[str] = None,
        color: Optional[DmsDataRemarkColorT] = None,
    ):
        """Adds a ``<RemarkText>`` to the object's ``<Remarks>`` section, identified by remark ID.

        Args:
            remark_id (int): ID of the existing remark to update or delete.
            action (Optional[DmsDataRemarkActionT]): Remark action (UPDATE, UPDATE_TEXT, UPDATE_COLOR, DELETE, …).
            text (Optional[str]): Remark text content.
            color (Optional[DmsDataRemarkColorT]): Background colour of the remark.
        """
        remarks_node = self._remarks_node

        remark_text_node = ET.SubElement(remarks_node, "RemarkText", {"remark_id": str(remark_id)})

        if text is not None:
            remark_text_node.text = text

        if action is not None:
            remark_text_node.attrib["action"] = action

        if color is not None:
            remark_text_node.attrib["color"] = color

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @property
    def _search_node(self) -> ET.Element:
        """Lazily creates and returns the ``<Search>`` child element of this object node."""
        search_node = self._node.find("Search")
        if search_node is None:
            search_node = ET.SubElement(self._node, "Search")
        return search_node

    @property
    def _remarks_node(self) -> ET.Element:
        """Lazily creates and returns the ``<Remarks>`` child element of this object node."""
        remarks_node = self._node.find("Remarks")
        if remarks_node is None:
            remarks_node = ET.SubElement(self._node, "Remarks")
        return remarks_node

    def _create_field(
        self,
        parent_node: ET.Element,
        list_tag_name: str,
        tag_name: str,
        internal_name: str | SystemFields,
        value: DmsObjectFieldValueTypes | DmsDataFieldFunction,
    ):
        """Appends a field element to a list container inside ``parent_node``.

        Creates the list container element (e.g. ``<Fields>``) if it does not yet exist, then
        appends a child element (e.g. ``<Field>``) with the given ``internal_name``. If ``value``
        is a ``DmsDataFieldFunction``, the ``field_function`` attribute is set instead of text
        content. System field names receive an additional ``system="1"`` attribute.

        Args:
            parent_node (ET.Element): The XML element that will contain the list element.
            list_tag_name (str): Tag name of the list container element (e.g. ``"Fields"``).
            tag_name (str): Tag name of the individual field element (e.g. ``"Field"``).
            internal_name (str | SystemFields): Internal field name or ``SystemFields`` member.
            value (DmsObjectFieldValueTypes | DmsDataFieldFunction): Field value or function placeholder.
        """
        list_node = parent_node.find(list_tag_name)
        if list_node is None:
            list_node = ET.SubElement(parent_node, list_tag_name)

        if isinstance(internal_name, SystemFields):
            internal_name = internal_name.name

        field_node = ET.SubElement(list_node, tag_name, {"internal_name": internal_name})

        if isinstance(value, DmsDataFieldFunction):
            field_node.attrib["field_function"] = value.value
        else:
            field_node.text = dms_object_field_value_to_str(value)
            if internal_name in SystemFields.__members__:
                field_node.attrib["system"] = "1"

    def __enter__(self):
        return self

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