from __future__ import annotations
from typing import Union
from datafinder.typed_attributes import *
from datafinder import FinderResult, convert_inputs_and_select, convert_inputs_and_select_for_date_range, convert_inputs_and_select_for_processing_range
from datafinder.attribute import Attribute
from datafinder.finder import RelatedFinder, display_prefix, via_joins
from datafinder.runner import convert_date, convert_date_time
from model.relational import Column, Table, NoOperation, JoinOperation, JoinTreeNodeOperation, CountAllOperation, Operation
from model.milestoning import ProcessingTemporalColumns, SingleBusinessDateColumn, SingleProcessingDatetimeColumn, BusinessDateAndProcessingTemporalColumns, BiTemporalColumns, BusinessDateRangeColumns, MilestonedTable
from {{class_module_map_base[rcm.clazz.name]}} import {{rcm.clazz.name}}FinderBase, {{rcm.clazz.name}}RelatedFinderBase

{% for rpm in rcm.property_mappings %}
{% if not is_primitive(rpm.property) and rpm.property.type.name != rcm.clazz.name %}from {{class_module_map[rpm.property.type.name]}} import {{rpm.property.type.name}}RelatedFinder
{% endif %}
{% endfor %}

{% for rpm in rcm.property_mappings %}
{% if not is_primitive(rpm.property) and rpm.property.type.name != rcm.clazz.name and flatten_embedded(rpm.target) %}
def _embedded_for_{{to_python_name(rpm.property)}}(incoming: dict | None, parent, relation_name: str) -> dict:
    """Join-less alternates for '{{rpm.property.id}}.*' properties satisfied by flat columns
    on this table, merged with any deeper embedding forwarded by the caller."""
    result: dict = {}
{% for dotted, leaf_prop, col in flatten_embedded(rpm.target) %}
    result['{{dotted}}'] = {{leaf_prop.type.name}}Attribute(relation_name.title() + ' {{display_name(leaf_prop)}}', '{{col.name}}', '{{col.type}}', '{{table_qualified_name(col.table)}}', parent, nullable={{col.nullable}}, optional={{ embedded_optional(rpm.target)[dotted] }})
{% endfor %}
    if incoming:
        result.update({k[len('{{rpm.property.id}}.'):]: v for k, v in incoming.items() if k.startswith('{{rpm.property.id}}.')})
    return result

{% endif %}
{% endfor %}

{% for ec in embedded_classes(rcm) %}
class {{ec.name}}:
    """{{ec.label}}: held in the columns of the owning row, so reading it needs no join."""

    def __init__(self, parent=None, prefix: str = ''):
{% for prop, col in ec.columns %}
        self.__{{to_python_name(prop)}} = {{prop.type.name}}Attribute(f'{prefix} {{ec.label}} {{display_name(prop)}}'.strip(), '{{col.name}}', '{{col.type}}', '{{table_qualified_name(col.table)}}', parent, nullable={{col.nullable}}, optional={{ ec.optional or prop.multiplicity.lower == 0 }})
{% endfor %}
{% for prop, child in ec.children %}
        self.__{{to_python_name(prop)}} = {{child}}(parent, prefix)
{% endfor %}
{% for prop, join in ec.joins %}
        from {{class_module_map[prop.type.name]}} import {{prop.type.name}}RelatedFinder
        self.__{{to_python_name(prop)}} = {{prop.type.name}}RelatedFinder('{{ec.label}} {{prop.name}}', {{column_code(join.lhs)}}, {{column_code(join.rhs)}}, {{parent_expr(join, 'parent')}}, many={{ 'True' if prop.multiplicity.is_many() else 'False' }}, optional={{ ec.optional or prop.multiplicity.lower == 0 }})
{% endfor %}
{% if not ec.columns and not ec.children and not ec.joins %}
        pass
{% endif %}
{% for prop, col in ec.columns %}

    def {{to_python_name(prop)}}(self) -> {{prop.type.name}}Attribute:
        return self.__{{to_python_name(prop)}}
{% endfor %}
{% for prop, child in ec.children %}

    def {{to_python_name(prop)}}(self) -> {{child}}:
        return self.__{{to_python_name(prop)}}
{% endfor %}
{% for prop, join in ec.joins %}

    def {{to_python_name(prop)}}(self) -> {{prop.type.name}}RelatedFinder:
        return self.__{{to_python_name(prop)}}
{% endfor %}

