"""Implementation of data types used in Participant Model."""
from __future__ import annotations

import inspect
import sys
from dataclasses import dataclass
from decimal import Decimal
from typing import TYPE_CHECKING

from sdc11073.namespaces import QN_TYPE, text_to_qname

from . import ext_qnames as ext
from . import msg_qnames as msg
from . import pm_qnames as pm
from . import xml_structure as cp
from .basetypes import StringEnum, XMLTypeBase

if TYPE_CHECKING:
    from lxml import etree

    from sdc11073 import xml_utils
    from sdc11073.xml_types.isoduration import DateTypeUnion, DurationType


class SafetyClassification(StringEnum):
    """Represents BICEPS SafetyClassification."""

    INF = 'Inf'
    MED_A = 'MedA'
    MED_B = 'MedB'
    MED_C = 'MedC'


class MdsOperatingMode(StringEnum):
    """Represents BICEPS MdsOperatingMode."""

    NORMAL = 'Nml'
    DEMO = 'Dmo'
    SERVICE = 'Srv'
    MAINTENANCE = 'Mtn'


class ComponentActivation(StringEnum):
    """Represents BICEPS ComponentActivation."""

    ON = 'On'
    NOT_READY = 'NotRdy'
    STANDBY = 'StndBy'
    OFF = 'Off'
    SHUTDOWN = 'Shtdn'
    FAILURE = 'Fail'


class ContextAssociation(StringEnum):
    """Represents BICEPS ContextAssociation."""

    NO_ASSOCIATION = 'No'
    PRE_ASSOCIATION = 'Pre'
    ASSOCIATED = 'Assoc'
    DISASSOCIATED = 'Dis'


class AlertConditionMonitoredLimits(StringEnum):
    """Represents BICEPS AlertConditionMonitoredLimits."""

    ALL_ON = 'All'
    LOW_OFF = 'LoOff'
    HIGH_OFF = 'HiOff'
    NONE = 'None'


class AlertConditionPriority(StringEnum):
    """Represents BICEPS AlertConditionPriority."""

    NONE = 'None'
    LOW = 'Lo'
    MEDIUM = 'Me'
    HIGH = 'Hi'


class AlertConditionKind(StringEnum):
    """Represents BICEPS AlertConditionKind."""

    PHYSIOLOGICAL = 'Phy'
    TECHNICAL = 'Tec'
    OTHER = 'Oth'


class CanEscalate(StringEnum):
    """Represents BICEPS CanEscalate."""

    LOW = 'Lo'
    MEDIUM = 'Me'
    HIGH = 'Hi'


class CanDeEscalate(StringEnum):
    """Represents BICEPS CanDeEscalate."""

    MEDIUM = 'Me'
    LOW = 'Lo'
    NONE = 'None'


class AlertSignalPresence(StringEnum):
    """Represents BICEPS AlertSignalPresence."""

    ON = 'On'
    OFF = 'Off'
    LATCH = 'Latch'
    ACK = 'Ack'


class MetricAvailability(StringEnum):
    """Represents BICEPS MetricAvailability."""

    INTERMITTENT = 'Intr'
    CONTINUOUS = 'Cont'


class MetricCategory(StringEnum):
    """Represents BICEPS MetricCategory."""

    UNSPECIFIED = 'Unspec'
    MEASUREMENT = 'Msrmt'
    CALCULATION = 'Clc'
    SETTING = 'Set'
    PRESETTING = 'Preset'
    RECOMMENDATION = 'Rcmm'


class DerivationMethod(StringEnum):
    """Represents BICEPS DerivationMethod."""

    AUTOMATIC = 'Auto'
    MANUAL = 'Man'


class AccessLevel(StringEnum):
    """Represents BICEPS AccessLevel."""

    USER = 'Usr'
    CLINICAL_SUPER_USER = 'CSUsr'
    RESPONSIBLE_ORGANIZATION = 'RO'
    SERVICE_PERSONNEL = 'SP'
    OTHER = 'Oth'


class AlertSignalPrimaryLocation(StringEnum):
    """Represents BICEPS AlertSignalPrimaryLocation."""

    LOCAL = 'Loc'
    REMOTE = 'Rem'


class LocalizedTextWidth(StringEnum):
    """Represents BICEPS LocalizedTextWidth."""

    XS = 'xs'
    S = 's'
    M = 'm'
    L = 'l'
    XL = 'xl'
    XXL = 'xxl'


class OperatingMode(StringEnum):
    """Represents BICEPS OperatingMode."""

    DISABLED = 'Dis'
    ENABLED = 'En'
    NA = 'NA'


OperatingModeType = OperatingMode  # TypeAlias


class MeasurementValidity(StringEnum):
    """Represents BICEPS MeasurementValidity."""

    VALID = 'Vld'
    VALIDATED_DATA = 'Vldated'
    MEASUREMENT_ONGOING = 'Ong'
    QUESTIONABLE = 'Qst'
    CALIBRATION_ONGOING = 'Calib'
    INVALID = 'Inv'
    OVERFLOW = 'Oflw'
    UNDERFLOW = 'Uflw'
    NA = 'NA'


class GenerationMode(StringEnum):
    """Represents BICEPS MeasurementValidity.

    Describes whether METRIC data is generated by real measurements or under unreal settings (demo or test data).
    """

    REAL = 'Real'  # Real Data. A value that is generated under real conditions
    TEST = 'Test'  # Test Data. A value that is arbitrary and is for testing purposes only
    DEMO = 'Demo'  # Demo Data. A value that is arbitrary and is for demonstration purposes only


class AlertSignalManifestation(StringEnum):
    """Represents BICEPS AlertSignalManifestation."""

    AUD = 'Aud'  # Aud = Audible. The ALERT SIGNAL manifests in an audible manner, i.e., the alert can be heard.
    #  Example: an alarm sound.
    VIS = 'Vis'  # Vis = Visible. The ALERT SIGNAL manifests in a visible manner, i.e., the alert can be seen.
    #  Example: a red flashing light.
    TAN = 'Tan'  # Tan = Tangible. The ALERT SIGNAL manifests in a tangible manner, i.e., the alert can be felt.
    #  Example: vibration.
    OTH = 'Oth'  # Oth = Other. The ALERT SIGNAL manifests in a manner not further specified.


class AlertActivation(StringEnum):
    """Represents BICEPS AlertActivation."""

    ON = 'On'
    OFF = 'Off'
    PAUSED = 'Psd'


class AbstractMetricDescriptorRelationKindEnum(StringEnum):
    """Represents BICEPS AbstractMetricDescriptor/Relation/Kind Definition."""

    RECOMMENDATION = 'Rcm'
    PRE_SETTING = 'PS'
    SET_OF_SUMMARY_STATISTICS = 'SST'
    EFFECT_ON_CONTAINMENT_TREE_ENTRIES = 'ECE'
    DERIVED_FROM_CONTAINMENT_TREE_ENTRIES = 'DCE'
    OTHER = 'Oth'


class PatientType(StringEnum):
    """Represents BICEPS PatientType."""

    UNSPECIFIED = 'Unspec'
    ADULT = 'Ad'
    ADOLESCENT = 'Ado'
    PEDIATRIC = 'Ped'
    INFANT = 'Inf'
    NEONATAL = 'Neo'
    OTHER = 'Oth'


PatientTypeType = PatientType  # TypeAlias


