from __future__ import annotations

{% if data_class.has_primitive_field_of_type((dm.Timestamp, dm.Date)) %}
import datetime
{% endif %}
import warnings
from collections.abc import Sequence
{% if data_class.has_dependencies %}
from typing import TYPE_CHECKING, Any, ClassVar, Literal,  no_type_check, Optional, Union
{% else %}
from typing import Any, ClassVar, Literal, no_type_check, Optional, Union
{% endif %}
{% if not data_class.use_optional_type %}
{% endif %}

from cognite.client import data_modeling as dm, CogniteClient
{% if data_class.has_primitive_field_of_type(dm.CDFExternalIdReference) %}
from cognite.client.data_classes import (
    {% if data_class.has_primitive_field_of_type(dm.FileReference) %}
    FileMetadata as CogniteFileMetadata,
    FileMetadataWrite as CogniteFileMetadataWrite,
    {% endif %}
    {% if data_class.has_primitive_field_of_type(dm.TimeSeriesReference) %}
    TimeSeries as CogniteTimeSeries,
    TimeSeriesWrite as CogniteTimeSeriesWrite,
    {% endif %}
    {% if data_class.has_primitive_field_of_type(dm.SequenceReference) %}
    Sequence as CogniteSequence,
    SequenceWrite as CogniteSequenceWrite,
    {% endif %}
)
{% endif %}
{% if data_class.use_pydantic_field %}
{{ data_class.import_pydantic_field }}
{% endif %}
from pydantic import field_validator, model_validator

from {{ top_level_package }}.data_classes._core import (
    {% if has_default_instance_space %}
    DEFAULT_INSTANCE_SPACE,
    {% endif %}
    DEFAULT_QUERY_LIMIT,
    {% if data_class.is_cognite_timeseries %}
    DataPointsAPI,
    {% endif %}
    {% if data_class.is_cognite_file %}
    FileContentAPI,
    {% endif %}
    DataRecord,
    DataRecordGraphQL,
    DataRecordWrite,
    DomainModel,
    DomainModelWrite,
    DomainModelWriteList,
    DomainModelList,
    DomainRelation,
    DomainRelationWrite,
    GraphQLCore,
    ResourcesWrite,
    {% if data_class.has_primitive_field_of_type(dm.CDFExternalIdReference) %}
    FileMetadata,
    FileMetadataWrite,
    FileMetadataGraphQL,
    {% endif %}
    {% if data_class.has_primitive_field_of_type(dm.TimeSeriesReference) %}
    TimeSeries,
    TimeSeriesWrite,
    TimeSeriesGraphQL,
    TimeSeriesReferenceAPI,
    {% endif %}
    {% if data_class.has_primitive_field_of_type(dm.SequenceReference) %}
    SequenceRead,
    SequenceWrite,
    SequenceGraphQL,
    {% endif %}
    T_DomainModelList,
    as_direct_relation_reference,
    as_instance_dict_id,
    as_node_id,
    as_pygen_node_id,
    are_nodes_equal,
    is_tuple_id,
    select_best_node,
    QueryCore,
    NodeQueryCore,
    StringFilter,
    {% if data_class.has_filtering_fields %}
    {{ data_class.filtering_import }}
    {% endif %}
)
{% if data_class.implements %}
{% for implements in data_class.implements %}
from {{ top_level_package }}.data_classes.{{ implements.file_name }} import {{ implements.read_name }}{% if data_class.is_writable or data_class.is_interface %}, {{ implements.write_name }}{% endif +%}
{% endfor %}
{% endif %}
{% if data_class.has_dependencies_not_self %}
if TYPE_CHECKING:
    {% for dependency_class in data_class.dependencies %}
    {% if dependency_class.file_name != data_class.file_name %}
    from {{ top_level_package }}.data_classes.{{ dependency_class.file_name }} import {{ dependency_class.read_name }}, {{ dependency_class.read_list_name }}, {{ dependency_class.graphql_name }}{% if dependency_class.is_writable or dependency_class.is_interface %}, {{ dependency_class.write_name }}, {{ dependency_class.write_list_name }}{% endif +%}
    {% endif %}
    {% endfor %}
{% endif %}