{% endfor %}

class {{rcm.clazz.name}}RelatedFinder({{rcm.clazz.name}}RelatedFinderBase, RelatedFinder):

{% if rcm.milestone_mapping %}
{% if has_bitemporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BiTemporalColumns(Column('{{rcm.milestone_mapping._date_from.target.name}}', '{{rcm.milestone_mapping._date_from.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._date_to.target.name}}', '{{rcm.milestone_mapping._date_to.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_business_date_range(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BusinessDateRangeColumns(Column('{{rcm.milestone_mapping._date_from.target.name}}', '{{rcm.milestone_mapping._date_from.target.type}}'),
                                                        Column('{{rcm.milestone_mapping._date_to.target.name}}', '{{rcm.milestone_mapping._date_to.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_business_date_and_processing(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BusinessDateAndProcessingTemporalColumns(Column('{{rcm.milestone_mapping._date.target.name}}', '{{rcm.milestone_mapping._date.target.type}}'),
                                                                        Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                                        Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_processing_temporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = ProcessingTemporalColumns(Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                         Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_processing_snapshot(rcm.milestone_mapping) %}
    __zz_milestoning_columns = SingleProcessingDatetimeColumn(Column('{{rcm.milestone_mapping._datetime.target.name}}', '{{rcm.milestone_mapping._datetime.target.type}}'))
{% elif has_uni_business_temporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = SingleBusinessDateColumn(Column('{{rcm.milestone_mapping._date.target.name}}', '{{rcm.milestone_mapping._date.target.type}}'))
{% endif %}
    __table = MilestonedTable('{{table_qualified_name(rcm.property_mappings[0].target.table)}}', [{% for rpm in rcm.property_mappings %}{% if is_primitive(rpm.property) and rpm.target.primary_key %}Column('{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', primary_key=True), {% endif %}{% endfor %}], __zz_milestoning_columns)
{% else %}
    __table = Table('{{table_qualified_name(rcm.property_mappings[0].target.table)}}', [{% for rpm in rcm.property_mappings %}{% if is_primitive(rpm.property) and rpm.target.primary_key %}Column('{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', primary_key=True), {% endif %}{% endfor %}])
{% endif %}

    _column_map = {
{% for rpm in rcm.property_mappings %}
{% if is_primitive(rpm.property) %}
        '{{rpm.property.id}}': ('{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}'),
{% endif %}
{% endfor %}
    }

    def __init__(self, relation_name: str, source: Column, target: Column, parent_join: JoinTreeNodeOperation | None = None,
                 embedded: dict | None = None, many: bool = False, optional: bool = False,
                 business_date: Union[datetime.date, str, None] = None,
                 processing_time: Union[datetime.datetime, datetime.date, str, None] = None, **kwargs):
        join = JoinOperation(relation_name, {{rcm.clazz.name}}RelatedFinder.__table, source, target)
        self.__node = JoinTreeNodeOperation(
            join, parent_join, many=many, optional=optional,
            business_date=None if business_date is None else convert_date(business_date),
            processing_datetime=None if processing_time is None else convert_date_time(processing_time))
        RelatedFinder.__init__(self, self.__node)
{% for rpm in rcm.property_mappings %}
{% if is_primitive(rpm.property) %}
        self.__{{to_python_name(rpm.property)}} = {{rpm.property.type.name}}Attribute(display_prefix(relation_name, parent_join) + ' {{display_name(rpm.property)}}', '{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', self.__node, embedded=(embedded or {}).get('{{rpm.property.id}}'), nullable={{rpm.target.nullable}}, optional={{ rpm.property.multiplicity.lower == 0 }})
{% endif %}
{% endfor %}
{% for rpm in rcm.property_mappings %}
{% if not is_primitive(rpm.property) and rpm.property.type.name != rcm.clazz.name %}
        self.__{{to_python_name(rpm.property)}} = {{rpm.property.type.name}}RelatedFinder('{{rpm.property.name}}', Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}),Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}), {{ parent_expr(rpm.target, 'self.__node') }}, embedded={% if flatten_embedded(rpm.target) %}_embedded_for_{{to_python_name(rpm.property)}}(embedded, self.__node, '{{rpm.property.name}}'){% else %}{k[len('{{rpm.property.id}}.'):]: v for k, v in (embedded or {}).items() if k.startswith('{{rpm.property.id}}.')} or None{% endif %}, many={{ 'True' if rpm.property.multiplicity.is_many() else 'False' }}, optional={{ rpm.property.multiplicity.lower == 0 }})
{% endif %}
{% endfor %}
{% for pm in rcm.embedded_mappings %}
        self.__{{to_python_name(pm.property)}} = _{{rcm.clazz.name}}{{pm.property.id[:1]|upper}}{{pm.property.id[1:]}}Embedded(self.__node, display_prefix(relation_name, parent_join))
{% endfor %}
{% for source_rcm, rpm, assoc, reverse_name in reverse_assocs %}
        self.__{{reverse_name}} = None
{% endfor %}
        if kwargs:
            join.filter_kwargs = kwargs
            join.filter_column_map = {{rcm.clazz.name}}RelatedFinder._column_map

{% for rpm in rcm.property_mappings %}
{% if is_primitive(rpm.property) %}
    def {{to_python_name(rpm.property)}}(self) -> {{rpm.property.type.name}}Attribute:
{% if rpm.property.tagged_values['doc'] %}
        """{{display_name(rpm.property)}}
        {{rpm.property.tagged_values['doc'].value}}
        """
{% else %}
        """{{display_name(rpm.property)}}"""
{% endif %}
        return self.__{{to_python_name(rpm.property)}}

{% elif rpm.property.type.name != rcm.clazz.name %}
    def {{to_python_name(rpm.property)}}(self, business_date: Union[datetime.date, str, None] = None,
            processing_time: Union[datetime.datetime, datetime.date, str, None] = None, **kwargs) -> {{rpm.property.type.name}}RelatedFinder:
{% if rpm.property.tagged_values['doc'] %}
        """{{display_name(rpm.property)}}
        {{rpm.property.tagged_values['doc'].value}}
        """
{% else %}
        """{{display_name(rpm.property)}}"""
{% endif %}
        if kwargs or business_date is not None or processing_time is not None:
            return {{rpm.property.type.name}}RelatedFinder('{{rpm.property.name}}', Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}), {{ parent_expr(rpm.target, 'self.__node') }}, many={{ 'True' if rpm.property.multiplicity.is_many() else 'False' }}, optional={{ rpm.property.multiplicity.lower == 0 }}, business_date=business_date, processing_time=processing_time, **kwargs)
        return self.__{{to_python_name(rpm.property)}}

