"""Common types, enumerations, and helper functions shared across DMSQuery builder modules."""

import xml.etree.ElementTree as ET
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Literal


class DmsDataFieldFunction(Enum):
    """Field function placeholders for DMSData field values.

    - ``NULL``: Sets the field value to NULL.
    - ``OBJECT_ID``: Uses the current object ID as the value.
    - ``USER``: Uses the current logged-in user as the value.
    """

    NULL = "NULL"
    OBJECT_ID = "OBJECT_ID"
    USER = "USER"


DmsDataRemarkActionT = Literal["INSERT", "UPDATE", "UPDATE_TEXT", "UPDATE_COLOR", "DELETE"]
"""Action type for remark operations in DMSData."""

DmsDataRemarkColorT = Literal["WHITE", "YELLOW", "GREEN", "BLUE"]
"""Background colour for remark objects in DMSData."""


@dataclass
class DmsQueryParamValue:
    """Reference to a named ``<Param>`` defined in the ``<Params>`` section of the DMSQuery.

    Use this in field condition values to reference a parameter by name.
    """

    ref: str


DmsQueryRequestTypesT = Literal["LOL", "HOL", "DTL", "MIX"]
"""
- LOL - Linear object request
- HOL - Hierarchical object request
- DTL - Deprecated, use MIX instead
- MIX - Mixed object request: index data in HOL format, optional parent tree and direct children in LOL format
"""


@dataclass
class DmsQueryLinkedValue:
    """Reference to a field value of the main (requesting) object for external object conditions.

    The ``ref`` must match a ``link_name`` attribute set on a ``<Field>`` element.
    """

    ref: str


@dataclass
class DmsQueryInternalSpecialValue:
    """Wraps a server-internal special value as ``<SpecialValue>text</SpecialValue>``.

    Different from ``DmsQuerySpecialValue`` (``<Value special="1">``) — use this for
    internal DMSQuery special values that are not user-facing placeholders.
    """

    value: str


class DmsQuerySpecialValue(Enum):
    """Special placeholder values resolved server-side in DMSQuery field conditions.

    - ``COMPUTER_GUID``: GUID of the client computer.
    - ``COMPUTER_IP``: IP address of the client computer.
    - ``COMPUTER_NAME``: Name of the client computer.
    - ``ANLEGER``: Login name of the user who created the object.
    - ``ANLEGEDATUM``: Date the object was created.
    - ``ARCHIVAR``: Login name of the user who archived the object.
    - ``ARCHIVIERUNGSDATUM``: Date the object was archived.
    - ``BENUTZER``: Login name of the currently logged-in user.
    - ``BESITZER``: Login name of the object owner.
    - ``DATUM``: Current server date.
    """

    COMPUTER_GUID = "#COMPUTER-GUID#"
    COMPUTER_IP = "#COMPUTER-IP#"
    COMPUTER_NAME = "#COMPUTER-NAME#"
    ANLEGER = "#ANLEGER#"
    ANLEGEDATUM = "#ANLEGEDATUM#"
    ARCHIVAR = "#ARCHIVAR#"
    ARCHIVIERUNGSDATUM = "#ARCHIVIERUNGSDATUM#"
    BENUTZER = "#BENUTZER#"
    BESITZER = "#BESITZER#"
    DATUM = "#DATUM#"


DmsQueryConditionFieldOperatorsT = Literal["<>", "<", "<=", "=", "!=", ">", ">=", "BETWEEN", "NOT BETWEEN", "IN", "NOT IN"]
DmsQueryConditionGroupOperatorsT = Literal["AND", "OR"]
DmsQueryFulltextModeT = Literal["BOOLEAN", "PATTERN", "CONCEPT"]
"""Full-text search mode for RetrievalWare-based fulltext queries (XSD default: ``PATTERN``)."""
DmsQueryObjectSchemaT = Literal["DEF", "ALL", "REGISTER", "DOCUMENTS"]
DmsQueryFieldSchemaT = Literal["DEF", "ALL", "ALLREL", "MIN"]
DmsQueryObjectIdentTypeT = Literal["FOLDER", "REGISTER", "DOCUMENT"]
ArchiveStateT = Literal["ARCHIVED", "ARCHIVABLE", "NOT_ARCHIVABLE", "NO_PAGES", "PAGE_ERROR", "REFERENCE", "UNKNOWN"]
LockedStateT = Literal["UNLOCKED", "SELF", "OTHERS", "EXTERNAL"]
DmsQueryFieldValueT = (
    DmsQueryLinkedValue
    | DmsQueryParamValue
    | DmsQuerySpecialValue
    | DmsQueryInternalSpecialValue
    | str
    | bool
    | int
    | date
    | datetime
    | None
    | list[
        DmsQueryLinkedValue
        | DmsQueryParamValue
        | DmsQuerySpecialValue
        | DmsQueryInternalSpecialValue
        | str
        | bool
        | int
        | date
        | datetime
    ]
)