__all__ = [
    "{{ data_class.read_name }}",
    {% if data_class.is_writable or data_class.is_interface %}
    "{{ data_class.write_name }}",
    "{{ data_class.read_name }}Apply",
    {% endif %}
    "{{ data_class.read_list_name }}",
    {% if data_class.is_writable or data_class.is_interface %}
    "{{ data_class.write_list_name }}",
    "{{ data_class.read_name }}ApplyList",
    {% endif %}
    {% if data_class.has_field_of_type(ft.BasePrimitiveField) %}
    "{{ data_class.field_names }}",
    {% endif %}
    {% if data_class.has_primitive_field_of_type((dm.Text, dm.CDFExternalIdReference)) %}
    "{{ data_class.text_field_names }}",
    {% endif %}
    "{{ data_class.graphql_name }}",
]


{{ data_class.text_field_names }} = Literal["external_id", {{ data_class.text_fields_literals }}]
{{ data_class.field_names }} = Literal["external_id", {{ data_class.fields_literals }}]

{{ data_class.properties_dict_name }} = {{'{'}}
    "external_id": "externalId",
    {% for field in data_class.fields_of_type(ft.BasePrimitiveField) %}
    "{{ field.name }}": "{{ field.prop_name }}",
    {% endfor %}
{{'}'}}


class {{ data_class.graphql_name }}(GraphQLCore{% if data_class.has_any_field_model_prefix %}, protected_namespaces=(){% endif %}):
    """This represents the reading version of {{ data_class.doc_name }}, used
    when data is retrieved from CDF using GraphQL.

    It is used when retrieving data from CDF using GraphQL.

    Args:
        space: The space where the node is located.
        external_id: The external id of the {{ data_class.doc_name }}.
        data_record: The data record of the {{ data_class.doc_name }} node.
        {% for field in data_class %}
        {{ field.name }}: {{ field.argument_documentation }}
        {% endfor %}
    """

    view_id: ClassVar[dm.ViewId] = dm.ViewId("{{ data_class.view_id.space }}", "{{ data_class.view_id.external_id }}", "{{ data_class.view_id.version }}")
    {% for field in data_class %}
    {{ field.name }}: {{ field.as_graphql_type_hint() }}
    {% endfor %}

    @model_validator(mode="before")
    def parse_data_record(cls, values: Any) -> Any:
        if not isinstance(values, dict):
            return values
        if "lastUpdatedTime" in values or "createdTime" in values:
            values["dataRecord"] = DataRecordGraphQL(
                created_time=values.pop("createdTime", None),
                last_updated_time=values.pop("lastUpdatedTime", None),
            )
        return values

    {% if data_class.has_field_of_type(ft.CDFExternalListField) %}
    @field_validator({% for field in data_class.fields_of_type(ft.CDFExternalListField) %}"{{ field.name }}", {% endfor %}mode="before")
    def clean_list(cls, value: Any) -> Any:
        if isinstance(value, list):
            return [v for v in value if v is not None] or None
        return value
    {% endif %}

    {% if data_class.has_field_of_type(ft.BaseConnectionField) %}
    @field_validator({% for field in data_class.fields_of_type(ft.BaseConnectionField) %}"{{ field.name }}", {% endfor %}mode="before")
    def parse_graphql(cls, value: Any) -> Any:
        if not isinstance(value, dict):
            return value
        if "items" in value:
            return value["items"]
        return value
    {% endif %}

    # We do the ignore argument type as we let pydantic handle the type checking
    @no_type_check
    def as_read(self) -> {{ data_class.read_name }}:
        """Convert this GraphQL format of {{ data_class.doc_name }} to the reading format."""
        if self.data_record is None:
            raise ValueError("This object cannot be converted to a read format because it lacks a data record.")
        return {{ data_class.read_name }}(
            space=self.space,
            external_id=self.external_id,
            data_record=DataRecord(
                version=0,
                last_updated_time=self.data_record.last_updated_time,
                created_time=self.data_record.created_time,
            ),
            {% for field in data_class %}
            {{ field.name }}={{ field.as_read_graphql() }},
            {% endfor %}
        )

    {% if data_class.is_writable or data_class.is_interface %}
    # We do the ignore argument type as we let pydantic handle the type checking
    @no_type_check
    def as_write(self) -> {{ data_class.write_name }}:
        """Convert this GraphQL format of {{ data_class.doc_name }} to the writing format."""
        return {{ data_class.write_name }}(
            space=self.space,
            external_id=self.external_id,
            data_record=DataRecordWrite(existing_version=0),
            {% for field in data_class %}
            {% if field.is_write_field %}
            {{ field.name }}={{ field.as_write_graphql() }},
            {% endif %}
            {% endfor %}
        )
    {% endif %}


