use core::hash::Hash;
use core::ops::Bound;
use core::ops::RangeInclusive;
use std::collections::HashSet;

use alloc::borrow::Cow;
use rust_decimal::Decimal;
use time::format_description::well_known::Iso8601;
use time::OffsetDateTime;

use crate::types::raw;
use crate::types::raw::BusinessProcessCatalogue;
use crate::types::raw::DataDictionary;
use crate::types::raw::DictionaryEntryType;
use crate::types::raw::MessageDefinitionIdentifier;
use crate::types::raw::Repository;
use crate::types::raw::SemanticMarkupType;
use crate::types::raw::TopLevelDictionaryEntry;
use core::borrow::Borrow;
use either::Either;

#[derive(Clone, Debug)]
pub struct Amount<'a> {
    pub currency_identifier_set: Option<Cow<'a, str>>,

    pub definition: Cow<'a, str>,

    pub fraction_digits: u32,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub range: (Bound<Decimal>, Bound<Decimal>),

    pub registration_status: RegistrationStatus,

    pub total_digits: u32,
}

impl<'a> Amount<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints: _,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Amount);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let fraction_digits = fraction_digits.unwrap();
        let range = (
            min_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
            max_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
        );
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);
        let total_digits = total_digits.unwrap();

        Self {
            currency_identifier_set,
            definition,
            fraction_digits,
            id,
            name,
            range,
            registration_status,
            total_digits,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Code<'a> {
    pub code: Cow<'a, str>,

    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Code<'a> {
    fn cook(code: raw::Code<'a>) -> Self {
        let raw::Code {
            code_name,
            definition,
            id,
            name,
            next_versions: _,
            previous_version: _,
            registration_status,
            removal_date,
            semantic_markup: _,
        } = code;

        let code = code_name.unwrap();
        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            code,
            definition,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct CodeSet<'a> {
    pub codes: Vec<Code<'a>>,

    pub definition: Cow<'a, str>,

    pub derivation: Option<Cow<'a, str>>,

    pub id: Cow<'a, str>,

    pub is_external: bool,

    pub length: Option<RangeInclusive<usize>>,

    pub name: Cow<'a, str>,

    pub pattern: Option<Cow<'a, str>>,

    pub registration_status: RegistrationStatus,

    pub trace: Option<Cow<'a, str>>,
}

impl<'a> CodeSet<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints: _,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions: _,
            pattern,
            previous_version: _,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::CodeSet);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(currency_identifier_set.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(xors.is_empty());

        let codes = if trace.is_some() {
            vec![]
        } else {
            codes.into_iter().map(Code::cook).collect()
        };

        let definition = definition.unwrap();
        let length = length.map(|length| length..=length).or_else(|| {
            max_length.map(|max_length| {
                let min_length = min_length.unwrap_or_default();
                min_length..=max_length
            })
        });
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        let is_external = semantic_markup
            .into_iter()
            .find(|markup| markup.typ == SemanticMarkupType::ExternalCodeSetAttribute)
            .map_or(false, |markup| {
                markup
                    .elements
                    .into_iter()
                    .find(|element| element.name == "IsExternalCodeSet")
                    .unwrap()
                    .value
                    .parse()
                    .unwrap()
            });

        Self {
            codes,
            definition,
            derivation,
            id,
            is_external,
            length,
            name,
            pattern,
            registration_status,
            trace,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Date<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Date<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Date);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct DateTime<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub pattern: Option<Cow<'a, str>>,

    pub registration_status: RegistrationStatus,
}

impl<'a> DateTime<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::DateTime);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            pattern,
            registration_status,
        }
    }
}