def convert_field_value_to_str(value: DmsQueryFieldValueT) -> str:
    """Converts a single scalar field value to its XML string representation.

    - ``datetime`` → ``DD.MM.YYYY HH:MM:SS``
    - ``date`` → ``DD.MM.YYYY``
    - ``bool`` → ``"1"`` / ``"0"``
    - All others → ``str(value)``
    """
    if isinstance(value, datetime):  # must precede date check — datetime is a subclass of date
        return value.strftime("%d.%m.%Y %H:%M:%S")
    if isinstance(value, date):
        return value.strftime("%d.%m.%Y")
    if isinstance(value, bool):
        return "1" if value else "0"
    return str(value)


def write_field_value(field_node: ET.Element, value: DmsQueryFieldValueT):
    """Appends the appropriate XML child element(s) for ``value`` to ``field_node``.

    Dispatch rules:
    - ``None`` → ``<NULL/>``
    - ``DmsQuerySpecialValue`` → ``<Value special="1">…</Value>``
    - ``DmsQueryParamValue`` → ``<ParamValue ref="…"/>``
    - ``DmsQueryLinkedValue`` → ``<LinkedValue ref="…"/>``
    - ``list`` → one child per element, applying the same rules recursively
    - scalar (str, bool, int, datetime) → ``<Value>…</Value>``
    """
    if value is None:
        ET.SubElement(field_node, "NULL")
    elif isinstance(value, DmsQuerySpecialValue):
        ET.SubElement(field_node, "Value", {"special": "1"}).text = value.value
    elif isinstance(value, DmsQueryInternalSpecialValue):
        ET.SubElement(field_node, "SpecialValue").text = value.value
    elif isinstance(value, DmsQueryParamValue):
        ET.SubElement(field_node, "ParamValue", {"ref": value.ref})
    elif isinstance(value, DmsQueryLinkedValue):
        ET.SubElement(field_node, "LinkedValue", {"ref": value.ref})
    elif isinstance(value, list):
        for v in value:
            if v is None:
                ET.SubElement(field_node, "NULL")
            elif isinstance(v, DmsQuerySpecialValue):
                ET.SubElement(field_node, "Value", {"special": "1"}).text = v.value
            elif isinstance(v, DmsQueryInternalSpecialValue):
                ET.SubElement(field_node, "SpecialValue").text = v.value
            elif isinstance(v, DmsQueryLinkedValue):
                ET.SubElement(field_node, "LinkedValue", {"ref": v.ref})
            elif isinstance(v, DmsQueryParamValue):
                ET.SubElement(field_node, "ParamValue", {"ref": v.ref})
            else:
                ET.SubElement(field_node, "Value").text = convert_field_value_to_str(v)
    else:
        ET.SubElement(field_node, "Value").text = convert_field_value_to_str(value)


def find_node_or_create(parent: ET.Element, tag: str, internal_name_or_type_id: str | int) -> ET.Element:
    """Finds an existing child element matching ``tag`` and the given identifier, or creates one.

    Looks for a child with a matching ``internal_name`` attribute (for strings) or ``id`` attribute
    (for integers). Creates a new child element if none is found.

    Args:
        parent (ET.Element): The parent XML element to search within.
        tag (str): The XML tag name to search for / create.
        internal_name_or_type_id (str | int): Internal name (str) or numeric type ID (int).

    Returns:
        ET.Element: The found or newly created child element.
    """
    node = next(
        (
            node
            for node in parent
            if node.tag == tag
            and (
                ("internal_name" in node.keys() and node.attrib["internal_name"] == str(internal_name_or_type_id))
                or ("id" in node.keys() and node.attrib["id"] == str(internal_name_or_type_id))
            )
        ),
        None,
    )

    if node is None:
        if isinstance(internal_name_or_type_id, int):
            node = ET.SubElement(parent, tag, {"id": str(internal_name_or_type_id)})
        else:
            node = ET.SubElement(parent, tag, {"internal_name": internal_name_or_type_id})
    return node