class {{ data_class.read_name }}({{ data_class.read_base_class }}{% if data_class.has_any_field_model_prefix %}, protected_namespaces=(){% endif %}):
    """This represents the reading version of {{ data_class.doc_name }}.

    It is used to when data is retrieved from CDF.

    Args:
        space: The space where the node is located.
        external_id: The external id of the {{ data_class.doc_name }}.
        data_record: The data record of the {{ data_class.doc_name }} node.
        {% for field in data_class %}
        {{ field.name }}: {{ field.argument_documentation }}
        {% endfor %}
    """

    _view_id: ClassVar[dm.ViewId] = {{ data_class.view_id_str }}

    {% if not data_class.implements %}
    space: str{% if has_default_instance_space %} = DEFAULT_INSTANCE_SPACE{% endif +%}
    {% endif %}
    node_type: Union[dm.DirectRelationReference, None] = {% if data_class.node_type %}dm.DirectRelationReference("{{ data_class.node_type.space }}", "{{ data_class.node_type.external_id }}"){% else %}None{% endif +%}
    {% for field in data_class.read_fields %}
    {{ field.name }}: {{ field.as_read_type_hint() }}
    {% endfor %}

{% if data_class.is_writable or data_class.is_interface %}
    def as_write(self) -> {{ data_class.write_name }}:
        """Convert this read version of {{ data_class.doc_name }} to the writing version."""
        return {{ data_class.write_name }}(
            space=self.space,
            external_id=self.external_id,
            data_record=DataRecordWrite(existing_version=self.data_record.version),
            {% for field in data_class %}
            {% if field.is_write_field %}
            {{ field.name }}={{ field.as_write() }},
            {% endif %}
            {% endfor %}
        )

    def as_apply(self) -> {{ data_class.write_name }}:
        """Convert this read version of {{ data_class.doc_name }} to the writing version."""
        warnings.warn(
            "as_apply is deprecated and will be removed in v1.0. Use as_write instead.",
            UserWarning,
            stacklevel=2,
        )
        return self.as_write(){% if data_class.has_dependencies %}

    @classmethod
    def _update_connections(
        cls,
        instances: dict[dm.NodeId{% if has_default_instance_space %} | str{% endif %}, {{ data_class.read_name }}],  # type: ignore[override]
        nodes_by_id: dict[dm.NodeId{% if has_default_instance_space %} | str{% endif %}, DomainModel],
        edges_by_source_node: dict[dm.NodeId, list[dm.Edge | DomainRelation]],
    ) -> None:
        {% if data_class.has_dependencies_not_self %}
        {% for dependency_class in data_class.dependencies %}
        {% if dependency_class.file_name != data_class.file_name %}
        from .{{ dependency_class.file_name }} import {{ dependency_class.read_name }}
        {% endif %}
        {% endfor %}
        {% endif %}
        {% if data_class.has_edges_or_direct_relations_with_target %}
        for instance in instances.values():
            {% for field in data_class.one_to_one_direct_relations_with_source %}
            if isinstance(instance.{{ field.name }}, {% if has_default_instance_space %}(dm.NodeId, str){% else %}dm.NodeId{% endif %}) and ({{ field.name }} := nodes_by_id.get(instance.{{ field.name }})) and isinstance(
                    {{ field.name }}, {{ field.destination_class.read_name }}
            ):
                instance.{{ field.name }} = {{ field.name }}
            {% endfor %}
            {% for field in data_class.one_to_many_direct_relations_with_source %}
            if instance.{{ field.name }}:
                new_{{ field.name }}: list[{{ field.destination_class.read_name }}{% if has_default_instance_space %} | str{% endif %} | dm.NodeId] = []
                for relation in instance.{{ field.name }}:
                    if isinstance(relation, {{ field.destination_class.read_name }}):
                        new_{{ field.name }}.append(relation)
                    elif (other := nodes_by_id.get(relation)) and isinstance(
                        other, {{ field.destination_class.read_name }}
                    ):
                        new_{{ field.name }}.append(other)
                    else:
                        new_{{ field.name }}.append(relation)
                instance.{{ field.name }} = new_{{ field.name }}
            {% endfor %}
            {% if data_class.has_edges %}
            if edges := edges_by_source_node.get(instance.as_id()):
                {% for field in data_class.one_to_many_edges_without_properties %}
                {{ field.name }}: list[{{ field.destination_class.read_name }}{% if has_default_instance_space %} | str{% endif %} | dm.NodeId] = []
                {% endfor %}
                {% for field in data_class.one_to_many_edges_with_properties %}
                {{ field.name }}: list[{{ field.edge_class.read_name }}] = []
                {% endfor %}
                for edge in edges:
                    value: DomainModel | DomainRelation{% if has_default_instance_space %} | str{% endif %} | dm.NodeId
                    if isinstance(edge, DomainRelation):
                        value = edge
                    else:
                        other_end: dm.DirectRelationReference = (
                            edge.end_node
                            if edge.start_node.space == instance.space
                            and edge.start_node.external_id == instance.external_id
                            else edge.start_node
                        )
                        destination:{% if has_default_instance_space %} dm.NodeId | str = (
                            as_node_id(other_end)
                            if other_end.space != DEFAULT_INSTANCE_SPACE
                            else other_end.external_id
                        ){% else %} dm.NodeId = as_node_id(other_end){% endif +%}
                        if destination in nodes_by_id:
                            value = nodes_by_id[destination]
                        else:
                            value = destination
                    edge_type = edge.edge_type if isinstance(edge, DomainRelation) else edge.type

                    {% for field in data_class.one_to_many_edges_without_properties %}
                    if edge_type == {{ field.edge_type_str }} and isinstance(
                        value, ({{ field.destination_class.read_name }}{% if has_default_instance_space %}, str{% endif %}, dm.NodeId)
                    ):
                        {{ field.name }}.append(value)
                    {% endfor %}
                    {% for field in data_class.one_to_many_edges_with_properties %}
                    if edge_type == {{ field.edge_type_str }} and isinstance(
                        value, {{ field.edge_class.read_name }}
                    ):
                        {{ field.name }}.append(value)
                        if end_node := nodes_by_id.get(as_pygen_node_id(value.end_node)):
                            value.end_node = end_node  # type: ignore[assignment]
                    {% endfor %}
                    {% for field in data_class.one_to_one_edge_without_properties %}
                    if edge_type == {{ field.edge_type_str }} and isinstance(
                        value, ({{ field.destination_class.read_name }}{% if has_default_instance_space %}, str{% endif %}, dm.NodeId)
                    ):
                        if instance.{{ field.name }} is None:
                            instance.{{ field.name }} = value
                        elif are_nodes_equal(value, instance.{{ field.name }}):
                            instance.{{ field.name }} = select_best_node(value, instance.{{ field.name }})
                        else:
                            warnings.warn(
                                f"Expected one edge for '{{ field.name }}' in {instance.as_id()}."
                                f"Ignoring new edge {value!s} in favor of {instance.{{ field.name }}!s}."
                            )
                    {% endfor %}
                    {% for field in data_class.one_to_one_edges_with_properties %}
                    if edge_type == {{ field.edge_type_str }} and isinstance(
                        value, {{ field.edge_class.read_name }}
                    ):
                        if instance.{{ field.name }} is None:
                            instance.{{ field.name }} = value
                        elif instance.{{ field.name }} == value:
                            # This is the same edge, so we don't need to do anything...
                            ...
                        else:
                            warnings.warn(
                                f"Expected one edge for '{{ field.name }}' in {instance.as_id()}."
                                f"Ignoring new edge {value!s} in favor of {instance.{{ field.name }}!s}."
                            )

                        if end_node := nodes_by_id.get(as_pygen_node_id(value.end_node)):
                            value.end_node = end_node  # type: ignore[assignment]

                    {% endfor %}

                {% for field in data_class.one_to_many_edges_without_properties %}
                instance.{{ field.name }} = {{ field.name }} or None
                {% endfor %}
                {% for field in data_class.one_to_many_edges_with_properties %}
                instance.{{ field.name }} = {{ field.name }}
                {% endfor %}

            {% endif %}
        {% endif %}
        {% if data_class.has_reverse_direct_relations %}
        for node in nodes_by_id.values():
            {% for field in data_class.one_to_one_reverse_direct_relation %}
            if (
                isinstance(node, {{ field.destination_class.read_name }})
                and node.{{ field.reverse_property.name }} is not None
                and ({{  field.reverse_property.name }} := instances.get(as_pygen_node_id(node.{{ field.reverse_property.name }})))
            ):
                if {{  field.reverse_property.name }}.{{ field.name }} is None:
                    {{  field.reverse_property.name }}.{{ field.name }} = node
                elif are_nodes_equal(node, {{  field.reverse_property.name }}.{{ field.name }}):
                    # This is the same node, so we don't need to do anything...
                    ...
                else:
                    warnings.warn(
                        f"Expected one direct relation for '{{ field.name }}' in {% raw %}{{% endraw %}{{  field.reverse_property.name }}.as_id(){% raw %}}{% endraw %}."
                        f"Ignoring new relation {node!s} in favor of {% raw %}{{% endraw %}{{  field.reverse_property.name }}.{{ field.name }}!s{% raw %}}{% endraw %}."
                    )
            {% endfor %}
            {% for field in data_class.one_to_many_reverse_direct_relations %}
            {% if field.reverse_property.is_one_to_one %}
            if (
                isinstance(node, {{ field.destination_class.read_name }})
                and node.{{  field.reverse_property.name }} is not None
                and ({{  field.reverse_property.name }} := instances.get(as_pygen_node_id(node.{{  field.reverse_property.name }})))
            ):
                if {{  field.reverse_property.name }}.{{ field.name }} is None:
                    {{  field.reverse_property.name }}.{{ field.name }} = []
                {{ field.reverse_property.name }}.{{ field.name }}.append(node)
            {% else %}
            if (
                isinstance(node, {{ field.destination_class.read_name }})
                and node.{{  field.reverse_property.name }} is not None
            ):
                for {{  field.reverse_property.name }} in node.{{  field.reverse_property.name }}:
                    if this_instance := instances.get(as_pygen_node_id({{  field.reverse_property.name }})):
                        if this_instance.{{ field.name }} is None:
                            this_instance.{{ field.name }} = [node]
                        else:
                            this_instance.{{ field.name }}.append(node)
            {% endif %}
            
            {% endfor %}
        {% endif %}
{% endif %}