{% endif %}
{% endfor %}
{% for pm in rcm.embedded_mappings %}
    def {{to_python_name(pm.property)}}(self) -> _{{rcm.clazz.name}}{{pm.property.id[:1]|upper}}{{pm.property.id[1:]}}Embedded:
        """{{display_name(pm.property)}}"""
        return self.__{{to_python_name(pm.property)}}

{% endfor %}
{% for source_rcm, rpm, assoc, reverse_name in reverse_assocs %}
    def {{reverse_name}}(self, business_date: Union[datetime.date, str, None] = None,
            processing_time: Union[datetime.datetime, datetime.date, str, None] = None, **kwargs) -> {{source_rcm.clazz.name}}RelatedFinder:
        """{{assoc.target_property.name}}"""
        if kwargs or business_date is not None or processing_time is not None:
            from {{class_module_map[source_rcm.clazz.name]}} import {{source_rcm.clazz.name}}RelatedFinder
            return {{source_rcm.clazz.name}}RelatedFinder('{{assoc.target_property.name}}', Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}), Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), self.__node, many={{ 'True' if assoc.target_multiplicity.is_many() else 'False' }}, optional={{ assoc.target_multiplicity.lower == 0 }}, business_date=business_date, processing_time=processing_time, **kwargs)
        if self.__{{reverse_name}} is None:
            from {{class_module_map[source_rcm.clazz.name]}} import {{source_rcm.clazz.name}}RelatedFinder
            self.__{{reverse_name}} = {{source_rcm.clazz.name}}RelatedFinder('{{assoc.target_property.name}}', Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}),Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), self.__node, many={{ 'True' if assoc.target_multiplicity.is_many() else 'False' }}, optional={{ assoc.target_multiplicity.lower == 0 }})
        return self.__{{reverse_name}}