class Sex(StringEnum):
    """Represents BICEPS Sex."""

    UNSPEC = 'Unspec'
    MALE = 'M'
    FEMALE = 'F'
    UNKNOWN = 'Unkn'


SexType = Sex  # TypeAlias


class CalibrationState(StringEnum):  # only used in CalibrationInfo
    """Represents BICEPS CalibrationState."""

    NOT_CALIBRATED = 'No'
    CALIBRATION_REQUIRED = 'Req'
    RUNNING = 'Run'
    CALIBRATED = 'Cal'
    OTHER = 'Oth'


class CalibrationType(StringEnum):  # only used in CalibrationInfo
    """Represents BICEPS CalibrationType."""

    OFFSET = 'Offset'
    GAIN = 'Gain'
    TWO_POINT_CALIBRATION = 'TP'
    UNSPEC = 'Unspec'


class PropertyBasedPMType(XMLTypeBase):
    """PropertyBasedPMType classes handle the XMLSchema-instance:Type attribute.

    The value_class_from_node method returns a matching specific class.
    it looks for classes with a matching NODETYPE member.
    """

    @classmethod
    def value_class_from_node(cls, node: xml_utils.LxmlElement) -> type[PropertyBasedPMType]:
        """If node has a xsi:Type attribute, return the class that reflects that type, else cls."""
        xsi_type_str = node.get(QN_TYPE)
        if xsi_type_str is None:
            return cls
        xsi_type = text_to_qname(xsi_type_str, node.nsmap)
        return _get_pmtypes_class(xsi_type)


class LocalizedText(PropertyBasedPMType):
    """Represents BICEPS LocalizedText."""

    NODETYPE = pm.LocalizedText
    text: str = cp.NodeStringProperty()  # this is the text of the node. Here attribute is lower case!
    Ref: str | None = cp.LocalizedTextRefAttributeProperty('Ref')
    Lang: str | None = cp.StringAttributeProperty('Lang')
    Version: int | None = cp.ReferencedVersionAttributeProperty('Version')
    TextWidth = cp.EnumAttributeProperty('TextWidth', enum_cls=LocalizedTextWidth)
    _props = ('text', 'Ref', 'Lang', 'Version', 'TextWidth')

    def __init__(self, text: str,
                 lang: str | None = None,
                 ref: str | None = None,
                 version: int | None = None,
                 text_width: LocalizedTextWidth | None = None):
        super().__init__()
        self.text = text
        self.Lang = lang
        self.Ref = ref
        self.Version = version
        self.TextWidth = text_width

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> LocalizedText:
        """Construct class from a node."""
        obj = cls('')
        obj.update_from_node(node)
        return obj

    def __repr__(self) -> str:
        params = [f'"{self.text}"']
        if self.Lang is not None:
            params.append(f'Lang={self.Lang}')
        if self.Ref is not None:
            params.append(f'Ref={self.Ref}')
        if self.Version is not None:
            params.append(f'Version={self.Version}')
        if self.TextWidth is not None:
            params.append(f'TextWidth={self.TextWidth}')
        return f'{self.__class__.__name__}({", ".join(params)})'


DEFAULT_CODING_SYSTEM = 'urn:oid:1.2.840.10004.1.1.1.0.0.1'  # ISO/IEC 11073-10101


@dataclass(frozen=True)
class Coding:
    """Coding contains the three Values that identify a CodedValue.

    It is a frozen class, it can be used as Key in dictionaries.
    """

    code: str
    coding_system: str = DEFAULT_CODING_SYSTEM
    coding_system_version: str | None = None

    def __post_init__(self):
        """Previous versions allowed also an int as code. This exception makes it easier to detect wrong type."""
        if not isinstance(self.code, str):
            raise TypeError('code must be a string!')

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> Coding:
        """Construct class from a node."""
        code = node.get('Code')
        coding_system = node.get('CodingSystem', DEFAULT_CODING_SYSTEM)
        coding_system_version = node.get('CodingSystemVersion')
        return cls(code, coding_system, coding_system_version)


class Translation(PropertyBasedPMType):
    """Represents BICEPS Translation."""

    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Code: str = cp.CodeIdentifierAttributeProperty('Code', is_optional=False)
    CodingSystem: str | None = cp.StringAttributeProperty('CodingSystem', implied_py_value=DEFAULT_CODING_SYSTEM)
    CodingSystemVersion: str | None = cp.StringAttributeProperty('CodingSystemVersion')

    _props = ('ExtExtension', 'Code', 'CodingSystem', 'CodingSystemVersion')

    def __init__(self, code: str,
                 coding_system: str | None = None,
                 coding_system_version: str | None = None):
        super().__init__()
        if code is not None and not isinstance(code, str):
            raise TypeError('code must be a string!')
        self.Code = code
        self.CodingSystem = coding_system
        self.CodingSystemVersion = coding_system_version

    @property
    def coding(self) -> Coding:
        """Return Coding."""
        return Coding(self.Code, self.CodingSystem, self.CodingSystemVersion)

    def __repr__(self) -> str:
        if self.CodingSystem is None:
            return f'CodedValue({self.Code})'
        if self.CodingSystemVersion is None:
            return f'CodedValue({self.Code}, codingsystem={self.CodingSystem})'
        return (f'CodedValue({self.Code}, codingsystem={self.CodingSystem}, '
                f'codingsystemversion={self.CodingSystemVersion})')

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> Translation:
        """Construct class from a node."""
        obj = cls('')
        obj.update_from_node(node)
        return obj


TranslationType = Translation  # TypeAlias