class {{ data_class.write_name }}({{ data_class.write_base_class }}{% if data_class.has_any_field_model_prefix %}, protected_namespaces=(){% endif %}):
    """This represents the writing version of {{ data_class.doc_name }}.

    It is used to when data is sent to CDF.

    Args:
        space: The space where the node is located.
        external_id: The external id of the {{ data_class.doc_name }}.
        data_record: The data record of the {{ data_class.doc_name }} node.
        {% for field in data_class %}
        {% if field.is_write_field %}
        {{ field.name }}: {{ field.argument_documentation }}
        {% endif %}
        {% endfor %}
    """

    _view_id: ClassVar[dm.ViewId] = {{ data_class.view_id_str }}

    {% if not data_class.implements %}
    space: str{% if has_default_instance_space %} = DEFAULT_INSTANCE_SPACE{% endif +%}
    {% endif %}
    node_type: Union[dm.DirectRelationReference, dm.NodeId, tuple[str, str], None] = {% if data_class.node_type %}dm.DirectRelationReference("{{ data_class.node_type.space }}", "{{ data_class.node_type.external_id }}"){% else %}None{% endif +%}
    {% for field in data_class.write_fields %}
    {{ field.name }}: {{ field.as_write_type_hint() }}
    {% endfor %}

    {% if data_class.has_write_connection_fields %}
    @field_validator({% for field in data_class.write_connection_fields %}"{{ field.name }}", {% endfor %}mode="before")
    def as_node_id(cls, value: Any) -> Any:
        if isinstance(value, dm.DirectRelationReference):
            return dm.NodeId(value.space, value.external_id)
        elif isinstance(value, tuple) and len(value) == 2 and all(isinstance(item, str) for item in value):
            return dm.NodeId(value[0], value[1])
        elif isinstance(value, list):
            return [cls.as_node_id(item) for item in value]
        return value
    {% endif %}

    def _to_instances_write(
        self,
        cache: set[tuple[str, str]],
        write_none: bool = False,
        allow_version_increase: bool = False,
    ) -> ResourcesWrite:
        {% if data_class.has_edge_with_property %}
        {% for dependency_class in data_class.dependencies_edges %}
        from .{{ dependency_class.file_name }} import {{ dependency_class.write_name }}
        {% endfor %}
        {% endif %}
        resources = ResourcesWrite()
        if self.as_tuple_id() in cache:
            return resources
        {% if not data_class.has_container_fields_writable %}
        cache.add(self.as_tuple_id())

        this_node = dm.NodeApply(
            space=self.space,
            external_id=self.external_id,
            existing_version=None if allow_version_increase else self.data_record.existing_version,
            {% if data_class.node_type %}
            type=as_direct_relation_reference(self.node_type),
            {% endif %}
            sources=None,
        )
        resources.nodes.append(this_node)
        {% endif %}

        {% if data_class.has_container_fields_writable %}
        properties: dict[str, Any] = {}

        {% for field in data_class.container_fields_writable %}
        if self.{{ field.name }} is not None{% if field.is_nullable %} or write_none{% endif %}:
            properties["{{ field.prop_name }}"] = {{ field.as_value() }}

        {% endfor %}
        if properties:
            this_node = dm.NodeApply(
                space=self.space,
                external_id=self.external_id,
                existing_version=None if allow_version_increase else self.data_record.existing_version,
                type=as_direct_relation_reference(self.node_type),
                sources=[
                    dm.NodeOrEdgeData(
                        source=self._view_id,
                        properties=properties,
                )],
            )
            resources.nodes.append(this_node)
            cache.add(self.as_tuple_id())
        {% endif %}

        {% for field in data_class.one_to_many_edges_with_properties %}
        for {{ field.variable }} in self.{{ field.name }} or []:
            if isinstance({{ field.variable }}, DomainRelationWrite):
                other_resources = {{ field.variable }}._to_instances_write(
                    cache,
                    self,
                    {{ field.edge_type_str }},
                )
                resources.extend(other_resources)

        {% endfor %}
        {% for field in data_class.one_to_one_edges_with_properties %}
        if self.{{ field.name }} is not None:
            other_resources = self.{{ field.name }}._to_instances_write(
                cache,
                self,
                {{ field.edge_type_str }},
            )
            resources.extend(other_resources)

        {% endfor %}
        {% for field in data_class.one_to_many_edges_without_properties %}
        edge_type = {{ field.edge_type_str }}
        for {{ field.variable }} in self.{{ field.name }} or []:
            {% if field.edge_direction == 'outwards' %}
            other_resources = DomainRelationWrite.from_edge_to_resources(
                cache,
                start_node=self,
                end_node={{field.variable}},
                edge_type=edge_type,
                write_none=write_none,
                allow_version_increase=allow_version_increase,
            )
            {% else %}
            other_resources = DomainRelationWrite.from_edge_to_resources(
                cache,
                start_node={{field.variable}},
                end_node=self,
                edge_type=edge_type,
                write_none=write_none,
                allow_version_increase=allow_version_increase,
            )
            {% endif %}
            resources.extend(other_resources)

        {% endfor %}
        {% for field in data_class.one_to_one_direct_relations_with_source_writable %}
        if isinstance(self.{{ field.name }}, DomainModelWrite):
            other_resources = self.{{ field.name }}._to_instances_write(cache)
            resources.extend(other_resources)

        {% endfor %}
        {% for field in data_class.one_to_many_direct_relations_with_source_writable %}
        for {{ field.variable }} in self.{{ field.name }} or []:
            if isinstance({{ field.variable }}, DomainModelWrite):
                other_resources = {{ field.variable }}._to_instances_write(cache)
                resources.extend(other_resources)

        {% endfor %}
        {% for field in data_class.primitive_fields_of_type(dm.CDFExternalIdReference) %}
        {% if field.is_list %}
        for {{ field.variable }} in self.{{ field.name }} or []:
            if isinstance({{ field.variable }}, {{ field.cognite_type_name('write') }}):
                resources.{{ field.resource_write_name }}.append({{ field.variable }})
        {% else %}
        if isinstance(self.{{ field.name }}, {{ field.cognite_type_name('write') }}):
            resources.{{ field.resource_write_name }}.append(self.{{ field.name }})
        {% endif %}

        {% endfor %}
        {% for field in data_class.one_to_one_edge_without_properties %}
        if self.{{field.name}} is not None:
            other_resources = DomainRelationWrite.from_edge_to_resources(
                cache,
                {% if field.edge_direction == 'outwards' %}
                start_node=self,
                end_node=self.{{ field.name }},
                {% else %}
                start_node=self.{{ field.name }},
                end_node=self,
                {% endif %}
                edge_type={{ field.edge_type_str }},
                write_none=write_none,
                allow_version_increase=allow_version_increase,
            )
            resources.extend(other_resources)

        {% endfor %}
        return resources