#[derive(Debug)]
pub enum Definition<'a> {
    Amount(Amount<'a>),
    ChoiceComponent(MessageComponent<'a>),
    CodeSet(CodeSet<'a>),
    Date(Date<'a>),
    DateTime(DateTime<'a>),
    ExternalSchema(ExternalSchema<'a>),
    IdentifierSet(IdentifierSet<'a>),
    Indicator(Indicator<'a>),
    MessageComponent(MessageComponent<'a>),
    MessageDefinition(MessageDefinition<'a>),
    Month(Month<'a>),
    Quantity(Quantity<'a>),
    Rate(Rate<'a>),
    Text(Text<'a>),
    Time(Time<'a>),
    Year(Year<'a>),
    YearMonth(YearMonth<'a>),
}

impl<'a> Definition<'a> {
    pub fn dependencies(&'a self) -> impl Iterator<Item = &'a str> {
        match self {
            Definition::Amount(Amount {
                currency_identifier_set,
                ..
            }) => Either::Left(Either::Left(currency_identifier_set.as_deref().into_iter())),

            Definition::ChoiceComponent(MessageComponent { elements, .. })
            | Definition::MessageComponent(MessageComponent { elements, .. })
            | Definition::MessageDefinition(MessageDefinition { elements, .. }) => Either::Left(
                Either::Right(elements.iter().map(|element| element.typ.as_ref())),
            ),

            Definition::CodeSet(CodeSet { trace, .. }) => {
                Either::Left(Either::Left(trace.as_deref().into_iter()))
            }

            _ => Either::Right(core::iter::empty()),
        }
    }

    pub fn id(&self) -> &str {
        let (Definition::Amount(Amount { id, .. })
        | Definition::ChoiceComponent(MessageComponent { id, .. })
        | Definition::CodeSet(CodeSet { id, .. })
        | Definition::Date(Date { id, .. })
        | Definition::DateTime(DateTime { id, .. })
        | Definition::ExternalSchema(ExternalSchema { id, .. })
        | Definition::IdentifierSet(IdentifierSet { id, .. })
        | Definition::Indicator(Indicator { id, .. })
        | Definition::MessageComponent(MessageComponent { id, .. })
        | Definition::MessageDefinition(MessageDefinition { id, .. })
        | Definition::Month(Month { id, .. })
        | Definition::Quantity(Quantity { id, .. })
        | Definition::Rate(Rate { id, .. })
        | Definition::Text(Text { id, .. })
        | Definition::Time(Time { id, .. })
        | Definition::Year(Year { id, .. })
        | Definition::YearMonth(YearMonth { id, .. })) = self;

        id
    }

    pub fn name(&self) -> &str {
        let (Definition::Amount(Amount { name, .. })
        | Definition::ChoiceComponent(MessageComponent { name, .. })
        | Definition::CodeSet(CodeSet { name, .. })
        | Definition::Date(Date { name, .. })
        | Definition::DateTime(DateTime { name, .. })
        | Definition::ExternalSchema(ExternalSchema { name, .. })
        | Definition::IdentifierSet(IdentifierSet { name, .. })
        | Definition::Indicator(Indicator { name, .. })
        | Definition::MessageComponent(MessageComponent { name, .. })
        | Definition::MessageDefinition(MessageDefinition { name, .. })
        | Definition::Month(Month { name, .. })
        | Definition::Quantity(Quantity { name, .. })
        | Definition::Rate(Rate { name, .. })
        | Definition::Text(Text { name, .. })
        | Definition::Time(Time { name, .. })
        | Definition::Year(Year { name, .. })
        | Definition::YearMonth(YearMonth { name, .. })) = self;

        name
    }
}

impl Borrow<str> for Definition<'_> {
    fn borrow(&self) -> &str {
        self.id()
    }
}

impl Eq for Definition<'_> {}

impl Hash for Definition<'_> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id().hash(state)
    }
}

impl PartialEq for Definition<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.id().eq(other.id())
    }
}

#[derive(Clone, Debug)]
pub struct ExternalSchema<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> ExternalSchema<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::ExternalSchema);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(codes.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(example.is_none());
        assert!(elements.is_empty());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct IdentifierSet<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub identification_scheme: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub pattern: Option<Cow<'a, str>>,

    pub registration_status: RegistrationStatus,
}

impl<'a> IdentifierSet<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints: _,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length: _,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length: _,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::IdentifierSet);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(fraction_digits.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let identification_scheme = identification_scheme.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            identification_scheme,
            name,
            pattern,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Indicator<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub meaning_when_false: Cow<'a, str>,

    pub meaning_when_true: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Indicator<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup: _,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Indicator);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let meaning_when_false = meaning_when_false.unwrap();
        let meaning_when_true = meaning_when_true.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            meaning_when_false,
            meaning_when_true,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct MessageComponent<'a> {
    pub definition: Cow<'a, str>,

    pub elements: Vec<MessageElement<'a>>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> MessageComponent<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block: _,
            codes,
            constraints: _,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions: _,
            pattern,
            previous_version: _,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup: _,
            sub_type,
            super_type,
            total_digits,
            trace: _,
            typ: DictionaryEntryType::ChoiceComponent | DictionaryEntryType::MessageComponent,
            xors: _,
        } = entry
        else {
            todo!()
        };

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(codes.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(pattern.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());

        let definition = definition.unwrap();
        let elements = if message_elements.is_empty() {
            todo!()
        } else {
            message_elements
                .into_iter()
                .map(MessageElement::cook)
                .collect()
        };
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            elements,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct MessageDefinition<'a> {
    pub definition: Cow<'a, str>,

    pub elements: Vec<MessageElement<'a>>,

    pub id: Cow<'a, str>,

    pub identifier: MessageDefinitionIdentifier<'a>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,

    pub xml_root_tag: Cow<'a, str>,

    pub xml_tag: Cow<'a, str>,
}

impl<'a> From<raw::MessageDefinition<'a>> for MessageDefinition<'a> {
    fn from(definition: raw::MessageDefinition<'a>) -> Self {
        let raw::MessageDefinition {
            building_blocks,
            constraints: _,
            definition,
            doclets: _,
            id,
            identifier,
            message_set: _,
            name,
            next_versions: _,
            previous_version: _,
            registration_status,
            root_element,
            semantic_markup: _,
            xml_name: _,
            xml_tag,
            xors: _,
        } = definition;

        let elements = building_blocks
            .into_iter()
            .map(MessageElement::from)
            .collect();
        let registration_status = RegistrationStatus::cook(registration_status, None);
        let xml_root_tag = root_element;

        Self {
            definition,
            elements,
            id,
            identifier,
            name,
            registration_status,
            xml_root_tag,
            xml_tag,
        }
    }
}

#[derive(Clone, Debug)]
pub struct MessageElement<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub occurs: (Bound<usize>, Bound<usize>),

    pub registration_status: RegistrationStatus,

    pub typ: Cow<'a, str>,

    pub xml_tag: Cow<'a, str>,
}

impl<'a> MessageElement<'a> {
    fn cook(element: raw::MessageElement<'a>) -> Self {
        Self::from(element)
    }
}

impl<'a> From<raw::MessageBuildingBlock<'a>> for MessageElement<'a> {
    fn from(raw: raw::MessageBuildingBlock<'a>) -> Self {
        let raw::MessageBuildingBlock {
            complex_type,
            definition,
            example: _,
            id,
            max_occurs,
            min_occurs,
            name,
            next_versions: _,
            previous_version: _,
            registration_status,
            semantic_markup: _,
            simple_type,
            xml_tag,
        } = raw;

        let definition = definition.unwrap_or_default();
        let occurs = (
            Bound::Included(min_occurs),
            max_occurs.map_or(Bound::Unbounded, Bound::Included),
        );
        let registration_status = RegistrationStatus::cook(registration_status, None);
        let typ = complex_type.xor(simple_type).unwrap();

        Self {
            definition,
            id,
            name,
            occurs,
            registration_status,
            typ,
            xml_tag,
        }
    }
}

impl<'a> From<raw::MessageElement<'a>> for MessageElement<'a> {
    fn from(element: raw::MessageElement<'a>) -> Self {
        let raw::MessageElement {
            business_component_trace: _,
            business_element_trace: _,
            complex_type,
            constraints: _,
            definition,
            element_type,
            example: _,
            id,
            is_composite,
            is_derived,
            max_occurs,
            min_occurs,
            name,
            next_versions: _,
            previous_version: _,
            registration_status,
            removal_date,
            semantic_markup: _,
            simple_type,
            typ,
            xml_tag,
        } = element;

        // Ensure assumptions: unused fields
        assert!(
            matches!(element_type, raw::MessageElementType::AssociationEnd)
                || is_composite.is_none()
        );
        assert!(!is_derived);

        let definition = definition.unwrap_or_default();
        let occurs = (
            Bound::Included(min_occurs),
            max_occurs.map_or(Bound::Unbounded, Bound::Included),
        );
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);
        let typ = match element_type {
            raw::MessageElementType::AssociationEnd => {
                assert!(complex_type.is_none());
                assert!(is_composite.is_some());
                assert!(simple_type.is_none());

                typ.unwrap()
            }

            raw::MessageElementType::Attribute => {
                assert!(is_composite.is_none());
                assert!(typ.is_none());

                complex_type.xor(simple_type).unwrap()
            }
        };

        Self {
            definition,
            id,
            name,
            occurs,
            registration_status,
            typ,
            xml_tag,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Month<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Month<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Month);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Quantity<'a> {
    pub definition: Cow<'a, str>,

    pub fraction_digits: u32,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub pattern: Option<Cow<'a, str>>,

    pub range: (Bound<Decimal>, Bound<Decimal>),

    pub registration_status: RegistrationStatus,

    pub total_digits: Option<u32>,
}

impl<'a> Quantity<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints: _,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Quantity);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let fraction_digits = fraction_digits.unwrap_or_default();
        let range = (
            min_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
            max_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
        );
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            fraction_digits,
            id,
            name,
            pattern,
            range,
            registration_status,
            total_digits,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Rate<'a> {
    pub base_value: Decimal,

    pub definition: Cow<'a, str>,

    pub fraction_digits: u32,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub range: (Bound<Decimal>, Bound<Decimal>),

    pub registration_status: RegistrationStatus,

    pub total_digits: u32,
}

impl<'a> Rate<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Rate);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let mut base_value: Decimal = base_value.unwrap();
        let definition = definition.unwrap();
        let fraction_digits = fraction_digits.unwrap();
        let range = (
            min_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
            max_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
        );
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);
        let total_digits = total_digits.unwrap();

        base_value.rescale(fraction_digits);

        Self {
            base_value,
            definition,
            fraction_digits,
            id,
            name,
            range,
            registration_status,
            total_digits,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum RegistrationStatus {
    Obsolete {
        removal_date: Option<OffsetDateTime>,
    },
    ProvisionallyRegistered,
    Registered,
}

impl RegistrationStatus {
    pub fn cook(status: raw::RegistrationStatus, removal_date: Option<Cow<str>>) -> Self {
        match status {
            raw::RegistrationStatus::Obsolete => {
                // TODO parse date & add as field
                let removal_date = removal_date.map(|removal_date| {
                    OffsetDateTime::parse(&removal_date, &Iso8601::PARSING).unwrap()
                });
                RegistrationStatus::Obsolete { removal_date }
            }
            raw::RegistrationStatus::ProvisionallyRegistered => {
                RegistrationStatus::ProvisionallyRegistered
            }
            raw::RegistrationStatus::Registered => RegistrationStatus::Registered,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Text<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub length: Option<RangeInclusive<usize>>,

    pub name: Cow<'a, str>,

    pub pattern: Option<Cow<'a, str>>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Text<'a> {
    pub fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Text);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let length = length.map(|length| length..=length).or_else(|| {
            max_length.map(|max_length| {
                let min_length = min_length.unwrap_or_default();
                min_length..=max_length
            })
        });
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            length,
            name,
            pattern,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Time<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> Time<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Time);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(example.is_none());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Year<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub range: (Bound<i32>, Bound<i32>),

    pub registration_status: RegistrationStatus,
}

impl<'a> Year<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::Year);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let range = (
            min_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
            max_inclusive.map_or(Bound::Unbounded, |bound| {
                let bound = bound.parse().unwrap();
                Bound::Included(bound)
            }),
        );
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            range,
            registration_status,
        }
    }
}

#[derive(Clone, Debug)]
pub struct YearMonth<'a> {
    pub definition: Cow<'a, str>,

    pub id: Cow<'a, str>,

    pub name: Cow<'a, str>,

    pub registration_status: RegistrationStatus,
}

