#pragma once

#include "{{ pair_header }}_fwd.hpp"

{% for file in declaration_includes(types.values()) %}
    #include <{{ file }}>
{%- endfor %}
{% for file in external_includes %}
    #include <{{ file }}>
{%- endfor %}

#include <userver/chaotic/type_bundle_hpp.hpp>

{% macro generate_type(name, type) %}
    {% if type.get_py_type() == 'CppStruct' %}
        {{ type.cpp_comment() }}
        struct {{ type.cpp_local_name() }} {
            {#- handle subtypes -#}
            {%- for fname, field in type.fields.items() -%}
                    {{- generate_type(fname,
                                     field.schema) -}}
            {%- endfor -%}

            {% if type.extra_type not in [True, False, None] %}
                {% if not type._is_default_dict() %}
                    {{- generate_type('Extra', type.extra_type) -}}
                {% endif %}
            {% endif %}

            {# properties #}
            {%- for fname, field in type.fields.items() -%}
                {{ field.cpp_field_type() }} {{ field.cpp_field_name() }}
                    { {{ field.get_default() }} };
            {%- endfor %}

            {% if type.extra_type %}
                {{ extra_cpp_type(type) }} extra;
            {% endif %}
        };
    {% elif type.get_py_type() == 'CppPrimitiveType' %}
        {{ generate_validators_data_declaration(name, type) }}
    {% elif type.get_py_type() == 'CppRef' %}
        {# nothing #}
    {% elif type.get_py_type() == 'CppIntEnum' %}
        enum class {{ type.cpp_local_name() }} {
        {%- for item in type.enums %}
            k{{ item }} = {{ item }},
        {%- endfor -%}
        };

        static constexpr {{ type.cpp_local_name() }} k{{ type.cpp_local_name() }}Values [] = {
        {%- for item in type.enums %}
            {{ type.cpp_local_name() }}::k{{ item }},
        {%- endfor -%}
        };
    {% elif type.get_py_type() == 'CppStringEnum' %}
        enum class {{ type.cpp_local_name() }} {
        {%- for item in type.enums %}
            {{ item.cpp_name }},
        {%- endfor -%}
        };

        static constexpr {{ type.cpp_local_name() }} k{{ type.cpp_local_name() }}Values [] = {
        {%- for item in type.enums %}
            {{ type.cpp_local_name() }}::{{ item.cpp_name }},
        {%- endfor -%}
        };
    {% elif type.get_py_type() == 'CppArray' %}
        {# handle subtype #}
        {{ generate_type(type.items.cpp_local_name(), type.items) }}
    {% elif type.get_py_type() == 'CppStructAllOf' %}
        {# handle subtypes #}
        {%- for parent in type.parents -%}
            {{ generate_type(parent.cpp_local_name(), parent) }}
        {% endfor %}

        struct {{ type.cpp_local_name() }} :
            {%- for parent in type.parents -%}
                {# TODO: x-usrv-cpp-type #}
                public {{ parent.cpp_global_name() }}
                {%- if not loop.last %} , {% endif -%}
            {%- endfor -%}
        {
            {{ type.cpp_local_name() }}() = default;

            {{ type.cpp_local_name() }}(
                {%- for num, parent in enumerate(type.parents) -%}
                    {{ parent.cpp_global_name() }}&& a{{ num }}
                    {%- if not loop.last %} , {% endif -%}
                {%- endfor -%}
                ) :
                    {%- for num, parent in enumerate(type.parents) -%}
                        {{ parent.cpp_global_name() }}(
                            std::move(a{{ num }})
                        )
                        {%- if not loop.last %} , {% endif -%}
                    {%- endfor -%}
                {}
        };
    {% elif type.get_py_type() == 'CppVariant' %}
        {# handle subtypes #}
        {%- for variant in type.variants -%}
            {{ generate_type(variant.cpp_local_name(), variant) }}
        {% endfor %}

        {# oneOf #}
        using {{ type.cpp_local_name() }} = std::variant<
            {%- for variant in type.variants -%}
                {{ variant.cpp_user_name() }}
                {%- if not loop.last %} , {% endif -%}
            {%- endfor -%}
        >;
    {% elif type.get_py_type() == 'CppVariantWithDiscriminator' %}
        [[maybe_unused]] static constexpr {{ userver }}::chaotic::OneOfSettings k{{ type.cpp_local_name() }}_Settings = {
            "{{ type.property_name }}",
            {{ userver }}::utils::TrivialSet(
                [](auto selector) {
		    return selector().template Type<std::string_view>()
                        {%- for var_name in type.variants -%}
                            .Case("{{ var_name }}")
                        {%- endfor -%}
                        ;
                }
            )
        };

        using {{ type.cpp_local_name() }} = std::variant<
            {%- for variant in type.variants.values() -%}
                {{ variant.cpp_global_name() }}
                {%- if not loop.last %} , {% endif -%}
            {%- endfor -%}
        >;
    {% else %}
        // {{ type.get_py_type() }}
    {% endif %}
{% endmacro %}

{% macro generate_validators_data_declaration(name, type) %}
    {% if type.validators.min != None %}
        static constexpr auto k{{ type.validators.prefix }}Minimum =
            {{ type.validators.min }};
    {% endif %}
    {% if type.validators.max != None %}
        static constexpr auto k{{ type.validators.prefix }}Maximum =
            {{ type.validators.max }};
    {% endif %}
    {% if type.validators.exclusiveMin != None %}
        static constexpr auto k{{ type.validators.prefix }}ExclusiveMinimum =
            {{ type.validators.exclusiveMin }};
    {% endif %}
    {% if type.validators.exclusiveMax != None %}
        static constexpr auto k{{ type.validators.prefix }}ExclusiveMaximum =
            {{ type.validators.exclusiveMax }};
    {% endif %}
    {% if type.validators.pattern %}
        static constexpr std::string_view k{{ type.validators.prefix }}Pattern =
            R"--({{ type.validators.pattern }})--";
    {% endif %}
{% endmacro %}


{% macro generate_operator_eq_declaration(name, type) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_operator_eq_declaration(
               schema.cpp_global_name(),
               schema,
           )
        }}
    {% endfor %}

    {% if type.need_operator_eq() %}
        bool operator==(const {{ name }} & lhs,const {{ name }} & rhs);
    {% endif %}
{% endmacro %}

{% macro generate_operator_lshift_declaration(name, type) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_operator_lshift_declaration(
               schema.cpp_global_name(),
               schema,
           )
        }}
    {% endfor %}

    {% if type.need_operator_lshift() %}
        {% if type.get_py_type() == 'CppStringEnum' or generate_serializer %}
            {{ userver }}::logging::LogHelper& operator<<({{ userver }}::logging::LogHelper& lh, const {{ name }}& value);
        {% endif %}
    {% endif %}
{% endmacro %}

{% macro generate_parser_declaration(name, type, format) %}
    {% if not type.may_generate_for_format(format) %}
        /* Parse({{ format }}::Value, To<{{ name }}>) was not generated: {{ type.only_json_reason() }} */
    {% else %}
        {{ _generate_parser_declaration(name, type, format) }}
    {% endif %}
{% endmacro %}

{% macro _generate_parser_declaration(name, type, format) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_parser_declaration(
                schema.cpp_global_name(),
                schema,
                format
           )
        }}
    {% endfor %}

    {% if type.need_dom_parser() %}
        {{ type.raw_cpp_type.in_scope(get_current_namespace()) }} Parse({{ format }}::Value json,
                         {{ userver }}::formats::parse::To<{{ name }}>);
    {% endif %}
{% endmacro %}

{% macro generate_string_parser_declaration(name, type) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_string_parser_declaration(
                schema.cpp_global_name(),
                schema
           )
        }}
    {% endfor %}

    {% if type.need_dom_parser() %}
        {% if type.get_py_type() == 'CppStringEnum' %}
            {{ type.raw_cpp_type.in_scope(get_current_namespace()) }} FromString(std::string_view value,
                            {{ userver }}::formats::parse::To<{{ name }}>);

            {# backward compatibility #}
            {{ type.raw_cpp_type.in_scope(get_current_namespace()) }} Parse(std::string_view value,
                            {{ userver }}::formats::parse::To<{{ name }}>);
        {% endif %}
    {% endif %}
{% endmacro %}

{% macro generate_serializer_declaration(name, type) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_serializer_declaration(
                schema.cpp_global_name(),
                schema
           )
        }}
    {% endfor %}

    {% if type.need_serializer() %}
        {{ userver }}::formats::json::Value Serialize(
            const {{ name }}& value,
            {{ userver }}::formats::serialize::To<{{ userver }}::formats::json::Value>
        );
    {% endif %}
{% endmacro %}

{% macro generate_tostring_declaration(name, type) %}
    {# handle subtypes #}
    {%- for schema in type.subtypes() -%}
        {{ generate_tostring_declaration(
                schema.cpp_global_name(),
                schema
           )
        }}
    {% endfor %}

    {% if type.get_py_type() == 'CppStringEnum' %}
        std::string ToString({{ name }} value);
    {% endif %}
{% endmacro %}

{% import 'templates/common.jinja' as common %}

{% for name, type in types.items() %}
    {{ common.switch_namespace(cpp_namespace(name)) }}

    {{ generate_type(name, type) }}

    {% if type.need_using_type() %}
        {# generate using because the user explicitly set custom name for the type #}
        using {{ cpp_type(name) }} = {{ type.cpp_global_name() }};
    {% endif %}

    {{ generate_operator_eq_declaration(name, type) }}

    {{ generate_operator_lshift_declaration(name, type) }}

    {% for parse_format in parse_formats %}
        {{ generate_parser_declaration(name, type, userver + parse_format) }}
    {% endfor %}

    {{ generate_string_parser_declaration(name, type) }}

    {% if generate_serializer %}
        {{ generate_serializer_declaration(name, type) }}
    {% endif %}

    {{ generate_tostring_declaration(name, type) }}
{% endfor %}

{{ common.switch_namespace('') }}