class {{ data_class.read_name }}Apply({{ data_class.write_name }}):
    def __new__(cls, *args, **kwargs) -> {{ data_class.read_name }}Apply:
        warnings.warn(
            "{{ data_class.read_name }}Apply is deprecated and will be removed in v1.0. Use {{ data_class.write_name }} instead."
            "The motivation for this change is that Write is a more descriptive name for the writing version of the"
            "{{ data_class.read_name }}.",
            UserWarning,
            stacklevel=2,
        )
        return super().__new__(cls)

{% endif %}
class {{ data_class.read_list_name }}(DomainModelList[{{ data_class.read_name }}]):
    """List of {{ data_class.doc_list_name }} in the read version."""

    _INSTANCE = {{ data_class.read_name }}
{% if data_class.is_writable or data_class.is_interface %}
    def as_write(self) -> {{ data_class.write_list_name }}:
        """Convert these read versions of {{ data_class.doc_name }} to the writing versions."""
        return {{ data_class.write_list_name }}([node.as_write() for node in self.data])

    def as_apply(self) -> {{ data_class.write_list_name }}:
        """Convert these read versions of primitive nullable to the writing versions."""
        warnings.warn(
            "as_apply is deprecated and will be removed in v1.0. Use as_write instead.",
            UserWarning,
            stacklevel=2,
        )
        return self.as_write()

    {% for field in data_class.read_connection_fields_including_parents %}
    @property
    def {{ field.name }}(self) -> {{ field.linked_class.read_list_name }}:
        {% if field.linked_class.file_name != data_class.file_name %}
        from .{{ field.linked_class.file_name }} import {{ field.linked_class.read_name }}, {{ field.linked_class.read_list_name }}
        {% endif %}
        {% if field.is_one_to_one %}
        return {{ field.linked_class.read_list_name }}([item.{{ field.name }} for item in self.data if isinstance(item.{{ field.name }}, {{ field.linked_class.read_name }})]){% else %}
        return {{ field.linked_class.read_list_name }}([item for items in self.data for item in items.{{ field.name }} or [] if isinstance(item, {{ field.linked_class.read_name }})])
        {% endif %}

    {% endfor %}