{% endfor %}

class {{rcm.clazz.name}}Finder({{rcm.clazz.name}}FinderBase):
{% if rcm.clazz.tagged_values['doc'] %}
    """{{rcm.clazz.tagged_values['doc'].value}}
    """
{% endif %}

{% if rcm.milestone_mapping %}
{% if has_bitemporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BiTemporalColumns(Column('{{rcm.milestone_mapping._date_from.target.name}}', '{{rcm.milestone_mapping._date_from.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._date_to.target.name}}', '{{rcm.milestone_mapping._date_to.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                 Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_business_date_range(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BusinessDateRangeColumns(Column('{{rcm.milestone_mapping._date_from.target.name}}', '{{rcm.milestone_mapping._date_from.target.type}}'),
                                                        Column('{{rcm.milestone_mapping._date_to.target.name}}', '{{rcm.milestone_mapping._date_to.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_business_date_and_processing(rcm.milestone_mapping) %}
    __zz_milestoning_columns = BusinessDateAndProcessingTemporalColumns(Column('{{rcm.milestone_mapping._date.target.name}}', '{{rcm.milestone_mapping._date.target.type}}'),
                                                                        Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                                        Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_processing_temporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = ProcessingTemporalColumns(Column('{{rcm.milestone_mapping._in.target.name}}', '{{rcm.milestone_mapping._in.target.type}}'),
                                                         Column('{{rcm.milestone_mapping._out.target.name}}', '{{rcm.milestone_mapping._out.target.type}}'){% if rcm.milestone_mapping.infinite_datetime %}, infinite_datetime='{{rcm.milestone_mapping.infinite_datetime}}'{% endif %})
{% elif has_processing_snapshot(rcm.milestone_mapping) %}
    __zz_milestoning_columns = SingleProcessingDatetimeColumn(Column('{{rcm.milestone_mapping._datetime.target.name}}', '{{rcm.milestone_mapping._datetime.target.type}}'))
{% elif has_uni_business_temporal(rcm.milestone_mapping) %}
    __zz_milestoning_columns = SingleBusinessDateColumn(Column('{{rcm.milestone_mapping._date.target.name}}', '{{rcm.milestone_mapping._date.target.type}}'))
{% endif %}
    __table = MilestonedTable('{{table_qualified_name(rcm.property_mappings[0].target.table)}}', [{% for rpm in rcm.property_mappings %}{% if is_primitive(rpm.property) and rpm.target.primary_key %}Column('{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', primary_key=True), {% endif %}{% endfor %}], __zz_milestoning_columns)
{% else %}
    __table = Table('{{table_qualified_name(rcm.property_mappings[0].target.table)}}', [{% for rpm in rcm.property_mappings %}{% if is_primitive(rpm.property) and rpm.target.primary_key %}Column('{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', primary_key=True), {% endif %}{% endfor %}])
{% endif %}

{% for rpm in rcm.property_mappings %}
{% if is_primitive(rpm.property) %}
    __{{to_python_name(rpm.property)}} = {{rpm.property.type.name}}Attribute('{{display_name(rpm.property)}}', '{{rpm.target.name}}', '{{rpm.target.type}}', '{{table_qualified_name(rpm.target.table)}}', nullable={{rpm.target.nullable}}, optional={{ rpm.property.multiplicity.lower == 0 }})
{% endif %}
{% endfor %}
{% for rpm in rcm.property_mappings %}
{% if not is_primitive(rpm.property) %}
    __{{to_python_name(rpm.property)}} = {{rpm.property.type.name}}RelatedFinder('{{rpm.property.name}}', Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}),Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}){% if rpm.property.type.name != rcm.clazz.name and flatten_embedded(rpm.target) %}, embedded=_embedded_for_{{to_python_name(rpm.property)}}(None, None, '{{rpm.property.name}}'){% endif %}, {% if rpm.target.via %}parent_join={{ parent_expr(rpm.target, 'None') }}, {% endif %}many={{ 'True' if rpm.property.multiplicity.is_many() else 'False' }}, optional={{ rpm.property.multiplicity.lower == 0 }})
{% endif %}
{% endfor %}
{% for pm in rcm.embedded_mappings %}
    __{{to_python_name(pm.property)}} = _{{rcm.clazz.name}}{{pm.property.id[:1]|upper}}{{pm.property.id[1:]}}Embedded()
{% endfor %}
{% if reverse_assocs %}

    def __init__(self):
{% for source_rcm, rpm, assoc, reverse_name in reverse_assocs %}
        self.__{{reverse_name}} = None
{% endfor %}

{% endif %}
{% for rpm in rcm.property_mappings %}
{% if is_primitive(rpm.property) %}
    def {{to_python_name(rpm.property)}}(self) -> {{rpm.property.type.name}}Attribute:
{% if rpm.property.tagged_values['doc'] %}
        """{{display_name(rpm.property)}}
        {{rpm.property.tagged_values['doc'].value}}
        """
{% else %}
        """{{display_name(rpm.property)}}"""
{% endif %}
        return {{rcm.clazz.name}}Finder.__{{to_python_name(rpm.property)}}

{% else %}
    def {{to_python_name(rpm.property)}}(self, business_date: Union[datetime.date, str, None] = None,
            processing_time: Union[datetime.datetime, datetime.date, str, None] = None, **kwargs) -> {{rpm.property.type.name}}RelatedFinder:
{% if rpm.property.tagged_values['doc'] %}
        """{{display_name(rpm.property)}}
        {{rpm.property.tagged_values['doc'].value}}
        """
{% else %}
        """{{display_name(rpm.property)}}"""
{% endif %}
        if kwargs or business_date is not None or processing_time is not None:
            return {{rpm.property.type.name}}RelatedFinder('{{rpm.property.name}}', Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}), {{ parent_expr(rpm.target, 'None') }}, many={{ 'True' if rpm.property.multiplicity.is_many() else 'False' }}, optional={{ rpm.property.multiplicity.lower == 0 }}, business_date=business_date, processing_time=processing_time, **kwargs)
        return {{rcm.clazz.name}}Finder.__{{to_python_name(rpm.property)}}