class CodedValue(PropertyBasedPMType):
    """Represents BICEPS CodedValue."""

    NODETYPE = pm.CodedValue
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    CodingSystemName: list[LocalizedText] | None = cp.SubElementListProperty(pm.CodingSystemName,
                                                                             value_class=LocalizedText)
    ConceptDescription: list[LocalizedText] | None = cp.SubElementListProperty(pm.ConceptDescription,
                                                                               value_class=LocalizedText)
    Translation: list[TranslationType] = cp.SubElementListProperty(pm.Translation, value_class=Translation)
    Code: str = cp.CodeIdentifierAttributeProperty('Code', is_optional=False)
    CodingSystem: str | None = cp.StringAttributeProperty('CodingSystem', implied_py_value=DEFAULT_CODING_SYSTEM)
    CodingSystemVersion: str | None = cp.StringAttributeProperty('CodingSystemVersion')
    SymbolicCodeName: str | None = cp.SymbolicCodeNameAttributeProperty('SymbolicCodeName')
    _props = ('ExtExtension', 'CodingSystemName', 'ConceptDescription', 'Translation',
              'Code', 'CodingSystem', 'CodingSystemVersion', 'SymbolicCodeName')

    def __init__(self, code: str,  # noqa: PLR0913
                 coding_system: str | None = None,
                 coding_system_version: str | None = None,
                 coding_system_names: list[LocalizedText] | None = None,
                 concept_descriptions: list[LocalizedText] | None = None,
                 symbolic_code_name: str | None = None):
        super().__init__()
        if code is not None and not isinstance(code, str):
            raise TypeError('code must be a string!')
        self.Code = code
        self.CodingSystem = coding_system
        self.CodingSystemVersion = coding_system_version
        self.CodingSystemName = [] if coding_system_names is None else coding_system_names
        self.ConceptDescription = [] if concept_descriptions is None else concept_descriptions
        self.SymbolicCodeName = symbolic_code_name

    @property
    def coding(self) -> Coding:
        """Return Coding."""
        return Coding(self.Code, self.CodingSystem, self.CodingSystemVersion)

    @property
    def all_codings(self) -> list[Coding]:
        """Return Coding of self plus Codings of all Translations."""
        ret = [self.coding]
        if self.Translation is not None:
            ret.extend([t.coding for t in self.Translation])
        return ret

    def __repr__(self) -> str:
        if self.CodingSystem is None:
            return f'CodedValue({self.Code})'
        if self.CodingSystemVersion is None:
            return f'CodedValue({self.Code}, codingsystem="{self.CodingSystem}")'
        return (f'CodedValue({self.Code}, codingsystem="{self.CodingSystem}", '
                f'codingsystemversion="{self.CodingSystemVersion}")')

    def is_equivalent(self, other: Coding | CodedValue) -> bool:
        """Compare with a CodedValue or a Coding.

        BICEPS specifies equivalence as follows:
        Two CodedValue objects C1 and C2 are equivalent, if
        - C1/@Code equals C2/@Code
        - C1/@CodingSystem equals C2/@CodingSystem, both with expanded default values
        - C1/@CodingSystemVersion equals C2/@CodingSystemVersion
        - If there exists a CodedValue object T1 in C1/pm:Translation and a CodedValue object T2 in C2/pm:Translation
          such that T1 and T2 are equivalent, C1 and T2 are equivalent, or C2 and T1 are equivalent.

        NOTE 1—In case that ./@CodingSystem is not explicitly defined in CodedValue, it is replaced implicitly by
               a default identifier. The ./@CodingSystem ATTRIBUTE is then called "expanded".
        NOTE 2—As prescribed in ./@CodingSystemVersion, a version is set only if a unique version identification
               by ./@CodingSystem is not possible. Hence, there can be no implicit version mismatch.
        NOTE 3—Equivalence between CodedValue objects is not necessarily transitive.
        """
        return have_matching_codes(self, other)

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> CodedValue:
        """Construct class from a node."""
        obj = cls('')
        obj.update_from_node(node)
        return obj


def have_matching_codes(code_a: CodedValue | Coding, code_b: CodedValue | Coding) -> bool:
    """Test if there is at least one common Coding in code_a and code_b."""
    try:
        codes_a = set(code_a.all_codings)
    except AttributeError:
        codes_a = {code_a}
    try:
        codes_b = set(code_b.all_codings)
    except AttributeError:
        codes_b = {code_b}
    common_codes = codes_a.intersection(codes_b)
    return len(common_codes) > 0


class Annotation(PropertyBasedPMType):
    """Represents BICEPS AbstractMetricValue/Annotation."""

    NODETYPE = pm.Annotation
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Type: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue)
    _props = ('ExtExtension', 'Type')

    def __init__(self, coded_value: CodedValue | None = None):
        super().__init__()
        self.Type = coded_value


AnnotationType = Annotation  # TypeAlias


class OperationGroup(PropertyBasedPMType):
    """Represents BICEPS ScoState/OperationGroup."""

    NODETYPE = pm.OperationGroup
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Type: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue)
    OperatingMode: OperatingModeType | None = cp.EnumAttributeProperty('OperatingMode', enum_cls=OperatingMode)
    Operations = cp.OperationRefListAttributeProperty('Operations')
    _props = ('ExtExtension', 'Type', 'OperatingMode', 'Operations')

    def __init__(self, coded_value: CodedValue | None = None,
                 operating_mode: OperatingModeType | None = None,
                 operations: list[str] | None = None):
        super().__init__()
        self.Type = coded_value
        self.OperatingMode = operating_mode
        self.Operations = operations


class InstanceIdentifier(PropertyBasedPMType):
    """Represents BICEPS InstanceIdentifier."""

    NODETYPE = pm.InstanceIdentifier
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Type: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue, is_optional=True)
    IdentifierName: list[LocalizedText] = cp.SubElementListProperty(pm.IdentifierName, value_class=LocalizedText)
    Root: str | None = cp.AnyURIAttributeProperty('Root')
    # default_py_value='biceps.uri.unk')  # default is defined in R0135
    Extension = cp.ExtensionAttributeProperty('Extension')  # a xsd:string
    _props = ('ExtExtension', 'Type', 'IdentifierName', 'Root', 'Extension')

    def __init__(self, root: str | None = None,
                 type_coded_value: CodedValue | None = None,
                 identifier_names: list[LocalizedText] | None = None,
                 extension_string: str | None = None):
        super().__init__()
        self.Root = root
        self.Type = type_coded_value
        self.IdentifierName = [] if identifier_names is None else identifier_names
        self.Extension = extension_string
        self.node = None

    def __repr__(self) -> str:
        params = [f'root={self.Root!r}']
        if self.Type is not None:
            params.append(f'Type={self.Type}')
        if len(self.IdentifierName) > 0:
            params.append(f'IdentifierName={[str(n) for n in self.IdentifierName]}')
        return f'InstanceIdentifier({", ".join(params)})'


class OperatingJurisdiction(InstanceIdentifier):
    """Represents BICEPS OperatingJurisdiction."""

    NODETYPE = pm.OperatingJurisdiction


class Range(PropertyBasedPMType):
    """Represents BICEPS Range."""

    NODETYPE = pm.Range
    Extension = cp.ExtensionNodeProperty(ext.Extension)
    Lower: Decimal | None = cp.DecimalAttributeProperty('Lower')
    Upper: Decimal | None = cp.DecimalAttributeProperty('Upper')
    StepWidth: Decimal | None = cp.DecimalAttributeProperty('StepWidth')
    RelativeAccuracy: Decimal | None = cp.DecimalAttributeProperty('RelativeAccuracy')
    AbsoluteAccuracy: Decimal | None = cp.DecimalAttributeProperty('AbsoluteAccuracy')
    _props = ('Extension', 'Lower', 'Upper', 'StepWidth', 'RelativeAccuracy', 'AbsoluteAccuracy')

    def __init__(self,
                 lower: Decimal | None = None,
                 upper: Decimal | None = None,
                 step_width: Decimal | None = None,
                 relative_accuracy: Decimal | None = None,
                 absolute_accuracy: Decimal | None = None):
        super().__init__()
        self.Lower = lower
        self.Upper = upper
        self.StepWidth = step_width
        self.RelativeAccuracy = relative_accuracy
        self.AbsoluteAccuracy = absolute_accuracy

    def __repr__(self) -> str:
        return (f'Range (Lower={self.Lower!r}, Upper={self.Upper!r}, StepWidth={self.StepWidth!r}, '
                f'RelativeAccuracy={self.RelativeAccuracy!r}, AbsoluteAccuracy={self.AbsoluteAccuracy!r})')


RangeType = Range  # TypeAlias


class Measurement(PropertyBasedPMType):
    """Represents BICEPS Measurement."""

    NODETYPE = pm.Measurement
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    MeasurementUnit: CodedValue = cp.SubElementProperty(pm.MeasurementUnit, value_class=CodedValue)
    MeasuredValue: Decimal = cp.DecimalAttributeProperty('MeasuredValue', is_optional=False)
    _props = ('ExtExtension', 'MeasurementUnit', 'MeasuredValue')

    def __init__(self,
                 value: Decimal | None,
                 unit: CodedValue | None):
        super().__init__()
        self.MeasuredValue = value
        self.MeasurementUnit = unit

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> Measurement:
        """Construct class from a node."""
        obj = cls(None, None)
        obj.update_from_node(node)
        return obj

    def __repr__(self) -> str:
        return f'Measurement(value={self.MeasuredValue!r}, Unit={self.MeasurementUnit!r})'