class {{ data_class.write_list_name }}(DomainModelWriteList[{{ data_class.write_name }}]):
    """List of {{ data_class.doc_list_name }} in the writing version."""

    _INSTANCE = {{ data_class.write_name }}
    {% for field in data_class.write_connection_fields_including_parents %}
    @property
    def {{ field.name }}(self) -> {{ field.linked_class.write_list_name }}:
        {% if field.linked_class.file_name != data_class.file_name %}
        from .{{ field.linked_class.file_name }} import {{ field.linked_class.write_name }}, {{ field.linked_class.write_list_name }}
        {% endif %}
        {% if field.is_one_to_one %}
        return {{ field.linked_class.write_list_name }}([item.{{ field.name }} for item in self.data if isinstance(item.{{ field.name }}, {{ field.linked_class.write_name }})]){% else %}
        return {{ field.linked_class.write_list_name }}([item for items in self.data for item in items.{{ field.name }} or [] if isinstance(item, {{ field.linked_class.write_name }})])
        {% endif %}

    {% endfor %}

class {{ data_class.read_name }}ApplyList({{ data_class.write_list_name }}): ...

{% endif %}

def {{ data_class.filter_name }}(
    view_id: dm.ViewId,
    {% for parm in list_method.parameters %}
    {{ parm.name }}: {{ parm.annotation }} = {{ parm.default }},
    {% endfor %}
    filter: dm.Filter | None = None,
) -> dm.Filter | None:
    filters: list[dm.Filter] = []
    {% for filter in list_method.implementations %}
    if {{ filter.condition }}:
        filters.append({{ filter.filter_call }}({{ filter.arguments }}))
    {% endfor %}
    if filter:
        filters.append(filter)
    return dm.filters.And(*filters) if filters else None