impl<'a> YearMonth<'a> {
    fn cook(entry: TopLevelDictionaryEntry<'a>) -> Self {
        let TopLevelDictionaryEntry {
            association_domain,
            base_value,
            building_block,
            codes,
            constraints,
            currency_identifier_set,
            definition,
            derivation,
            derivation_component,
            derivation_element,
            elements,
            example: _,
            fraction_digits,
            id,
            identification_scheme,
            kind,
            length,
            max_inclusive,
            max_length,
            meaning_when_false,
            meaning_when_true,
            message_elements,
            min_inclusive,
            min_length,
            name,
            namespace,
            namespace_list_attr,
            namespace_list_child,
            next_versions,
            pattern,
            previous_version,
            process_content,
            process_contents,
            registration_status,
            removal_date,
            semantic_markup,
            sub_type,
            super_type,
            total_digits,
            trace,
            typ,
            xors,
        } = entry;

        assert_eq!(typ, DictionaryEntryType::YearMonth);

        // Ensure assumptions: unused fields
        assert!(association_domain.is_none());
        assert!(base_value.is_none());
        assert!(building_block.is_none());
        assert!(codes.is_empty());
        assert!(constraints.is_empty());
        assert!(currency_identifier_set.is_none());
        assert!(derivation.is_none());
        assert!(derivation_component.is_none());
        assert!(derivation_element.is_none());
        assert!(elements.is_empty());
        assert!(fraction_digits.is_none());
        assert!(identification_scheme.is_none());
        assert!(length.is_none());
        assert!(kind.is_none());
        assert!(max_inclusive.is_none());
        assert!(max_length.is_none());
        assert!(meaning_when_false.is_none());
        assert!(meaning_when_true.is_none());
        assert!(min_inclusive.is_none());
        assert!(min_length.is_none());
        assert!(message_elements.is_empty());
        assert!(namespace.is_none());
        assert!(namespace_list_attr.is_none());
        assert!(namespace_list_child.is_empty());
        assert!(next_versions.is_none());
        assert!(pattern.is_none());
        assert!(previous_version.is_none());
        assert!(process_content.is_none());
        assert!(process_contents.is_none());
        assert!(semantic_markup.is_empty());
        assert!(sub_type.is_none());
        assert!(super_type.is_none());
        assert!(total_digits.is_none());
        assert!(trace.is_none());
        assert!(xors.is_empty());

        let definition = definition.unwrap();
        let registration_status = RegistrationStatus::cook(registration_status, removal_date);

        Self {
            definition,
            id,
            name,
            registration_status,
        }
    }
}

