"""Abstract base class shared by HOL and LOL DMSQuery XML builders."""
import warnings
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from typing import cast

from ecmind_blue_client.ecm.builder._common import (
    DmsQueryFieldValueT,
    DmsQueryRequestTypesT,
    write_field_value,
)
from ecmind_blue_client.ecm.builder._dms_query_base_params import (
    DmsDocumentBaseParams,
    DmsFolderBaseParams,
    DmsRegisterBaseParams,
)
from ecmind_blue_client.ecm.builder._dms_query_fullext_query import DmsQueryFullextQuery
from ecmind_blue_client.ecm.builder._dms_query_object_type import DmsQueryObjectType


class DmsQueryBuilder(ABC):
    """
    Abstract builder for the DMSQuery XML structure used by the dms.GetResultList API method.

    Configure the query by setting properties and calling context-manager methods to build the
    XML tree, then call ``to_xml()`` to serialise it.

    Archive identification (set at most one): ``archive_id``, ``archive_internal_name``,
    ``archive_osguid``.

    Result-set control: ``requesttype``, ``offset``, ``pagesize``, ``maxhits``.

    Additional response fields: ``status``, ``fileinfo``, ``object_inserts``,
    ``registercontext``, ``rights``, ``sql``, ``baseparams``, ``icons``, ``combined``,
    ``variants``, ``outputformat``.

    Search behaviour: ``disablesearchgroups``, ``narrowsearch``.
    """

    def __init__(self):
        # Initialize the root
        self._node = ET.Element("DMSQuery")
        self._params_node: ET.Element | None = None
        # Initialize the archive node
        self._archive_node = ET.SubElement(self._node, "Archive")

    @property
    def status(self) -> bool | None:
        """Include link count and document status information in the result.

        For documents this also includes module type, archive status, checkout status, page count
        (OBJECT_DOCPAGECOUNT, OBJECT_LOCKUSER, OBJECT_LINKS, OBJECT_MAIN, OBJECT_COUNT).
        """
        status = self._node.attrib.get("status", None)
        return bool(status) if status else None

    @status.setter
    def status(self, value: bool):
        self._node.attrib["status"] = "1" if value else "0"

    @property
    def fileinfo(self) -> bool | None:
        """Include file properties in the result (count, size, extension, mimetype)."""
        fileinfo = self._node.attrib.get("fileinfo", None)
        return bool(fileinfo) if fileinfo else None

    @fileinfo.setter
    def fileinfo(self, value: bool):
        self._node.attrib["fileinfo"] = "1" if value else "0"

    @property
    def object_inserts(self) -> bool | None:
        """Include object inserts information in the result."""
        object_inserts = self._node.attrib.get("object_inserts", None)
        return bool(object_inserts) if object_inserts else None

    @object_inserts.setter
    def object_inserts(self, value: bool):
        self._node.attrib["object_inserts"] = "1" if value else "0"

    @property
    def registercontext(self) -> bool | None:
        """Include register context information in the result."""
        registercontext = self._node.attrib.get("registercontext", None)
        return bool(registercontext) if registercontext else None

    @registercontext.setter
    def registercontext(self, value: bool):
        self._node.attrib["registercontext"] = "1" if value else "0"

    @property
    def rights(self) -> bool | None:
        """Include access rights information in the result (object_inserts, modify_index, etc.)."""
        rights = self._node.attrib.get("rights", None)
        return bool(rights) if rights else None

    @rights.setter
    def rights(self, value: bool):
        self._node.attrib["rights"] = "1" if value else "0"

    @property
    def sql(self) -> bool | None:
        """Include the generated SQL SELECT statement in the result."""
        sql = self._node.attrib.get("sql", None)
        return bool(sql) if sql else None

    @sql.setter
    def sql(self, value: bool):
        self._node.attrib["sql"] = "1" if value else "0"

    @property
    def combined(self) -> bool | None:
        """Include combined document information in the result (HOL requests only)."""
        combined = self._node.attrib.get("combined", None)
        return bool(combined) if combined else None

    @combined.setter
    def combined(self, value: bool):
        self._node.attrib["combined"] = "1" if value else "0"

    @property
    def baseparams(self) -> bool | None:
        """Include all base parameters in the result."""
        baseparams = self._node.attrib.get("baseparams", None)
        return bool(baseparams) if baseparams else None

    @baseparams.setter
    def baseparams(self, value: bool):
        self._node.attrib["baseparams"] = "1" if value else "0"

    @property
    def remarks(self) -> bool | None:
        """Include remarks in the result."""
        remarks = self._node.attrib.get("remarks", None)
        return bool(remarks) if remarks else None

    @remarks.setter
    def remarks(self, value: bool):
        self._node.attrib["remarks"] = "1" if value else "0"

    @property
    def icons(self) -> bool | None:
        """Include icon IDs of the returned objects in the result."""
        icons = self._node.attrib.get("icons", None)
        return bool(icons) if icons else None

    @icons.setter
    def icons(self, value: bool):
        self._node.attrib["icons"] = "1" if value else "0"

    @property
    def disablesearchgroups(self) -> bool | None:
        """Disable search groups for this query. XSD default is 1 (search groups disabled)."""
        disablesearchgroups = self._node.attrib.get("disablesearchgroups", None)
        return bool(disablesearchgroups) if disablesearchgroups else None

    @disablesearchgroups.setter
    def disablesearchgroups(self, value: bool):
        self._node.attrib["disablesearchgroups"] = "1" if value else "0"

    @property
    def variants(self) -> bool | None:
        """Include W-module document variant information in the result (HOL requests only)."""
        variants = self._node.attrib.get("variants", None)
        return bool(variants) if variants else None

    @variants.setter
    def variants(self, value: bool):
        self._node.attrib["variants"] = "1" if value else "0"

    @property
    def narrowsearch(self) -> bool | None:
        """Use narrow search mode.

        In narrow mode, only registers or documents directly inside the filtered registers are
        returned. In broad mode (default), objects at the folder level are also included.
        """
        narrowsearch = self._node.attrib.get("narrowsearch", None)
        return bool(narrowsearch) if narrowsearch else None

    @narrowsearch.setter
    def narrowsearch(self, value: bool):
        self._node.attrib["narrowsearch"] = "1" if value else "0"

    @property
    def offset(self) -> int | None:
        """Zero-based offset for paginated results. XSD default is 0."""
        offset = self._node.attrib.get("offset", None)
        return int(offset) if offset else None

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

    @property
    def pagesize(self) -> int | None:
        """Number of results per page. XSD default is 0 (no limit)."""
        pagesize = self._node.attrib.get("pagesize", None)
        return int(pagesize) if pagesize else None

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

    @property
    def maxhits(self) -> int | None:
        """Maximum number of results to return. XSD default is -1 (unlimited)."""
        maxhits = self._node.attrib.get("maxhits", None)
        return int(maxhits) if maxhits else None

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

    @property
    def archive_id(self) -> int | None:
        """Numeric ID of the cabinet to query."""
        val = self._archive_node.attrib.get("id", None)
        return int(val) if val else None

    @archive_id.setter
    def archive_id(self, value: int):
        self._archive_node.attrib["id"] = str(value)

    @property
    def archive_internal_name(self) -> str | None:
        """Internal name of the cabinet to query."""
        return self._archive_node.attrib.get("internal_name", None)

    @archive_internal_name.setter
    def archive_internal_name(self, value: str):
        self._archive_node.attrib["internal_name"] = value

    @property
    def archive_osguid(self) -> str | None:
        """OS GUID of the cabinet to query."""
        return self._archive_node.attrib.get("osguid", None)

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

    @property
    def archive_name(self) -> str | None:
        """
        .. deprecated::
            Use ``archive_internal_name``, ``archive_osguid``, or ``archive_id`` instead.
            The display name is ambiguous and not recommended for archive identification.
            Prefer ``archive_internal_name`` whenever possible.
        """
        return self._archive_node.attrib.get("name", None)

    @archive_name.setter
    def archive_name(self, value: str):
        warnings.warn(
            "archive_name is deprecated. Use archive_internal_name, archive_osguid, or archive_id instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        self._archive_node.attrib["name"] = value

    @property
    def requesttype(self) -> DmsQueryRequestTypesT | None:
        """Result list format. Allowed values: ``LOL``, ``HOL``, ``MIX``, ``DTL`` (deprecated).

        XSD default is ``LOL`` (linear object list).
        """
        return cast(DmsQueryRequestTypesT | None, self._node.attrib.get("requesttype", None))

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

    @property
    def outputformat(self) -> int | None:
        """Numeric output format (xs:short). Optional, no XSD default."""
        val = self._node.attrib.get("outputformat", None)
        return int(val) if val is not None else None

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

    def param(self, name: str, value: DmsQueryFieldValueT):
        """
        Adds a parameter to the query. This parameter can be used as ParamValue in other parts of the query.

        Args:
            name (str): The name of the parameter.
            value (DmsQueryFieldValueT): The value of the parameter.

        """
        if self._params_node is None:
            self._params_node = ET.SubElement(self._node, "Params")
        param_node = ET.SubElement(self._params_node, "Param", {"name": name})

        write_field_value(param_node, value)

    def folder_base_params(self) -> DmsFolderBaseParams:
        """
        Adds a FolderBaseParams element to the query for filtering folders by base properties
        (creator, creation date, modifier, modification date, owner).

        Returns:
            DmsFolderBaseParams: A DmsFolderBaseParams instance to configure the filter.
        """
        return DmsFolderBaseParams(self._archive_node)

    def register_base_params(self) -> DmsRegisterBaseParams:
        """
        Adds a RegisterBaseParams element to the query for filtering registers by base properties
        (creator, creation date, modifier, modification date, owner).

        Returns:
            DmsRegisterBaseParams: A DmsRegisterBaseParams instance to configure the filter.
        """
        return DmsRegisterBaseParams(self._archive_node)

    def document_base_params(self) -> DmsDocumentBaseParams:
        """
        Adds a DocumentBaseParams element to the query for filtering documents by base properties
        (creator, creation date, modifier, modification date, owner, archive state, lock state).

        Returns:
            DmsDocumentBaseParams: A DmsDocumentBaseParams instance to configure the filter.
        """
        return DmsDocumentBaseParams(self._archive_node)

    def fulltext_query(self, value: DmsQueryFieldValueT) -> DmsQueryFullextQuery:
        """
        Adds a FulltextQuery element to the archive for full-text search.

        Args:
            value (DmsQueryFieldValueT): The full-text search term. Accepts a plain string,
                a ``DmsQueryParamValue`` reference, or any other supported field value type.

        Returns:
            DmsQueryFullextQuery: The created fulltext query instance, which can optionally be
                used to configure child objects.
        """
        return DmsQueryFullextQuery(self._archive_node, value)

    @abstractmethod
    def object_type(self, internal_name_or_type_id: str | int) -> DmsQueryObjectType:
        """Return an ``ObjectType`` builder for the given internal name or numeric type ID."""

    def to_xml(self) -> bytes:
        """
        Generates the DMSQuery XML structure and returns it as bytes

        Returns:
            bytes: The generated DMSQuery XML structure as bytes
        """

        ET.indent(self._node, "    ")
        return ET.tostring(self._node, encoding="utf-8", xml_declaration=True)