class _{{ data_class.query_cls_name }}(NodeQueryCore[T_DomainModelList, {{ data_class.read_list_name }}]):
    _view_id = {{ data_class.read_name }}._view_id
    _result_cls = {{ data_class.read_name }}
    _result_list_cls_end = {{ data_class.read_list_name }}

    def __init__(
        self,
        created_types: set[type],
        creation_path: list[QueryCore],
        client: CogniteClient,
        result_list_cls: type[T_DomainModelList],
        expression: dm.query.ResultSetExpression | None = None,
        connection_name: str | None = None,
        connection_type: Literal["reverse-list"] | None = None,
        reverse_expression: dm.query.ResultSetExpression | None = None,
    ):
        {% for dependency_class in data_class.dependencies_with_edge_destinations %}
        {% if dependency_class.file_name != data_class.file_name %}
        from .{{ dependency_class.file_name }} import _{{ dependency_class.query_cls_name }}
        {% endif %}
        {% endfor %}

        super().__init__(
            created_types,
            creation_path,
            client,
            result_list_cls,
            expression,
            dm.filters.HasData(views=[self._view_id]),
            connection_name,
            connection_type,
            reverse_expression,
        )

        {% for field in data_class.fields_of_type(ft.BaseConnectionField) %}
        {% if not field.is_direct_relation_no_source %}
        if _{{ field.linked_class.query_cls_name }} not in created_types:
            self.{{ field.name }} = _{{ field.linked_class.query_cls_name }}(
                created_types.copy(),
                self._creation_path,
                client,
                result_list_cls,
                {% if field.is_edge_with_properties %}
                _{{ field.destination_class.query_cls_name }},
                {% endif %}
                {% if field.is_direct_relation %}
                dm.query.NodeResultSetExpression(
                    through=self._view_id.as_property_ref("{{ field.prop_name }}"),
                    direction="outwards",
                ),
                {% elif field.is_reverse_direct_relation %}
                dm.query.NodeResultSetExpression(
                    through={{ field.through_str }},
                    direction="inwards",
                ),
                {% else %}
                dm.query.EdgeResultSetExpression(
                    direction="{{ field.edge_direction }}",
                    chain_to="destination",
                ),
                {% endif %}
                connection_name="{{ field.name }}",
                {% if field.is_reverse_direct_relation and field.reverse_property.is_one_to_many %}
                connection_type="reverse-list",
                {% endif %}
            )
        {% endif %}

        {% endfor %}
        self.space = StringFilter(self, ["node", "space"])
        self.external_id = StringFilter(self, ["node", "externalId"])
        {% if data_class.has_filtering_fields %}
        {% for field in data_class.filtering_fields %}
        self.{{ field.name }} = {{ field.filtering_cls }}(self, self._view_id.as_property_ref("{{ field.prop_name }}"))
        {% endfor %}
        self._filter_classes.extend([
            self.space,
            self.external_id,
            {% for field in data_class.filtering_fields %}
            self.{{ field.name }},
            {% endfor %}
        ])
        {% endif %}
        {% if data_class.is_cognite_timeseries %}
        self.data = DataPointsAPI(client, lambda limit: self._list(limit=limit).as_node_ids())
        {% endif %}
        {% if data_class.is_cognite_file %}
        self.content = FileContentAPI(client, lambda limit: self._list(limit=limit).as_node_ids())
        {% endif %}{% for field in data_class.timeseries_fields() %}
        {% if field.is_list %}
        self.{{ field.name }} = TimeSeriesReferenceAPI(client,  lambda limit: [
            ts if isinstance(ts, str) else ts.external_id #type: ignore[misc]
            for item in self._list(limit=limit)
            if item.{{ field.name }} is not None
            for ts in item.{{ field.name }}
            if ts is not None and
               (isinstance(ts, str) or ts.external_id is not None)
        ]) {% else %}
        self.{{ field.name }} = TimeSeriesReferenceAPI(client,  lambda limit: [
            item.{{ field.name }} if isinstance(item.{{ field.name }}, str) else item.{{ field.name }}.external_id #type: ignore[misc]
            for item in self._list(limit=limit)
            if item.{{ field.name }} is not None and
               (isinstance(item.{{ field.name }}, str) or item.{{ field.name }}.external_id is not None)
        ]){% endif %}

{% endfor %}

    def list_{{ data_class.variable }}(self, limit: int = DEFAULT_QUERY_LIMIT) -> {{ data_class.read_list_name }}:
        return self._list(limit=limit)


class {{ data_class.query_cls_name }}(_{{ data_class.query_cls_name }}[{{ data_class.read_list_name }}]):
    def __init__(self, client: CogniteClient):
        super().__init__(set(), [], client, {{ data_class.read_list_name }})
