{% for _, enum in output_file.enums|dictsort(by="key") %}
class {{ enum.py_name | add_to_all }}(betterproto2.Enum):
    {% if enum.comment %}
    """
    {{ enum.comment | indent(4) }}
    """
    {% endif %}

    {% if output_file.settings.google_protobuf_descriptors %}
    {# Add descriptor class property to be more drop-in compatible with other libraries. #}
    @betterproto2.staticproperty
    @staticmethod
    def DESCRIPTOR() -> EnumDescriptor:
        return {{ enum.descriptor_name }}.enum_types_by_name['{{ enum.prefixed_proto_name }}']
    {% endif %}

    {% for entry in enum.entries %}
    {{ entry.name }} = {{ entry.value }}
    {% if entry.comment %}
    """
    {{ entry.comment | indent(4) }}
    """
    {% endif %}

    {% endfor %}

    {% if enum.has_renamed_entries %}
    @classmethod
    def betterproto_value_to_renamed_proto_names(cls) -> dict[int, str]:
        return {
            {% for entry in enum.entries %}
            {% if entry.proto_name != entry.name %}
            {{ entry.value }}: "{{ entry.proto_name }}",
            {% endif %}
            {% endfor %}
        }

    @classmethod
    def betterproto_renamed_proto_names_to_value(cls) -> dict[str, int]:
        return {
            {% for entry in enum.entries %}
            {% if entry.proto_name != entry.name %}
            "{{ entry.proto_name }}": {{ entry.value }},
            {% endif %}
            {% endfor %}
        }
    {% endif %}

{% endfor %}
{% for _, message in output_file.messages|dictsort(by="key") %}
{% if output_file.settings.pydantic_dataclasses %}
@dataclass(eq=False, repr=False, config={"extra": "forbid"})
{% else %}
@dataclass(eq=False, repr=False)
{% endif %}
class {{ message.py_name | add_to_all }}(betterproto2.Message):
    {% if message.comment or message.oneofs %}
    """
    {{ message.comment | indent(4) }}
    {% if message.oneofs %}

    Oneofs:
        {% for oneof in message.oneofs %}
        - {{ oneof.name }}: {{ oneof.comment | indent(12) }}
        {% endfor %}
    {% endif %}
    """
    {% endif %}

    {% if output_file.settings.google_protobuf_descriptors %}
    {# Add descriptor class property to be more drop-in compatible with other libraries. #}
    @betterproto2.staticproperty
    @staticmethod
    def DESCRIPTOR() -> Descriptor:
        return {{ message.descriptor_name }}.message_types_by_name['{{ message.prefixed_proto_name }}']
    {% endif %}

    {% for field in message.fields %}
    {{ field.get_field_string() }}
    {% if field.comment %}
    """
    {{ field.comment | indent(4) }}
    """
    {% endif %}

    {% endfor %}

    {% if not message.fields %}
    pass
    {% endif %}

    {% if message.deprecated or message.has_deprecated_fields %}
    def __post_init__(self) -> None:
        {% if message.deprecated %}
        warnings.warn("{{ message.py_name }} is deprecated", DeprecationWarning)
        {% endif %}
        super().__post_init__()
        {% for field in message.deprecated_fields %}
        if self.is_set("{{ field }}"):
            warnings.warn("{{ message.py_name }}.{{ field }} is deprecated", DeprecationWarning)
        {% endfor %}
    {%  endif %}

    {% if output_file.settings.pydantic_dataclasses and message.has_oneof_fields %}
    @model_validator(mode='after')
    def check_oneof(cls, values):
        return cls._validate_field_groups(values)

    {%  endif %}
    {% for method_source in message.custom_methods %}
    {{ method_source }}
    {% endfor %}

default_message_pool.register_message("{{ output_file.package }}", "{{ message.prefixed_proto_name }}", {{ message.py_name }})


{% endfor %}

{% for _, service in output_file.services|dictsort(by="key") %}

{% if output_file.settings.client_generation.is_sync_generated %}
{% include "service_stub_sync.py.j2" %}
{% endif %}

{% if output_file.settings.client_generation.is_async_generated %}
{% include "service_stub_async.py.j2" %}
{% endif %}

{% endfor %}

{% for i in output_file.imports_end %}
{{ i }}
{% endfor %}

{% if output_file.settings.google_protobuf_descriptors %}
{# Add descriptors to Google protobuf's default pool to be more drop-in compatible with other libraries. #}
{{ output_file.descriptors }}
{% endif %}

{% if output_file.settings.server_generation == "async" %}
{% for _, service in output_file.services|dictsort(by="key") %}
class {{ (service.py_name + "Base") | add_to_all }}(betterproto2_grpclib.ServiceBase):
    {% if service.comment %}
    """
    {{ service.comment | indent(4) }}
    """
    {% endif %}

    {% for method in service.methods %}
    async def {{ method.py_name }}(self
        {%- if not method.client_streaming -%}
            , message: "{{ method.py_input_message_type }}"
        {%- else -%}
            {# Client streaming: need a request iterator instead #}
            , messages: "AsyncIterator[{{ method.py_input_message_type }}]"
        {%- endif -%}
            ) -> {% if method.server_streaming %}"AsyncIterator[{{ method.py_output_message_type }}]"{% else %}"{{ method.py_output_message_type }}"{% endif %}:
        {% if method.comment %}
        """
        {{ method.comment | indent(8) }}
        """
        {% endif %}

        raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
        {% if method.server_streaming %}
        {# yielding here changes the return type from a coroutine to an async_generator #}
        yield {{ method.py_output_message_type }}()
        {% endif %}

    {% endfor %}

    {% for method in service.methods %}
    async def __rpc_{{ method.py_name }}(self, stream: "grpclib.server.Stream[{{ method.py_input_message_type }}, {{ method.py_output_message_type }}]") -> None:
        {% if not method.client_streaming %}
        request = await stream.recv_message()
        assert request is not None
        {% else %}
        request = stream.__aiter__()
        {% endif %}
        {% if not method.server_streaming %}
        response = await self.{{ method.py_name }}(request)
        await stream.send_message(response)
        {% else %}
        await self._call_rpc_handler_server_stream(
            self.{{ method.py_name }},
            stream,
            request,
        )
        {% endif %}

    {% endfor %}

    def __mapping__(self) -> "dict[str, grpclib.const.Handler]":
        return {
        {% for method in service.methods %}
        "{{ method.route }}": grpclib.const.Handler(
            self.__rpc_{{ method.py_name }},
            {% if not method.client_streaming and not method.server_streaming %}
            grpclib.const.Cardinality.UNARY_UNARY,
            {% elif not method.client_streaming and method.server_streaming %}
            grpclib.const.Cardinality.UNARY_STREAM,
            {% elif method.client_streaming and not method.server_streaming %}
            grpclib.const.Cardinality.STREAM_UNARY,
            {% else %}
            grpclib.const.Cardinality.STREAM_STREAM,
            {% endif %}
            {{ method.py_input_message_type }},
            {{ method.py_output_message_type }},
        ),
        {% endfor %}
        }

{% endfor %}
{% endif %}