{% endif %}
{% endfor %}
{% for pm in rcm.embedded_mappings %}
    def {{to_python_name(pm.property)}}(self) -> _{{rcm.clazz.name}}{{pm.property.id[:1]|upper}}{{pm.property.id[1:]}}Embedded:
        """{{display_name(pm.property)}}"""
        return {{rcm.clazz.name}}Finder.__{{to_python_name(pm.property)}}

{% endfor %}
{% for source_rcm, rpm, assoc, reverse_name in reverse_assocs %}
    def {{reverse_name}}(self, business_date: Union[datetime.date, str, None] = None,
            processing_time: Union[datetime.datetime, datetime.date, str, None] = None, **kwargs) -> {{source_rcm.clazz.name}}RelatedFinder:
        """{{assoc.target_property.name}}"""
        if kwargs or business_date is not None or processing_time is not None:
            from {{class_module_map[source_rcm.clazz.name]}} import {{source_rcm.clazz.name}}RelatedFinder
            return {{source_rcm.clazz.name}}RelatedFinder('{{assoc.target_property.name}}', Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}), Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), None, many={{ 'True' if assoc.target_multiplicity.is_many() else 'False' }}, optional={{ assoc.target_multiplicity.lower == 0 }}, business_date=business_date, processing_time=processing_time, **kwargs)
        if self.__{{reverse_name}} is None:
            from {{class_module_map[source_rcm.clazz.name]}} import {{source_rcm.clazz.name}}RelatedFinder
            self.__{{reverse_name}} = {{source_rcm.clazz.name}}RelatedFinder('{{assoc.target_property.name}}', Column('{{rpm.target.rhs.name}}', '{{rpm.target.rhs.type}}', '{{table_qualified_name(rpm.target.rhs.table)}}'{% if rpm.target.rhs.primary_key %}, primary_key=True{% endif %}),Column('{{rpm.target.lhs.name}}', '{{rpm.target.lhs.type}}', '{{table_qualified_name(rpm.target.lhs.table)}}'{% if rpm.target.lhs.primary_key %}, primary_key=True{% endif %}), many={{ 'True' if assoc.target_multiplicity.is_many() else 'False' }}, optional={{ assoc.target_multiplicity.lower == 0 }})
        return self.__{{reverse_name}}