class AllowedValue(PropertyBasedPMType):
    """One AllowedValue of a EnumStringMetricDescriptor."""

    NODETYPE = pm.AllowedValue
    Value: str = cp.NodeStringProperty(pm.Value)
    Type: CodedValue | None = cp.SubElementProperty(pm.Type,
                                                    value_class=CodedValue,
                                                    is_optional=True)
    Identification: CodedValue | None = cp.SubElementProperty(pm.Identification,
                                                              value_class=InstanceIdentifier,
                                                              is_optional=True)
    Characteristic: Measurement | None = cp.SubElementProperty(pm.Characteristic,
                                                               value_class=Measurement,
                                                               is_optional=True)
    _props = ('Value', 'Type', 'Identification', 'Characteristic')

    def __init__(self, value: str = '',
                 type_coding: CodedValue | None = None,
                 identification: CodedValue | None = None,
                 characteristic: Measurement | None = None):
        """Construct one AllowedValue of a EnumStringMetricDescriptor."""
        super().__init__()
        self.Value = value
        self.Type = type_coding
        self.Identification = identification
        self.Characteristic = characteristic


class MetricQuality(PropertyBasedPMType):
    """Represents BICEPS AbstractMetricValue/MetricQuality."""

    NODETYPE = pm.MetricQuality
    Validity: MeasurementValidity = cp.EnumAttributeProperty('Validity',
                                                             enum_cls=MeasurementValidity,
                                                             is_optional=False,
                                                             default_py_value=MeasurementValidity.VALID)
    Mode: GenerationMode | None = cp.EnumAttributeProperty('Mode',
                                                           implied_py_value=GenerationMode.REAL,
                                                           enum_cls=GenerationMode)
    Qi: Decimal | None = cp.QualityIndicatorAttributeProperty('Qi', implied_py_value=Decimal('1'))
    _props = ('Validity', 'Mode', 'Qi')


MetricQualityType = MetricQuality  # TypeAlias


class AbstractMetricValue(PropertyBasedPMType):
    """Represents BICEPS AbstractMetricValue."""

    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    StartTime: float = cp.TimestampAttributeProperty('StartTime')
    StopTime: float = cp.TimestampAttributeProperty('StopTime')
    DeterminationTime: float = cp.TimestampAttributeProperty('DeterminationTime')
    MetricQuality: MetricQualityType = cp.SubElementProperty(pm.MetricQuality,
                                                             value_class=MetricQuality,
                                                             default_py_value=MetricQuality())
    Annotation: list[AnnotationType] = cp.SubElementListProperty(pm.Annotation, Annotation)
    _props = ('ExtExtension', 'StartTime', 'StopTime', 'DeterminationTime', 'MetricQuality', 'Annotation')


class NumericMetricValue(AbstractMetricValue):
    """Represents BICEPS NumericMetricValue."""

    NODETYPE = pm.NumericMetricValue
    Value: Decimal | None = cp.DecimalAttributeProperty('Value')
    _props = ('Value',)

    def __repr__(self) -> str:
        return (f'{self.__class__.__name__} Validity={self.MetricQuality.Validity}'
                f' Value={self.Value} DeterminationTime={self.DeterminationTime}')


class StringMetricValue(AbstractMetricValue):
    """Represents BICEPS StringMetricValue."""

    NODETYPE = pm.StringMetricValue
    Value: str | None = cp.StringAttributeProperty('Value')
    _props = ('Value',)

    def __repr__(self) -> str:
        return (f'{self.__class__.__name__} Validity={self.MetricQuality.Validity} '
                f'Value={self.Value} DeterminationTime={self.DeterminationTime}')


class ApplyAnnotation(PropertyBasedPMType):
    """Represents BICEPS SampleArrayValue/ApplyAnnotation."""

    NODETYPE = pm.ApplyAnnotation
    AnnotationIndex: int = cp.UnsignedIntAttributeProperty('AnnotationIndex', is_optional=False)
    SampleIndex: int = cp.UnsignedIntAttributeProperty('SampleIndex', is_optional=False)
    _props = ('AnnotationIndex', 'SampleIndex')

    def __init__(self, annotation_index: int | None = None, sample_index: int | None = None):
        super().__init__()
        self.AnnotationIndex = annotation_index
        self.SampleIndex = sample_index

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(AnnotationIndex={self.AnnotationIndex}, SampleIndex={self.SampleIndex})'


ApplyAnnotationType = ApplyAnnotation  # TypeAlias


class SampleArrayValue(AbstractMetricValue):
    """Represents BICEPS SampleArrayValue."""

    NODETYPE = pm.SampleArrayValue
    Samples: list[Decimal] = cp.DecimalListAttributeProperty('Samples')  # list of xs:decimal types
    ApplyAnnotation: list[ApplyAnnotationType] = cp.SubElementListProperty(pm.ApplyAnnotation, ApplyAnnotation)
    _props = ('Samples', 'ApplyAnnotation')

    def __repr__(self) -> str:
        return f'{self.__class__.__name__} Samples={self.Samples} ApplyAnnotation={self.ApplyAnnotation}'


class RemedyInfo(PropertyBasedPMType):
    """Represents BICEPS RemedyInfo."""

    NODETYPE = pm.RemedyInfo
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Description: list[LocalizedText] = cp.SubElementListProperty(pm.Description, value_class=LocalizedText)
    _props = ('ExtExtension', 'Description')

    def __init__(self, descriptions: list[LocalizedText] | None = None):
        super().__init__()
        if descriptions:
            self.Description = descriptions


RemedyInfoType = RemedyInfo  # TypeAlias


class CauseInfo(PropertyBasedPMType):
    """Represents BICEPS CauseInfo."""

    NODETYPE = pm.CauseInfo
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    RemedyInfo: RemedyInfoType | None = cp.SubElementProperty(pm.RemedyInfo, value_class=RemedyInfo)
    Description: list[LocalizedText] = cp.SubElementListProperty(pm.Description, value_class=LocalizedText)
    _props = ('ExtExtension', 'RemedyInfo', 'Description')

    def __init__(self, remedy_info: RemedyInfoType | None = None,
                 descriptions: list[LocalizedText] | None = None):
        super().__init__()
        self.RemedyInfo = remedy_info
        self.Description = descriptions or []


class ActivateOperationDescriptorArgument(PropertyBasedPMType):
    """Represents BICEPS AbstractSetStateOperationDescriptor/Argument."""

    ArgName: CodedValue = cp.SubElementProperty(pm.ArgName, value_class=CodedValue, is_optional=False)
    Arg: xml_utils.QName | None = cp.NodeTextQNameProperty(pm.Arg, is_optional=False)
    _props = ('ArgName', 'Arg')

    def __init__(self, arg_name: CodedValue | None = None, arg: xml_utils.QName | None = None):
        super().__init__()
        self.ArgName = arg_name
        self.Arg = arg

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(argName={self.ArgName}, arg={self.Arg})'