pub fn cook(repository: Repository) -> HashSet<Definition> {
    let Repository {
        business_process_catalogue,
        data_dictionary,
        ..
    } = repository;

    let mut definitions = cook_business_process_catalogue(business_process_catalogue).collect();
    cook_data_dictionary(&mut definitions, data_dictionary);

    definitions
}

fn cook_business_process_catalogue(
    dictionary: BusinessProcessCatalogue,
) -> impl Iterator<Item = Definition> {
    dictionary
        .entries
        .into_iter()
        .flat_map(|entry| entry.message_definitions)
        .map(MessageDefinition::from)
        .map(Definition::MessageDefinition)
}

fn cook_data_dictionary<'a>(
    definitions: &mut HashSet<Definition<'a>>,
    dictionary: DataDictionary<'a>,
) {
    for entry in dictionary.entries {
        let definition = match entry.typ {
            DictionaryEntryType::Amount => Definition::Amount(Amount::cook(entry)),
            DictionaryEntryType::ChoiceComponent => {
                Definition::ChoiceComponent(MessageComponent::cook(entry))
            }
            DictionaryEntryType::CodeSet => Definition::CodeSet(CodeSet::cook(entry)),
            DictionaryEntryType::Date => Definition::Date(Date::cook(entry)),
            DictionaryEntryType::DateTime => Definition::DateTime(DateTime::cook(entry)),
            DictionaryEntryType::ExternalSchema => {
                Definition::ExternalSchema(ExternalSchema::cook(entry))
            }
            DictionaryEntryType::IdentifierSet => {
                Definition::IdentifierSet(IdentifierSet::cook(entry))
            }
            DictionaryEntryType::Indicator => Definition::Indicator(Indicator::cook(entry)),
            DictionaryEntryType::MessageComponent => {
                Definition::MessageComponent(MessageComponent::cook(entry))
            }
            DictionaryEntryType::Month => Definition::Month(Month::cook(entry)),
            DictionaryEntryType::Quantity => Definition::Quantity(Quantity::cook(entry)),
            DictionaryEntryType::Rate => Definition::Rate(Rate::cook(entry)),
            DictionaryEntryType::Text => Definition::Text(Text::cook(entry)),
            DictionaryEntryType::Time => Definition::Time(Time::cook(entry)),
            DictionaryEntryType::Year => Definition::Year(Year::cook(entry)),
            DictionaryEntryType::YearMonth => Definition::YearMonth(YearMonth::cook(entry)),

            _ => {
                // TODO ignored for now
                continue;
            }
        };

        definitions.insert(definition);
    }
}