{% endfor %}
    def count(self) -> CountAllOperation:
        """Count all rows in the underlying table."""
        return CountAllOperation('{{table_qualified_name(rcm.property_mappings[0].target.table)}}')

    def find_all(self,
                 business_date: Union[datetime.date, str],
                 processing_time: Union[datetime.datetime, datetime.date, str],
                 display_columns: list[Attribute],
                 filter_op: Operation = NoOperation()) -> FinderResult:
        """Returns data matching the optional filter."""
{% if rcm.milestone_mapping %}
{% if has_bitemporal(rcm.milestone_mapping) %}
        return convert_inputs_and_select(business_date, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% elif has_business_date_and_processing(rcm.milestone_mapping) %}
        return convert_inputs_and_select(business_date, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% elif has_processing_temporal(rcm.milestone_mapping) %}
        return convert_inputs_and_select(None, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% elif has_processing_snapshot(rcm.milestone_mapping) %}
        return convert_inputs_and_select(None, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% elif has_business_date_range(rcm.milestone_mapping) %}
        return convert_inputs_and_select(business_date, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% elif has_uni_business_temporal(rcm.milestone_mapping) %}
        return convert_inputs_and_select(business_date, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% endif %}
{% else %}
        return convert_inputs_and_select(business_date, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% endif %}
{% if rcm.milestone_mapping and (has_bitemporal(rcm.milestone_mapping) or has_business_date_and_processing(rcm.milestone_mapping) or has_business_date_range(rcm.milestone_mapping) or has_uni_business_temporal(rcm.milestone_mapping)) %}

    def find_for_date_range(self,
                            business_date_from: Union[datetime.date, str],
                            business_date_to: Union[datetime.date, str],
                            processing_time: Union[datetime.datetime, datetime.date, str],
                            display_columns: list[Attribute],
                            filter_op: Operation = NoOperation()) -> FinderResult:
        """Returns data for all records whose business date overlaps the given range.

        For bitemporal tables the range uses interval overlap:
            business_date_from_col <= business_date_to AND business_date_to_col > business_date_from
        For single-date tables:
            business_date >= business_date_from AND business_date <= business_date_to
        """
        return convert_inputs_and_select_for_date_range(business_date_from, business_date_to, processing_time, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% endif %}
{% if rcm.milestone_mapping and has_processing_snapshot(rcm.milestone_mapping) %}

    def find_for_processing_range(self,
                                  processing_from: Union[datetime.datetime, datetime.date, str],
                                  processing_to: Union[datetime.datetime, datetime.date, str],
                                  business_date: Union[datetime.date, str, None],
                                  display_columns: list[Attribute],
                                  filter_op: Operation = NoOperation()) -> FinderResult:
        """Returns the rows taken at any processing time from ``processing_from`` up to and including ``processing_to``.

        The processing snapshot column is compared with both ends of the range. A table joined from this one with a
        processing range is read over the same range, and one with a single snapshot column is read within it.
        """
        return convert_inputs_and_select_for_processing_range(business_date, processing_from, processing_to, display_columns, {{rcm.clazz.name}}Finder.__table, filter_op)  # type: ignore[arg-type]
{% endif %}