class PhysicalConnectorInfo(PropertyBasedPMType):
    """Represents BICEPS PhysicalConnectorInfo.

    PhysicalConnectorInfo defines a number in order to allow to guide the clinical user for a failure,
    e.g., in case of a disconnection of a sensor or an ultrasonic handpiece.
    """

    NODETYPE = pm.PhysicalConnectorInfo
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Label: list[LocalizedText] = cp.SubElementListProperty(pm.Label,
                                                           value_class=LocalizedText)
    Number: int | None = cp.IntegerAttributeProperty('Number')
    _props = ('ExtExtension', 'Label', 'Number')

    def __init__(self, labels: list[LocalizedText] | None = None,
                 number: int | None = None):
        super().__init__()
        self.Label = labels or []
        self.Number = number

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(label={self.Label}, number={self.Number})'


class SystemSignalActivation(PropertyBasedPMType):
    """Represents BICEPS SystemSignalActivation."""

    NODETYPE = pm.SystemSignalActivation
    Manifestation: AlertSignalManifestation = cp.EnumAttributeProperty('Manifestation',
                                                                       enum_cls=AlertSignalManifestation,
                                                                       default_py_value=AlertSignalManifestation.OTH,
                                                                       is_optional=False)
    State: AlertActivation = cp.EnumAttributeProperty('State', default_py_value=AlertActivation.ON,
                                                      enum_cls=AlertActivation, is_optional=False)
    _props = ('Manifestation', 'State')

    def __init__(self, manifestation: AlertSignalManifestation | None = None,
                 state: AlertActivation | None = None):
        super().__init__()
        self.Manifestation = manifestation
        self.State = state

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}(Manifestation={self.Manifestation}, State={self.State})'


class ProductionSpecification(PropertyBasedPMType):
    """Represents BICEPS AbstractDeviceComponentDescriptor/ProductionSpecification."""

    NODETYPE = pm.ProductionSpecification
    SpecType: CodedValue = cp.SubElementProperty(pm.SpecType, value_class=CodedValue)
    ProductionSpec: str = cp.NodeStringProperty(pm.ProductionSpec)
    ComponentId: InstanceIdentifier | None = cp.SubElementProperty(pm.ComponentId,
                                                                   value_class=InstanceIdentifier, is_optional=True)
    _props = ('SpecType', 'ProductionSpec', 'ComponentId')

    def __init__(self, spec_type: CodedValue | None = None,
                 production_spec: str | None = None,
                 component_id: InstanceIdentifier | None = None):
        super().__init__()
        self.SpecType = spec_type
        self.ProductionSpec = production_spec
        self.ComponentId = component_id


class BaseDemographics(PropertyBasedPMType):
    """Represents BICEPS BaseDemographics."""

    NODETYPE = pm.BaseDemographics
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Givenname: str | None = cp.NodeStringProperty(pm.Givenname, is_optional=True)
    Middlename: list[str] = cp.SubElementStringListProperty(pm.Middlename)
    Familyname: str | None = cp.NodeStringProperty(pm.Familyname, is_optional=True)
    Birthname: str | None = cp.NodeStringProperty(pm.Birthname, is_optional=True)
    Title: str | None = cp.NodeStringProperty(pm.Title, is_optional=True)
    _props = ('ExtExtension', 'Givenname', 'Middlename', 'Familyname', 'Birthname', 'Title')

    def __init__(self,
                 given_name: str | None = None,
                 middle_names: list[str] | None = None,
                 family_name: str | None = None,
                 birth_name: str | None = None,
                 title: str | None = None):
        super().__init__()
        self.Givenname = given_name
        self.Middlename = middle_names or []
        self.Familyname = family_name
        self.Birthname = birth_name
        self.Title = title


class PersonReference(PropertyBasedPMType):
    """Represents BICEPS PersonReference."""

    NODETYPE = pm.PersonReference
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Identification: list[InstanceIdentifier] = cp.SubElementListProperty(pm.Identification,
                                                                         value_class=InstanceIdentifier)  # 1...n
    Name: BaseDemographics | None = cp.SubElementProperty(pm.Name, value_class=BaseDemographics, is_optional=True)
    _props = ('ExtExtension', 'Identification', 'Name')

    def __init__(self, identifications: list[InstanceIdentifier] | None = None,
                 name: BaseDemographics | None = None):
        super().__init__()
        if identifications:
            self.Identification = identifications
        self.Name = name


class PersonParticipation(PersonReference):
    """Represents BICEPS PersonParticipation."""

    NODETYPE = pm.PersonParticipation
    Role: list[CodedValue] = cp.SubElementListProperty(pm.Role, value_class=CodedValue)
    _props = ('Role',)

    def __init__(self,
                 identifications: list[InstanceIdentifier] | None = None,
                 name: BaseDemographics | None = None,
                 roles: list[CodedValue] | None = None):
        super().__init__(identifications, name)
        if roles:
            self.Role = roles


class LocationDetail(PropertyBasedPMType):
    """Represents BICEPS LocationDetail."""

    NODETYPE = pm.LocationDetail
    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    PoC: str | None = cp.StringAttributeProperty('PoC')
    Room: str | None = cp.StringAttributeProperty('Room')
    Bed: str | None = cp.StringAttributeProperty('Bed')
    Facility: str | None = cp.StringAttributeProperty('Facility')
    Building: str | None = cp.StringAttributeProperty('Building')
    Floor: str | None = cp.StringAttributeProperty('Floor')
    _props = ('ExtExtension', 'PoC', 'Room', 'Bed', 'Facility', 'Building', 'Floor')

    def __init__(self,  # noqa: PLR0913
                 poc: str | None = None,
                 room: str | None = None,
                 bed: str | None = None,
                 facility: str | None = None,
                 building: str | None = None,
                 floor: str | None = None):
        super().__init__()
        self.PoC = poc
        self.Room = room
        self.Bed = bed
        self.Facility = facility
        self.Building = building
        self.Floor = floor


LocationDetailType = LocationDetail  # TypeAlias


class LocationReference(PropertyBasedPMType):
    """Represents BICEPS LocationReference."""

    NODETYPE = pm.LocationReference
    Identification: list[InstanceIdentifier] = cp.SubElementListProperty(pm.Identification,
                                                                         value_class=InstanceIdentifier)  # 1...n
    LocationDetail: LocationDetailType | None = cp.SubElementProperty(pm.LocationDetail,
                                                                      value_class=LocationDetail,
                                                                      is_optional=True)
    _props = ('Identification', 'LocationDetail')

    def __init__(self, identifications: list[InstanceIdentifier] | None = None,
                 location_detail: LocationDetailType | None = None):
        super().__init__()
        if identifications:
            self.Identification = identifications
        self.LocationDetail = location_detail


class ReferenceRange(PropertyBasedPMType):
    """Represents BICEPS ClinicalInfo/RelatedMeasurement/ReferenceRange."""

    NODETYPE = pm.ReferenceRange
    Range: RangeType = cp.SubElementProperty(pm.Range, value_class=Range)
    Meaning: CodedValue | None = cp.SubElementProperty(pm.Meaning, value_class=CodedValue, is_optional=True)
    _props = ('Range', 'Meaning')

    def __init__(self, ref_range: RangeType, meaning: CodedValue | None = None):
        super().__init__()
        self.Range = ref_range
        self.Meaning = meaning


ReferenceRangeType = ReferenceRange  # TypeAlias


