"""The OCX Schema content classes."""
#  Copyright (c) 2022-2025. OCX Consortium https://3docx.org. See the LICENSE
from collections import defaultdict
from typing import Dict, List, Union
from loguru import logger
# Third party imports
from lxml.etree import Element, QName
from ocx_schema_parser.data_classes import OcxSchemaAttribute, OcxSchemaChild
# Module imports
from ocx_schema_parser.helpers import SchemaHelper
from ocx_schema_parser.xelement import LxmlElement
[docs]class OcxGlobalElement:
    """Global schema element class capturing the xsd schema definition of a global ``xs:element``.
    Args:
        xsd_element: The lxml.etree.Element class
    Attributes:
        _element: The ``lxml.Element`` instance
        _attributes: The attributes of the global element including the attributes of all schema supertypes
         _tag: The unique global tag of the ``OcXGlobalElement``
        _parents: Hash table of references to all parent schema types with tag as key
        _children: List of references to all children schema types with tag as key.
                        Includes also children of all super-types.
        -assertions: List of any assertions associated with the ``xs:element``
    """
    def __init__(self, xsd_element: Element, unique_tag: str, namespaces: Dict):
        # Private
        self._element: Element = xsd_element
        self._attributes: List[OcxSchemaAttribute] = []
        self._namespace: str = QName(unique_tag).namespace
        self._tag: str = unique_tag
        self._cardinality: tuple = LxmlElement.cardinality(xsd_element)
        self._children: List = []
        self._parents: Dict = {}
        self._assertions: List = []
        self._namespaces: Dict = namespaces
[docs]    def add_attribute(self, attribute: OcxSchemaAttribute):
        """Add attributes to the global element.
        Arguments:
            attribute : The attribute instance to be added
        """
        self._attributes.append(attribute) 
[docs]    def add_child(self, child: OcxSchemaChild):
        """Add a child of an OCX global element'
        Arguments:
            child: The added  child instance
        Returns:
            Nothing
        """
        self._children.append(child) 
[docs]    def add_assertion(self, test: str):
        """Add an assertion test associated to me
        Arguments:
            test: The definition of the assertion represented as a string
        Returns:
            Nothing
        """
        self._assertions.append(test) 
[docs]    def has_assertion(self) -> bool:
        """Whether the element has assertions or not'
        Returns:
             Tru if the global element as assertions, False otherwise
        """
        return len(self._assertions) > 0 
[docs]    def put_parent(self, tag: str, parent: Element):
        """Add a parent element
        Arguments:
            tag: The unique tag of the parent element
            parent: The parent xsd schema element
        Returns:
            None
        """
        self._parents[tag] = parent 
[docs]    def get_parents(self) -> dict:
        """Return all my parents
        Returns:
            Return all parents as a dict of key-value pairs ``(tag, Element)``
        """
        return self._parents 
[docs]    def get_parent_names(self) -> List:
        """Get all my parent names
        Returns:
            Return all parents names in a list
        """
        return [LxmlElement.strip_namespace_tag(tag) for tag in self._parents] 
[docs]    def get_assertion_tests(self) -> List:
        """Get all my assertions
        Returns:
            Assertion tests in a list
        """
        return self._assertions 
[docs]    def get_children(self) -> List:
        """Get all my children XSD types.
        Returns:
            Return all children as a dict of key-value pairs ``(tag, OCXChildElement)``
        """
        return self._children 
[docs]    def get_namespace(self) -> str:
        """The element _namespace
        Returns:
            The _namespace of the global schema element as a str
        """
        return self._namespace 
[docs]    def get_attributes(self) -> List:
        """The global element attributes including also parent attributes
        Returns:
            A dict of all attributes including also parent attributes
        """
        return self._attributes 
[docs]    def get_name(self) -> str:
        """The global element name
        Returns:
            The name of the global schema element as a str
        """
        return LxmlElement.get_name(self._element) 
[docs]    def get_annotation(self) -> str:
        """The global element annotation or description
        Returns:
            The annotation string of the element
        """
        annotation = LxmlElement.find_child_with_name(self._element, "annotation")
        if annotation is not None:
            return LxmlElement.get_element_text(annotation) 
[docs]    def get_type(self) -> str:
        """The global element type
        Returns:
            The type of the global schema element as a str
        """
        return SchemaHelper.get_type(self._element) 
