"""Protected module containing the ECMFieldDefinition dataclass."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ecmind_blue_client.ecm.definition._ecm_description_entry import (
        ECMDescriptionEntry,
    )


class ECMCatalogType(Enum):
    """Catalog type of an ECM field, derived from specific bits of ``flags``.

    Attributes:
        DATABASE: Field uses a database catalog (bit 1 of ``flags`` is set).
        ADDON: Field uses an add-on catalog (bit 21 of ``flags`` is set).
        LIST: Field uses a flat list catalog (bit 7 of ``flags`` is set).
        TREE: Field uses a tree catalog (bit 8 of ``flags`` is set).
        HIERARCHY_TREE: Field uses a hierarchy tree catalog (bit 9 of ``flags`` is set).
        STRUCTURE_TREE: Field uses a structure tree catalog (bit 10 of ``flags`` is set).
    """

    DATABASE = "database"
    ADDON = "addon"
    LIST = "list"
    TREE = "tree"
    HIERARCHY_TREE = "hierarchy_tree"
    STRUCTURE_TREE = "structure_tree"


class ECMControlType(Enum):
    """UI control type of an ECM field, derived from the ``dt`` attribute.

    Attributes:
        TEXTFIELD: Standard text/date/number input (``dt`` is one of ``X``, ``P``, ``D``, ``9``, ``#``, ``R``, ``T``, ``S``, ``Q``, ``Z``).
        TABLEFIELD: Table / grid control (``dt`` is ``W``).
        RADIOBUTTON: Radio button (``dt`` is ``1``).
        CHECKBOX: Checkbox (``dt`` is ``0``).
        PICTURE: Image control (``dt`` is ``B``).
        GROUP: Group box (``dt`` is ``G``).
        PAGE_CONTROL: Tab / page control (``dt`` is ``C``).
        WEBCONTROL: Embedded web control (``dt`` is ``E``).
        BUTTON: Push button (``dt`` is ``K``).
        LABEL: Static label — no ``dt`` value (``dt`` is empty).
    """

    TEXTFIELD = "textfield"
    TABLEFIELD = "tablefield"
    RADIOBUTTON = "radiobutton"
    CHECKBOX = "checkbox"
    PICTURE = "picture"
    GROUP = "group"
    PAGE_CONTROL = "page_control"
    WEBCONTROL = "webcontrol"
    BUTTON = "button"
    LABEL = "label"


class ECMDefaultValueMode(Enum):
    """Mode by which the default value of an ECM field is determined, derived from ``flags1``.

    Attributes:
        FUNCTION: Default value is computed by a function (bit 17 of ``flags1`` is set).
        CONSTANT: Default value is a constant (bit 16 of ``flags1`` is set).
    """

    FUNCTION = "function"
    CONSTANT = "constant"


class ECMTextAlignment(Enum):
    """Text alignment of an ECM field, derived from bits 24–25 of ``flags2``.

    Attributes:
        CENTER: Text is centred (bit 24 of ``flags2`` is set).
        LEFT: Text is left-aligned (bit 25 of ``flags2`` is set).
        RIGHT: Text is right-aligned (neither bit 24 nor bit 25 is set).
    """

    CENTER = "center"
    LEFT = "left"
    RIGHT = "right"


class ECMFieldDataType(Enum):
    """Logical data type of an ECM field, derived from ``field_name`` prefix and ``dt`` code.

    Attributes:
        STRING: Text field (``fieldname`` starts with ``feld``).
        INTEGER: Whole-number field (``fieldname`` starts with ``zahl``, ``dt`` is ``"9"`` or ``"1"``).
        BOOLEAN: Boolean / checkbox field (``fieldname`` starts with ``zahl``, ``dt`` is ``"0"``).
        DOUBLE: Decimal number field (``fieldname`` starts with ``real``).
        DATE: Date field (``fieldname`` starts with ``datum``).
        TIME: Time field (``fieldname`` starts with ``zahl``, bit 18 of ``flags`` is set).
        DATE_TIME: Date and time field (``fieldname`` starts with ``zahl`` , bit 22 of ``flags1`` is set).
    """

    STRING = "string"
    INTEGER = "integer"
    BOOLEAN = "boolean"
    DOUBLE = "double"
    DATE = "date"
    TIME = "time"
    DATE_TIME = "date_time"


@dataclass(frozen=True)
class ECMListRowEntry:
    """A single row in a flat inline list catalog (``<row>`` element).

    Attributes:
        index: Zero-based position of the row in the list.
        value: Display value of the row.
    """

    index: int
    value: str


@dataclass(frozen=True)
class ECMListNodeEntry:
    """A single node in a hierarchical inline tree catalog (``<node>`` element).

    Attributes:
        index: Global zero-based index of the node across the entire tree.
        name: Display name of the node.
        value: Selection value of the node (may be path-joined for hierarchy trees).
        children: Ordered child nodes of this node, empty for leaf nodes.
    """

    index: int
    name: str
    value: str
    children: list[ECMListNodeEntry]


@dataclass(frozen=True)
class ECMFieldList:
    """Inline catalog values stored in the ``<list>`` child element of a field.

    A field carries a ``<list>`` element when its catalog values are embedded directly
    in the definition XML rather than fetched from a server catalog at runtime.

    Attributes:
        multiselection: Whether multiple values can be selected simultaneously.
        order: Sort order code (``0`` = unsorted).
        keyselcol: Zero-based index of the key selection column.
        sortcol: Zero-based index of the sort column.
        separator: Path separator used for hierarchy tree values (empty for flat lists).
        rows: Flat list entries, in index order.  Empty for tree catalogs.
        nodes: Root nodes of the tree.  Empty for flat list catalogs.
        use_intermediate_nodes: Whether intermediate (non-leaf) nodes can be selected in a
            hierarchy tree catalog (``<USEINTERMEDIATENODES>`` element; ``"ja"`` → ``True``).
        match_mode: Match mode for the catalog lookup (``<MATCHMODE>`` element; ``0`` = exact).
        width: Display width of the list control in pixels (``<WIDTH>`` element; ``0`` = default).
        height: Display height of the list control in pixels (``<HEIGHT>`` element; ``0`` = default).
        addon32: Filename of the 32-bit add-on DLL for add-on catalogs (``<ADDON32>`` element;
            empty for non-add-on lists).
        rawdata: Raw legacy text payload of the list (``<rawdata>`` element; empty if absent).
    """

    multiselection: bool
    order: int
    keyselcol: int
    sortcol: int
    separator: str
    rows: list[ECMListRowEntry]
    nodes: list[ECMListNodeEntry]
    use_intermediate_nodes: bool = False
    match_mode: int = 0
    width: int = 0
    height: int = 0
    addon32: str = ""
    rawdata: str = ""


@dataclass(frozen=True)
class ECMTableColumnDefinition:
    """Represents a single table column definition from the ``<listctrl>`` XML element.

    Table columns are nested inside a ``<field>`` element whose ``dt`` code is ``"W"``
    (TableField). Each column defines one column of the grid control.

    Attributes:
        internal_name: Internal (programmatic) name of the column.
        os_guid: Server-side GUID identifying this column.
        field_name: Database column name.
        id: Numeric column identifier.
        length: Maximum data length of the column.
        col_width: Display width of the column in the grid (``0`` = auto).
        col_pos: Zero-based display order of the column in the grid.
        color: Column text color as a packed integer (``0`` = default).
        text_align: Text alignment code (``0`` = default).
        catalog_type: Catalog type code (``0`` = none).
        link_id: Link identifier (``0`` = none).
        readonly: Whether the column is read-only.
        values_id: Catalog values identifier, empty if not configured.
        description: Name entry for the primary language, or ``None`` if not available.
        descriptions: All language-specific name entries, keyed by ISO locale code.
        data_type: Logical data type derived from ``field_name`` prefix and the raw ``type``
            attribute, or ``None`` for columns without a database column.
    """

    internal_name: str
    os_guid: str
    field_name: str
    _type: str
    id: int
    length: int
    col_width: int
    col_pos: int
    color: int
    text_align: int
    catalog_type: int
    link_id: int
    readonly: bool
    values_id: str
    description: "ECMDescriptionEntry | None"
    descriptions: "dict[str, ECMDescriptionEntry]"

    @property
    def data_type(self) -> ECMFieldDataType | None:
        """Logical data type derived from ``field_name`` prefix and the raw ``type`` attribute.

        Returns:
            The matching ``ECMFieldDataType`` member, or ``None`` for columns without
            a database column.
        """
        if self.field_name.startswith("feld"):
            return ECMFieldDataType.STRING
        if self.field_name.startswith("zahl"):
            if self._type == "0":
                return ECMFieldDataType.BOOLEAN
            if self._type in ("9", "1"):
                return ECMFieldDataType.INTEGER
        if self.field_name.startswith("real"):
            return ECMFieldDataType.DOUBLE
        if self.field_name.startswith("datum"):
            return ECMFieldDataType.DATE
        return None


@dataclass(frozen=True)
class ECMFieldPosition:
    """Bounding box of a UI element within the ECM form layout.

    All values are in form grid units as stored in the XML.

    Attributes:
        left: Left edge of the element.
        top: Top edge of the element.
        right: Width of the element.
        bottom: Height of the element.
    """

    left: int
    top: int
    right: int
    bottom: int


@dataclass(frozen=True)
class ECMFieldDefinition:
    """Represents a single field definition from the ``<field>`` XML element.

    Attributes:
        tab_order: Display order of the field in the UI tab.
        internal_name: Internal (programmatic) name of the field.
        field_name: Database column name.
        prn_alias: Print alias of the field.
        os_guid: Server-side GUID identifying this field.
        class_string: Class configuration string.
        init: Default / initialisation value expression.
        search_group_name: Search group this field belongs to.
        regular_expression: Validation regular expression.
        label_position: Position and size of the label element on the form.
        control_position: Position and size of the input control on the form.
        input_length: Maximum input length of the field, or ``None`` if not specified.
        flags: Raw flags bitmask from the ``<flags>`` element.
        flags1: Raw flags1 bitmask from the ``<flags>`` element.
        flags2: Raw flags2 bitmask from the ``<flags>`` element.
        dt: Single-character data type code (e.g. ``"X"`` string, ``"D"`` date,
            ``"9"`` number, ``"#"`` decimal, ``"G"`` group).
        case: Case conversion flag (``1`` = as-is, ``0`` = lower), or ``None`` if not specified.
        description: Name entry for the primary language, or ``None`` if not available.
        descriptions: All language-specific name entries, keyed by ISO locale code.
        required: Whether this field is mandatory (bit 0 of ``flags`` is set).
        key_field: Whether this field is a key field (bit 2 of ``flags`` is set).
        supervisor: Whether this field is supervisor-only (bit 3 of ``flags`` is set).
        readonly_after_archiving: Whether this field becomes read-only after archiving (bit 26 of ``flags2`` is set).
        readonly_after_initialization: Whether this field becomes read-only after initialization (bit 27 of ``flags2`` is set).
        readonly: Whether this field is always read-only (bit 28 of ``flags2`` is set).
        catalog: Catalog type derived from ``flags``, or ``None`` if no catalog is configured.
        addon_auto_execute: Whether the add-on catalog is executed automatically on field entry (bit 11 of ``flags`` is set).
        label_visible: Whether the field label is visible (bit 5 of ``flags`` is not set).
        leading_zeros: Whether the field displays leading zeros (bit 6 of ``flags`` is set).
        multiline: Whether the field accepts multiline text input (bit 19 of ``flags`` is set).
        control_hidden: Whether the field control is hidden (bit 23 of ``flags1`` is set).
        regex_protected: Whether the field input is validated against a regular expression
            in a protected manner (bit 20 of ``flags1`` is set).
        crosscheck_position: Cross-check position derived from bits 0–7 of ``flags1``, or ``0`` if not set.
        crosscheck_target_tab_position: Cross-check target tab position derived from bits 8–15 of ``flags1``, or ``0`` if not set.
        default_value_mode: How the default value is determined, derived from ``flags1``,
            or ``None`` if no default value mode is configured.
        default_value_function: Default value function identifier derived from bits 24–31 of ``flags1``.
        mask_protected: Whether the field input is masked/protected (bit 18 of ``flags1`` is set).
        control: UI control type derived from the ``dt`` attribute.
        textcolor: RGB text color as ``(red, green, blue)`` derived from the lowest 3 bytes of ``flags2``.
        text_alignment: Text alignment derived from bits 24–25 of ``flags2``.
        pages: Tabs of a PageControl field, keyed by internal name. Empty for all other field types.
        columns: Table column definitions of a TableField (``dt="W"``), keyed by internal name.
            Empty for all other field types.
        list_values: Inline catalog values embedded in the field definition, or ``None`` if the
            field has no embedded list (e.g. database catalogs, add-on catalogs without inline data).
    """

    tab_order: int
    internal_name: str
    field_name: str
    prn_alias: str
    os_guid: str
    class_string: str
    init: str
    search_group_name: str
    regular_expression: str
    label_position: ECMFieldPosition
    control_position: ECMFieldPosition
    input_length: int | None
    flags: int
    flags1: int
    flags2: int
    dt: str
    _datatype: str
    _align: str
    case: int | None
    _control_type: str
    description: ECMDescriptionEntry
    descriptions: dict[str, ECMDescriptionEntry]
    pages: dict[str, ECMPageDefinition] = field(default_factory=dict)
    columns: dict[str, ECMTableColumnDefinition] = field(default_factory=dict)
    list_values: ECMFieldList | None = None

    @property
    def required(self) -> bool:
        """Whether this field is mandatory.

        Returns:
            ``True`` if bit 0 of ``flags`` is set.
        """
        return bool(self.flags & 1)

    @property
    def key_field(self) -> bool:
        """Whether this field is a key field.

        Returns:
            ``True`` if bit 2 of ``flags`` is set.
        """
        return bool(self.flags & (1 << 2))

    @property
    def supervisor(self) -> bool:
        """Whether this field is supervisor-only.

        Returns:
            ``True`` if bit 3 of ``flags`` is set.
        """
        return bool(self.flags & (1 << 3))

    @property
    def readonly_after_archiving(self) -> bool:
        """Whether this field becomes read-only after archiving.

        Returns:
            ``True`` if bit 26 of ``flags2`` is set.
        """
        return bool(self.flags2 & (1 << 26))

    @property
    def readonly_after_initialization(self) -> bool:
        """Whether this field becomes read-only after initialization.

        Returns:
            ``True`` if bit 27 of ``flags2`` is set.
        """
        return bool(self.flags2 & (1 << 27))

    @property
    def readonly(self) -> bool:
        """Whether this field is always read-only.

        Returns:
            ``True`` if bit 28 of ``flags2`` is set.
        """
        return bool(self.flags2 & (1 << 28))

    @property
    def textcolor(self) -> tuple[int, int, int]:
        """RGB text color derived from the lowest 3 bytes of ``flags2``.

        Returns:
            A ``(red, green, blue)`` tuple where each component is in the range 0–255.
            Red is stored in byte 0 (bits 0–7), green in byte 1 (bits 8–15),
            blue in byte 2 (bits 16–23).
        """
        red = self.flags2 & 0xFF
        green = (self.flags2 >> 8) & 0xFF
        blue = (self.flags2 >> 16) & 0xFF
        return (red, green, blue)

    @property
    def text_alignment(self) -> ECMTextAlignment:
        """Text alignment derived from bits 24–25 of ``flags2``.

        Returns:
            ``ECMTextAlignment.CENTER`` if bit 24 is set,
            ``ECMTextAlignment.LEFT`` if bit 25 is set,
            ``ECMTextAlignment.RIGHT`` otherwise.
        """
        if self.flags2 & (1 << 24):
            return ECMTextAlignment.CENTER
        if self.flags2 & (1 << 25):
            return ECMTextAlignment.LEFT
        return ECMTextAlignment.RIGHT

    @property
    def data_type(self) -> ECMFieldDataType | None:
        """Logical data type derived from ``field_name`` prefix and ``dt`` code.

        Returns:
            The matching ``ECMFieldDataType`` member, or ``None`` for static UI elements
            (groups, labels, page controls, etc.) that have no database column.
        """
        if self.field_name.startswith("feld"):
            return ECMFieldDataType.STRING
        if self.field_name.startswith("zahl"):
            if self.flags & (1 << 18):
                return ECMFieldDataType.TIME
            if self.flags1 & (1 << 22):
                return ECMFieldDataType.DATE_TIME
            if self.dt == "0":
                return ECMFieldDataType.BOOLEAN
            if self.dt in ("9", "1"):
                return ECMFieldDataType.INTEGER
        if self.field_name.startswith("real"):
            return ECMFieldDataType.DOUBLE
        if self.field_name.startswith("datum"):
            return ECMFieldDataType.DATE
        return None

    @property
    def catalog(self) -> ECMCatalogType | None:
        """Catalog type derived from specific bits of ``flags``.

        Returns:
            The matching ``ECMCatalogType`` member, or ``None`` if no catalog bit is set.
            Bits are evaluated in the following priority order:
            ``DATABASE`` (bit 1), ``ADDON`` (bit 21), ``LIST`` (bit 7), ``TREE`` (bit 8),
            ``HIERARCHY_TREE`` (bit 9), ``STRUCTURE_TREE`` (bit 10).
        """
        if self.flags & (1 << 1):
            return ECMCatalogType.DATABASE
        if self.flags & (1 << 7):
            return ECMCatalogType.LIST
        if self.flags & (1 << 8):
            return ECMCatalogType.TREE
        if self.flags & (1 << 9):
            return ECMCatalogType.HIERARCHY_TREE
        if self.flags & (1 << 10):
            return ECMCatalogType.STRUCTURE_TREE
        if self.flags & (1 << 21):
            return ECMCatalogType.ADDON
        return None

    @property
    def label_visible(self) -> bool:
        """Whether the field label is visible.

        Returns:
            ``False`` if bit 5 of ``flags`` is set, ``True`` otherwise.
        """
        return not bool(self.flags & (1 << 5))

    @property
    def leading_zeros(self) -> bool:
        """Whether the field displays leading zeros.

        Returns:
            ``True`` if bit 6 of ``flags`` is set.
        """
        return bool(self.flags & (1 << 6))

    @property
    def multiline(self) -> bool:
        """Whether the field accepts multiline text input.

        Returns:
            ``True`` if bit 19 of ``flags`` is set.
        """
        return bool(self.flags & (1 << 19))

    @property
    def control_hidden(self) -> bool:
        """Whether the field control is hidden.

        Returns:
            ``True`` if bit 23 of ``flags1`` is set.
        """
        return bool(self.flags1 & (1 << 23))

    @property
    def regex_protected(self) -> bool:
        """Whether the field input is validated against a regular expression in a protected manner.

        Returns:
            ``True`` if bit 20 of ``flags1`` is set.
        """
        return bool(self.flags1 & (1 << 20))

    @property
    def crosscheck_position(self) -> int:
        """Cross-check position derived from bits 0–7 of ``flags1``.

        Returns:
            Integer value of bits 0–7 of ``flags1``, or ``0`` if not set.
        """
        return self.flags1 & 0xFF

    @property
    def crosscheck_target_tab_position(self) -> int:
        """Cross-check target tab position derived from bits 8–15 of ``flags1``.

        Returns:
            Integer value of bits 8–15 of ``flags1``, or ``0`` if not set.
        """
        return (self.flags1 >> 8) & 0xFF

    @property
    def default_value_mode(self) -> ECMDefaultValueMode | None:
        """How the default value of the field is determined.

        Returns:
            ``ECMDefaultValueMode.FUNCTION`` if bit 17 of ``flags1`` is set,
            ``ECMDefaultValueMode.CONSTANT`` if bit 16 of ``flags1`` is set,
            ``None`` otherwise.
        """
        if self.flags1 & (1 << 17):
            return ECMDefaultValueMode.FUNCTION
        if self.flags1 & (1 << 16):
            return ECMDefaultValueMode.CONSTANT
        return None

    @property
    def default_value_function(self) -> int:
        """Default value function identifier derived from bits 24–31 of ``flags1``.

        Returns:
            Integer value of bits 24–31 of ``flags1``.
        """
        return (self.flags1 >> 24) & 0xFF

    @property
    def mask_protected(self) -> bool:
        """Whether the field input is masked/protected.

        Returns:
            ``True`` if bit 18 of ``flags1`` is set.
        """
        return bool(self.flags1 & (1 << 18))

    @property
    def control(self) -> ECMControlType:
        """UI control type derived from the ``dt`` attribute.

        Returns:
            The matching ``ECMControlType`` member based on ``dt``.
        """
        _DT_MAP: dict[str, ECMControlType] = {
            "X": ECMControlType.TEXTFIELD,
            "P": ECMControlType.TEXTFIELD,
            "D": ECMControlType.TEXTFIELD,
            "9": ECMControlType.TEXTFIELD,
            "#": ECMControlType.TEXTFIELD,
            "R": ECMControlType.TEXTFIELD,
            "T": ECMControlType.TEXTFIELD,
            "S": ECMControlType.TEXTFIELD,
            "Q": ECMControlType.TEXTFIELD,
            "Z": ECMControlType.TEXTFIELD,
            "W": ECMControlType.TABLEFIELD,
            "1": ECMControlType.RADIOBUTTON,
            "0": ECMControlType.CHECKBOX,
            "B": ECMControlType.PICTURE,
            "G": ECMControlType.GROUP,
            "C": ECMControlType.PAGE_CONTROL,
            "E": ECMControlType.WEBCONTROL,
            "K": ECMControlType.BUTTON,
        }
        if not self.dt:
            return ECMControlType.LABEL
        return _DT_MAP.get(self.dt, ECMControlType.LABEL)

    @property
    def addon_auto_execute(self) -> bool:
        """Whether the add-on catalog is executed automatically on field entry.

        Returns:
            ``True`` if bit 11 of ``flags`` is set.
        """
        return bool(self.flags & (1 << 11))

    @property
    def is_static(self) -> bool:
        """Whether this field is a pure UI element without a database column.

        Returns:
            ``True`` if ``field_name`` is empty (groups, labels, page controls, etc.),
            ``False`` if the field is backed by a database column.
        """
        return not bool(self.field_name)


@dataclass(frozen=True)
class ECMPageDefinition:
    """Represents a single tab (``<page>``) within a PageControl field.

    Pages are nested inside a ``<field>`` element whose ``dt`` code is ``"C"``
    (PageControl). Each page groups a set of fields displayed on one tab sheet.
    Pages may themselves contain PageControl fields, producing nested tabs.

    Attributes:
        page_id: Server-side GUID identifying this page.
        internal_name: Internal (programmatic) name of the page, empty if not set.
        position: Zero-based display order of the tab within the PageControl.
        description: Name entry for the primary language, or ``None`` if not available.
        descriptions: All language-specific name entries, keyed by ISO locale code.
        fields: All field definitions belonging to this page, keyed by internal name.
    """

    page_id: str
    internal_name: str
    position: int
    description: ECMDescriptionEntry | None
    descriptions: dict[str, ECMDescriptionEntry]
    fields: dict[str, ECMFieldDefinition]