class RelatedMeasurement(PropertyBasedPMType):
    """Represents BICEPS ClinicalInfo/RelatedMeasurement."""

    NODETYPE = pm.RelatedMeasurement
    Value: Measurement = cp.SubElementProperty(pm.Value, value_class=Measurement)
    ReferenceRange: list[ReferenceRangeType] = cp.SubElementListProperty(pm.ReferenceRange, value_class=ReferenceRange)
    _props = ('Value', 'ReferenceRange')

    def __init__(self,
                 value: Measurement,
                 reference_range: list[ReferenceRangeType] | None = None):
        super().__init__()
        self.Value = value
        if reference_range is not None:
            self.ReferenceRange = reference_range


class Criticality(StringEnum):
    """Represents BICEPS ClinicalInfo/Criticality."""

    Low = 'Lo'
    High = 'Hi'


CriticalityType = Criticality


class ClinicalInfo(PropertyBasedPMType):
    """Represents BICEPS ClinicalInfo."""

    NODETYPE = pm.ClinicalInfo
    Type: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue, is_optional=True)
    Code: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue, is_optional=True)
    Criticality: CriticalityType = cp.NodeEnumTextProperty(pm.Criticality, Criticality,
                                                           is_optional=True, implied_py_value=Criticality.Low)
    Description: list[LocalizedText] = cp.SubElementListProperty(pm.Description, value_class=LocalizedText)
    RelatedMeasurement: list[RelatedMeasurement] = cp.SubElementListProperty(pm.RelatedMeasurement,
                                                                             value_class=RelatedMeasurement)
    _props = ('Type', 'Code', 'Criticality', 'Description', 'RelatedMeasurement')

    def __init__(self,
                 type_: CodedValue | None = None,
                 code: CodedValue | None = None,
                 criticality: CriticalityType | None = None,
                 descriptions: list[LocalizedText] | None = None,
                 related_measurements: list[RelatedMeasurement] | None = None):
        super().__init__()
        self.Type = type_
        self.Code = code
        self.Criticality = criticality
        if descriptions:
            self.Description = descriptions
        if related_measurements:
            self.RelatedMeasurement = related_measurements


ClinicalInfoType = ClinicalInfo  # TypeAlias


class ImagingProcedure(PropertyBasedPMType):
    """Represents BICEPS ImagingProcedure."""

    NODETYPE = pm.ImagingProcedure
    AccessionIdentifier: InstanceIdentifier = cp.SubElementProperty(pm.AccessionIdentifier,
                                                                    value_class=InstanceIdentifier)
    RequestedProcedureId: InstanceIdentifier = cp.SubElementProperty(pm.RequestedProcedureId,
                                                                     value_class=InstanceIdentifier)
    StudyInstanceUid: InstanceIdentifier = cp.SubElementProperty(pm.StudyInstanceUid, value_class=InstanceIdentifier)
    ScheduledProcedureStepId: InstanceIdentifier = cp.SubElementProperty(pm.ScheduledProcedureStepId,
                                                                         value_class=InstanceIdentifier)
    Modality: CodedValue | None = cp.SubElementProperty(pm.Modality, value_class=CodedValue, is_optional=True)
    ProtocolCode: CodedValue | None = cp.SubElementProperty(pm.ProtocolCode, value_class=CodedValue, is_optional=True)
    _props = ('AccessionIdentifier', 'RequestedProcedureId', 'StudyInstanceUid', 'ScheduledProcedureStepId',
              'Modality', 'ProtocolCode')

    def __init__(self,  # noqa: PLR0913
                 accession_identifier: InstanceIdentifier | None = None,
                 requested_procedure_id: InstanceIdentifier | None = None,
                 study_instance_uid: InstanceIdentifier | None = None,
                 scheduled_procedure_step_id: InstanceIdentifier | None = None,
                 modality: InstanceIdentifier | None = None,
                 protocol_code: CodedValue | None = None):
        super().__init__()
        self.AccessionIdentifier = accession_identifier
        self.RequestedProcedureId = requested_procedure_id
        self.StudyInstanceUid = study_instance_uid
        self.ScheduledProcedureStepId = scheduled_procedure_step_id
        self.Modality = modality
        self.ProtocolCode = protocol_code


ImagingProcedureType = ImagingProcedure  # TypeAlias


class OrderDetail(PropertyBasedPMType):
    """Represents BICEPS OrderDetail."""

    NODETYPE = pm.OrderDetail
    Start: str | None = cp.NodeStringProperty(pm.Start, is_optional=True)  # xsd:dateTime
    End: str | None = cp.NodeStringProperty(pm.End, is_optional=True)  # xsd:dateTime
    Performer: list[PersonParticipation] = cp.SubElementListProperty(pm.Performer, value_class=PersonParticipation)
    Service: list[CodedValue] = cp.SubElementListProperty(pm.Service, value_class=CodedValue)
    ImagingProcedure: list[ImagingProcedureType] = cp.SubElementListProperty(pm.ImagingProcedure,
                                                                             value_class=ImagingProcedure)
    _props = ('Start', 'End', 'Performer', 'Service', 'ImagingProcedure')

    def __init__(self,
                 start: str | None = None,
                 end: str | None = None,
                 performer: list[PersonParticipation] | None = None,
                 service: list[CodedValue] | None = None,
                 imaging_procedure: list[ImagingProcedureType] | None = None):
        super().__init__()
        self.Start = start
        self.End = end
        if performer:
            self.Performer = performer
        if service:
            self.Service = service
        if imaging_procedure:
            self.ImagingProcedure = imaging_procedure


class RequestedOrderDetail(OrderDetail):
    """Represents BICEPS WorkflowContextState/WorkflowDetail/RequestedOrderDetail."""

    NODETYPE = pm.RequestedOrderDetail
    ReferringPhysician: PersonReference | None = cp.SubElementProperty(pm.ReferringPhysician,
                                                                       value_class=PersonReference,
                                                                       is_optional=True)
    RequestingPhysician: PersonReference | None = cp.SubElementProperty(pm.RequestingPhysician,
                                                                        value_class=PersonReference,
                                                                        is_optional=True)
    PlacerOrderNumber: InstanceIdentifier = cp.SubElementProperty(pm.PlacerOrderNumber, value_class=InstanceIdentifier)
    _props = ('ReferringPhysician', 'RequestingPhysician', 'PlacerOrderNumber')

    def __init__(self,  # noqa: PLR0913
                 start: str | None = None,
                 end: str | None = None,
                 performer: list[PersonParticipation] | None = None,
                 service: list[CodedValue] | None = None,
                 imaging_procedure: list[ImagingProcedure] | None = None,
                 referring_physician: PersonReference | None = None,
                 requesting_physician: PersonReference | None = None,
                 placer_order_number: InstanceIdentifier | None = None):
        super().__init__(start, end, performer, service, imaging_procedure)
        self.ReferringPhysician = referring_physician
        self.RequestingPhysician = requesting_physician
        self.PlacerOrderNumber = placer_order_number


RequestedOrderDetailType = RequestedOrderDetail  # TypeAlias