[docs]    def get_prefix(self) -> str:
        """The global element _namespace prefix
        Returns:
            The namespace prefix of the global schema element
        """
        nsprefix = list(iter(self._namespaces))
        nstags = list(iter(self._namespaces.values()))
        prefix = ""
        try:
            index = nstags.index(self._namespace)
            prefix = nsprefix[index]
            if prefix is None:
                nstags.pop(index)
                nsprefix.pop(index)
                index = nstags.index(self._namespace)
                prefix = nsprefix.index(index)
        except ValueError as e:
            logger.error(f"{self._namespace} is not in the namespace list: {e}")
        if prefix == "":
            logger.debug(f"Empty namespace prefix in global elem,ent {self.get_name()}")
        return prefix 
[docs]    def get_schema_element(self) -> Element:
        """Get the schema xsd element of the ``OcxSchemeElement`` object
        Returns:
            My xsd schema element
        """
        return self._element 
[docs]    def put_cardinality(self, element: Element):
        """Override the cardinality of the OcxGlobalElement
        Args:
            element: the etree.Element node
        """
        self._cardinality = LxmlElement.cardinality(element) 
[docs]    def get_cardinality(self) -> str:
        """Get the cardinality of the OcxGlobalElement
        Returns:
            The cardinality as sting represented by [lower, upper]
        """
        lower, upper = self._cardinality
        if upper == "unbounded":
            upper = "\u221e"  # UTF-8 Infinity symbol
        return f"[{lower}, {upper}]" 
[docs]    def is_reference(self) -> bool:
        """Whether the element has a reference or not
        Returns:
            is_reference : True if the element has a reference, False otherwise
        """
        return LxmlElement.is_reference(self._element) 
[docs]    def is_mandatory(self) -> bool:
        """Whether the element mandatory or not
        Returns:
            Returns True if the element is mandatory, False otherwise
        """
        return LxmlElement.is_mandatory(self._element) 
[docs]    def is_choice(self) -> bool:
        """Whether the element is a choice or not
        Returns:
            True if the element is a choice, False otherwise
        """
        return LxmlElement.is_choice(self._element) 
[docs]    def is_substitution_group(self) -> bool:
        """Whether the element is part of a substitutionGroup
        Returns:
            True if the element is a substitutionGroup, False otherwise
        """
        return LxmlElement.is_substitution_group(self._element) 
[docs]    def is_abstract(self) -> bool:
        """Whether the element is abstract
        Returns:
            True if the element is abstract, False otherwise
        """
        return LxmlElement.is_abstract(self._element) 
[docs]    def get_substitution_group(self) -> Union[str, None]:
        """Return the name of the substitutionGroup
        Returns:
            The name of the ``substitutionGroup``, None otherwise
        """
        return LxmlElement.get_substitution_group(self._element) 
[docs]    def get_tag(self) -> str:
        """The global schema element unique tag
        Returns:
            The element tag on the form  ``{prefix}name``
        """
        return self._tag 
[docs]    def get_use(self) -> str:
        """The element's use, required or optional
        Returns:
            The element use:  ``req.`` if mandatory, else ``opt``
        """
        use = "opt"
        if self.is_mandatory():
            use = "req"
        return use 
[docs]    def get_properties(self) -> Dict:
        """A dictionary of all ``OcxGlobalElement`` property values
        Returns:
           main: A dictionary of property values with heading keys:
            .. list-table:: Heading keys
               :widths: 25 25 25 25 25 50
               * - Name
                 - Type
                 - Use
                 - Cardinality
                 - Fixed
                 - Description
        """
        table = defaultdict(list)
        table["Name"].append(self.get_name())
        table["Type"].append(self.get_type())
        table["Use"].append(self.get_use())
        table["Cardinality"].append(self.get_cardinality())
        table["Description"].append(self.get_annotation())
        return table 
[docs]    def attributes_to_dict(self) -> Dict:
        """A dictionary of all ``OcxGlobalElement`` attribute values
        Returns:
            A dictionary of attribute values with heading keys
            .. list-table:: Heading keys
               :widths: 25 25 25 25 25 50
               * - Attribute
                 - Type
                 - Use
                 - Default
                 - Fixed
                 - Description
        """
        table = defaultdict(list)
        for attr in self._attributes:
            attributes = attr.to_dict()
            for a in attributes:
                table[a].append(attributes[a])
        return table 
[docs]    def children_to_dict(self) -> Dict:
        """A dictionary of all ``OcxGlobalElement`` children values
        Returns:
            main: A dictionary of attribute values with heading keys:
            .. list-table:: Heading keys
               :widths: 25 25 25 25 50
               * - Child
                 - Type
                 - Use
                 - Cardinality
                 - Description
        """
        table = defaultdict(list)
        for child in sorted(self._children, key=lambda x: x.name):
            attributes = child.to_dict()
            for a in attributes:
                table[a].append(attributes[a])
        return table