class PerformedOrderDetail(OrderDetail):
    """Represents BICEPS WorkflowContextState/WorkflowDetail/PerformedOrderDetail."""

    NODETYPE = pm.PerformedOrderDetail
    FillerOrderNumber: InstanceIdentifier | None = cp.SubElementProperty(pm.FillerOrderNumber,
                                                                         value_class=InstanceIdentifier,
                                                                         is_optional=True)
    ResultingClinicalInfo: ClinicalInfoType = cp.SubElementListProperty(pm.RelevantClinicalInfo,
                                                                        value_class=ClinicalInfo)
    _props = ('FillerOrderNumber', 'ResultingClinicalInfo')

    def __init__(self,  # noqa: PLR0913
                 start: str | None = None,
                 end: str | None = None,
                 performer: list[PersonParticipation] | None = None,
                 service: list[CodedValue] | None = None,
                 imaging_procedure: list[ImagingProcedure] | None = None,
                 filler_order_number: InstanceIdentifier | None = None,
                 resulting_clinical_info: ClinicalInfoType | None = None):
        super().__init__(start, end, performer, service, imaging_procedure)
        self.FillerOrderNumber = filler_order_number
        if resulting_clinical_info:
            self.ResultingClinicalInfo = resulting_clinical_info


PerformedOrderDetailType = PerformedOrderDetail  # TypeAlias


class WorkflowDetail(PropertyBasedPMType):
    """Represents BICEPS WorkflowContextState/WorkflowDetail."""

    NODETYPE = pm.WorkflowDetail
    Patient = cp.SubElementProperty(pm.Patient, value_class=PersonReference)
    AssignedLocation: LocationReference | None = cp.SubElementProperty(pm.AssignedLocation,
                                                                       value_class=LocationReference,
                                                                       is_optional=True)
    VisitNumber: InstanceIdentifier | None = cp.SubElementProperty(pm.VisitNumber,
                                                                   value_class=InstanceIdentifier, is_optional=True)
    DangerCode: list[CodedValue] = cp.SubElementListProperty(pm.DangerCode, value_class=CodedValue)
    RelevantClinicalInfo: list[ClinicalInfoType] = cp.SubElementListProperty(pm.RelevantClinicalInfo,
                                                                             value_class=ClinicalInfo)
    RequestedOrderDetail: RequestedOrderDetailType | None = cp.SubElementProperty(pm.RequestedOrderDetail,
                                                                                  value_class=RequestedOrderDetail,
                                                                                  is_optional=True)
    PerformedOrderDetail: PerformedOrderDetailType = cp.SubElementProperty(pm.PerformedOrderDetail,
                                                                           value_class=PerformedOrderDetail,
                                                                           is_optional=True)
    _props = ('Patient', 'AssignedLocation', 'VisitNumber', 'DangerCode',
              'RelevantClinicalInfo', 'RequestedOrderDetail', 'PerformedOrderDetail')

    def __init__(self,  # noqa: PLR0913
                 patient: PersonReference | None = None,
                 assigned_location: LocationReference | None = None,
                 visit_number: InstanceIdentifier | None = None,
                 danger_code: list[CodedValue] | None = None,
                 relevant_clinical_info: list[ClinicalInfo] | None = None,
                 requested_order_detail: RequestedOrderDetailType | None = None,
                 performed_order_detail: PerformedOrderDetailType | None = None):
        super().__init__()
        self.Patient = patient
        self.AssignedLocation = assigned_location
        self.VisitNumber = visit_number
        if danger_code:
            self.DangerCode = danger_code
        if relevant_clinical_info:
            self.RelevantClinicalInfo = relevant_clinical_info
        self.RequestedOrderDetail = requested_order_detail
        self.PerformedOrderDetail = performed_order_detail


class Relation(PropertyBasedPMType):
    """Represents BICEPS AbstractMetricDescriptor/Relation.

    Relation allows the modelling of relationships between a metric and other containment tree entries.
    """

    ExtExtension = cp.ExtensionNodeProperty(ext.Extension)
    Code: CodedValue | None = cp.SubElementProperty(pm.Code, value_class=CodedValue, is_optional=True)
    Identification: InstanceIdentifier | None = cp.SubElementProperty(pm.Identification,
                                                                      value_class=InstanceIdentifier,
                                                                      is_optional=True)
    Kind: AbstractMetricDescriptorRelationKindEnum | None = cp.EnumAttributeProperty(
        'Kind',
        enum_cls=AbstractMetricDescriptorRelationKindEnum,
        is_optional=False)
    Entries: list[str] = cp.EntryRefListAttributeProperty('Entries')
    _props = ('ExtExtension', 'Code', 'Identification', 'Kind', 'Entries')


class PatientDemographicsCoreData(BaseDemographics):
    """Represents BICEPS PatientDemographicsCoreData."""

    NODETYPE = pm.PatientDemographicsCoreData
    Sex: SexType | None = cp.NodeEnumTextProperty(pm.Sex, Sex, is_optional=True)
    PatientType: PatientTypeType | None = cp.NodeEnumTextProperty(pm.PatientType, PatientType, is_optional=True)
    DateOfBirth: DateTypeUnion | None = cp.DateOfBirthProperty(pm.DateOfBirth, is_optional=True)
    Height: Measurement | None = cp.SubElementProperty(pm.Height, value_class=Measurement, is_optional=True)
    Weight: Measurement | None = cp.SubElementProperty(pm.Weight, value_class=Measurement, is_optional=True)
    Race: CodedValue | None = cp.SubElementProperty(pm.Race, value_class=CodedValue, is_optional=True)
    _props = ('Sex', 'PatientType', 'DateOfBirth', 'Height', 'Weight', 'Race')

    def set_birthdate(self, date_time_of_birth_string: str):
        """Format string acc. to XML Schema: xsd:dateTime, xsd:date, xsd:gYearMonth or xsd:gYear.

        Internally it holds it as a datetime object, so specific formatting of the dateTimeOfBirth_string will be lost.
        """
        if not date_time_of_birth_string:
            self.DateOfBirth = None
        else:
            self.DateOfBirth = cp.DateOfBirthProperty.mk_value_object(date_time_of_birth_string)


class NeonatalPatientDemographicsCoreData(PatientDemographicsCoreData):
    """Represents BICEPS NeonatalPatientDemographicsCoreData."""

    NODETYPE = pm.NeonatalPatientDemographicsCoreData
    GestationalAge: Measurement | None = cp.SubElementProperty(pm.GestationalAge,
                                                               value_class=Measurement,
                                                               is_optional=True)
    BirthLength: Measurement | None = cp.SubElementProperty(pm.BirthLength, value_class=Measurement, is_optional=True)
    BirthWeight: Measurement | None = cp.SubElementProperty(pm.BirthWeight, value_class=Measurement, is_optional=True)
    HeadCircumference: Measurement | None = cp.SubElementProperty(pm.HeadCircumference,
                                                                  value_class=Measurement,
                                                                  is_optional=True)
    Mother: PersonReference | None = cp.SubElementProperty(pm.Mother, value_class=PersonReference, is_optional=True)
    _props = ('GestationalAge', 'BirthLength', 'BirthWeight', 'HeadCircumference', 'Mother')


class Udi(PropertyBasedPMType):
    """Represents BICEPS MdsDescriptor/MetaData/Udi."""

    NODETYPE = pm.Udi
    DeviceIdentifier: str = cp.NodeStringProperty(pm.DeviceIdentifier)
    HumanReadableForm: str = cp.NodeStringProperty(pm.HumanReadableForm)
    Issuer: InstanceIdentifier = cp.SubElementProperty(pm.Issuer, value_class=InstanceIdentifier)
    Jurisdiction: InstanceIdentifier | None = cp.SubElementProperty(pm.Jurisdiction,
                                                                    value_class=InstanceIdentifier,
                                                                    is_optional=True)
    _props = ('DeviceIdentifier', 'HumanReadableForm', 'Issuer', 'Jurisdiction')

    def __init__(self, device_identifier: str | None = None,
                 human_readable_form: str | None = None,
                 issuer: InstanceIdentifier | None = None,
                 jurisdiction: InstanceIdentifier | None = None):
        super().__init__()
        self.DeviceIdentifier = device_identifier
        self.HumanReadableForm = human_readable_form
        self.Issuer = issuer
        self.Jurisdiction = jurisdiction


UdiType = Udi  # TypeAlias


class MetaData(PropertyBasedPMType):
    """Represents BICEPS MdsDescriptor/MetaData."""

    NODETYPE = pm.MetaData
    Udi: list[UdiType] = cp.SubElementListProperty(pm.Udi, value_class=UdiType)
    LotNumber: str | None = cp.NodeStringProperty(pm.LotNumber, is_optional=True)
    Manufacturer: list[LocalizedText] = cp.SubElementListProperty(pm.Manufacturer, value_class=LocalizedText)
    ManufactureDate: str | None = cp.NodeStringProperty(pm.ManufactureDate, is_optional=True)
    ExpirationDate: str | None = cp.NodeStringProperty(pm.ExpirationDate, is_optional=True)
    ModelName: list[LocalizedText] = cp.SubElementListProperty(pm.ModelName, value_class=LocalizedText)
    ModelNumber: str | None = cp.NodeStringProperty(pm.ModelNumber, is_optional=True)
    SerialNumber: list[str] = cp.SubElementStringListProperty(pm.SerialNumber)
    _props = ('Udi', 'LotNumber', 'Manufacturer', 'ManufactureDate', 'ExpirationDate',
              'ModelName', 'ModelNumber', 'SerialNumber')


class CalibrationResult(PropertyBasedPMType):
    """Represents BICEPS CalibrationInfo/CalibrationDocumentation/CalibrationResult."""

    NODETYPE = pm.CalibrationResult
    Code: CodedValue = cp.SubElementProperty(pm.Code, value_class=CodedValue)
    Value: Measurement = cp.SubElementProperty(pm.Value, value_class=Measurement)
    _props = ('Code', 'Value')


class CalibrationDocumentation(PropertyBasedPMType):
    """Represents BICEPS CalibrationInfo/CalibrationDocumentation."""

    NODETYPE = pm.CalibrationDocumentation
    Documentation: list[LocalizedText] = cp.SubElementListProperty(pm.Documentation, value_class=LocalizedText)
    CalibrationResult: list[CalibrationResult] = cp.SubElementListProperty(pm.CalibrationResult,
                                                                           value_class=CalibrationResult)
    _props = ('Documentation', 'CalibrationResult')


class CalibrationInfo(PropertyBasedPMType):
    """Represents BICEPS CalibrationInfo."""

    NODETYPE = pm.CalibrationInfo
    CalibrationDocumentation: list[CalibrationDocumentation] = cp.SubElementListProperty(
        pm.CalibrationDocumentation, value_class=CalibrationDocumentation)
    ComponentCalibrationState: CalibrationState | None = cp.EnumAttributeProperty('ComponentCalibrationState',
                                                                                  enum_cls=CalibrationState)
    Type: CalibrationType | None = cp.EnumAttributeProperty('Type',
                                                            implied_py_value=CalibrationType.UNSPEC,
                                                            enum_cls=CalibrationType)
    Time: float | None = cp.TimestampAttributeProperty('Time')
    _props = ('CalibrationDocumentation', 'ComponentCalibrationState', 'Type', 'Time')


class ApprovedJurisdictions(PropertyBasedPMType):
    """Represents BICEPS ApprovedJurisdictions."""

    NODETYPE = pm.ApprovedJurisdictions
    ApprovedJurisdiction: list[InstanceIdentifier] = cp.SubElementListProperty(pm.ApprovedJurisdiction,
                                                                               value_class=InstanceIdentifier)
    _props = ('ApprovedJurisdiction',)


class ContainmentTreeEntry(PropertyBasedPMType):
    """Represents BICEPS ContainmentTree/Entry."""

    Type: CodedValue | None = cp.SubElementProperty(pm.Type, value_class=CodedValue, is_optional=True)
    _props = ('Type',)


class ContainmentTree(PropertyBasedPMType):
    """Represents BICEPS ContainmentTree."""

    Entry: ContainmentTreeEntry = cp.SubElementListProperty(pm.Entry, value_class=ContainmentTreeEntry)
    _props = ('Entry',)


# Technically, Retrievalibity belongs to msg_types, because it is defined in BICEPS message model.
# But its usage is in mdib, not in messages, it fits here better.
# This also helps to avoid a circular dependency.
class RetrievabilityMethod(StringEnum):
    """Represents BICEPS message model RetrievabilityMethod."""

    GET = 'Get'
    PERIODIC = 'Per'
    EPISODIC = 'Ep'
    STREAM = 'Strm'


class RetrievabilityInfo(PropertyBasedPMType):
    """Represents BICEPS message model RetrievabilityInfo."""

    NODETYPE = msg.RetrievabilityInfo
    Method: RetrievabilityMethod = cp.EnumAttributeProperty('Method',
                                                            enum_cls=RetrievabilityMethod,
                                                            is_optional=False)
    UpdatePeriod: DurationType | None = cp.DurationAttributeProperty('UpdatePeriod', implied_py_value=1.0)
    _props = ('Method', 'UpdatePeriod')

    def __init__(self, method: RetrievabilityMethod, update_period: DurationType | None = None):
        super().__init__()
        self.Method = method
        self.UpdatePeriod = update_period

    @classmethod
    def from_node(cls, node: xml_utils.LxmlElement) -> RetrievabilityInfo:
        """Construct class from a node."""
        obj = cls(RetrievabilityMethod.GET)  # any allowed value, will be overwritten in update_node
        obj.update_from_node(node)
        return obj

    def __repr__(self) -> str:
        return f'{self.__class__.__name__} {self.Method} period={self.UpdatePeriod}'


class Retrievability(PropertyBasedPMType):
    """Represents BICEPS message model Retrievability."""

    By: list[RetrievabilityInfo] = cp.SubElementListProperty(msg.By, value_class=RetrievabilityInfo)
    _props = ('By',)

    def __init__(self, retrievability_info_list: list[RetrievabilityInfo] | None = None):
        super().__init__()
        if retrievability_info_list:
            self.By = retrievability_info_list


# mapping of types: xsi:type information to classes
# find all classes in this module that have a member "NODETYPE"
classes = inspect.getmembers(sys.modules[__name__],
                             lambda member: inspect.isclass(member) and member.__module__ == __name__)

# make a dictionary from found classes: (Key is NODETYPE, value is the class itself
_classes = [c[1] for c in classes if hasattr(c[1], 'NODETYPE') and c[1].NODETYPE is not None]

_name_class_lookup = {c.NODETYPE: c for c in _classes}


def _get_pmtypes_class(qname: etree.QName) -> type[PropertyBasedPMType]:
    """Find class in _name_class_lookup."""
    try:
        return _name_class_lookup[qname]
    except KeyError as ex:
        raise KeyError(f'{qname.namespace}.{qname.localname}') from ex  # noqa: EM102
