// -*- mode: kotlin -*-

/*
 * AUTOGENERATED BY glean_parser v{{ parser_version }}. DO NOT EDIT. DO NOT COMMIT.
 */
{# The rendered markdown is autogenerated, but this
Jinja2 template is not. Please file bugs! #}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
{%- macro obj_declaration(obj, suffix='', access='', lazy=False) -%}
{% if (access != "private ") -%}
@get:JvmName("{{ obj.name|camelize }}{{ suffix }}")
{% endif -%}
{{ access }}val {{ obj.name|camelize }}{{ suffix }}: {{ obj|type_name }}{% if lazy %} by lazy { {%- else %} ={% endif %} // generated from {{ obj.identifier() }}
{% if obj.type == 'ping' %}
    {{ obj|type_name }}(
        {% for arg_name in ping_args if obj[arg_name] is defined %}
        {{ arg_name|camelize }} = {{ obj[arg_name]|kotlin }}{{ "," if not loop.last }}
        {% endfor %}
    )
{% else %}
    {{ obj|type_name }}(
        CommonMetricData(
            {% for arg_name in common_metric_args if obj[arg_name] is defined %}
            {{ arg_name|camelize }} = {{ obj[arg_name]|kotlin }}{{ "," if not loop.last }}
            {% endfor %}
        ){%- for arg_name in extra_metric_args if obj[arg_name] is defined -%}
        , {{ arg_name|camelize }} = {{ obj[arg_name]|kotlin }}
        {%- endfor -%}
    )
{% endif %}
{% if lazy %}}{% endif %}
{%- endmacro -%}

{%- macro reason_enum_decl(obj, name, suffix) -%}
@Suppress("ClassNaming", "EnumNaming")
enum class {{ obj.name|camelize }}{{ suffix }} : ReasonCode {
{% for key in obj|attr(name) %}
    {{ key|camelize }} {
        override fun code(): Int = {{ loop.index-1 }}
    }{{ "," if not loop.last }}{{ ";" if loop.last }}

{% endfor %}
}
{%- endmacro %}

{%- macro struct_decl(obj, name, suffix) -%}
@Suppress("ClassNaming", "EnumNaming")
data class {{ obj.name|Camelize }}{{ suffix }}(
{% for item, typ in obj|attr(name) %}
    val {{ item|camelize }}: {{typ|extra_type_name}}? = null{{ "," if not loop.last }}
{% endfor %}
) : EventExtras {
    override fun toExtraRecord(): Map<String, String> {
        val map = mutableMapOf<String, String>()

        {% for item, typ in obj|attr(name) %}
        this.{{ item|camelize }}?.let {
            {% if typ != "string" %}
            map.put("{{item}}", it.toString())
            {% else %}
            map.put("{{item}}", it)
            {% endif %}
        }
        {% endfor %}
        return map
    }
}
{%- endmacro -%}

{%- macro generate_structure(name, struct) %}
{%- if struct.type == "oneof" -%}
    @Serializable(with = {{name}}.Serializer::class)
    sealed class {{ name }}(): ObjectSerialize {
        internal object Serializer : KSerializer<{{name}}> {
            @OptIn(kotlinx.serialization.InternalSerializationApi::class)
            override val descriptor = buildSerialDescriptor("inner", SerialKind.CONTEXTUAL)

            override fun deserialize(decoder: Decoder): {{name}} {
                throw NullPointerException()
                {{ struct.subtypes[0]|Camelize }}()
            }

            override fun serialize(encoder: Encoder, value: {{name}}) {
                when (value) {
                {% for ty in struct.subtypes %}
                    is {{ty|Camelize}} -> {{ty|Camelize}}.Serializer.serialize(encoder, value)
                {% endfor %}
                }
            }
        }

    {% for ty in struct.subtypes %}
        {% set inner_ty = ty|Camelize %}
        @Serializable(with = {{inner_ty}}.Serializer::class)
        class {{inner_ty}}(val inner: kotlin.{{ty|structure_type_name}}? = null) : {{ name }}() {
            override fun intoSerializedObject(): kotlin.String {
                return this.inner?.intoSerializedObject() ?: "null"
            }

            internal object Serializer : KSerializer<{{inner_ty}}> {
                @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
                {% if ty == "string" %}
                override val descriptor = kotlin.String.Companion.serializer().descriptor

                override fun deserialize(decoder: Decoder): {{inner_ty}} {
                    val inner = decoder.decodeInline(descriptor).decodeString()
                    return {{inner_ty}}(inner = inner)
                }

                override fun serialize(encoder: Encoder, value: {{inner_ty}}) {
                    value.inner?.let {
                        encoder.encodeInline(descriptor).encodeString(it)
                    }
                }
                {% elif ty == "number" %}
                override val descriptor = kotlin.Int.Companion.serializer().descriptor

                override fun deserialize(decoder: Decoder): {{inner_ty}} {
                    val inner = decoder.decodeInline(descriptor).decodeInt()
                    return {{inner_ty}}(inner = inner)
                }

                override fun serialize(encoder: Encoder, value: {{inner_ty}}) {
                    value.inner?.let {
                        encoder.encodeInline(descriptor).encodeInt(it)
                    }
                }
                {% elif ty == "boolean" %}
                override val descriptor = kotlin.Boolean.Companion.serializer().descriptor

                override fun deserialize(decoder: Decoder): {{inner_ty}} {
                    val inner = decoder.decodeInline(descriptor).decodeBoolean()
                    return {{inner_ty}}(inner = inner)
                }

                override fun serialize(encoder: Encoder, value: {{inner_ty}}) {
                    value.inner?.let {
                        encoder.encodeInline(descriptor).encodeBoolean(it)
                    }
                }
                {% endif %}
            }
        }
    {% endfor %}
    }
{%- elif struct.type == "array" -%}
    @Serializable(with = {{name}}.Serializer::class)
    data class {{ name }}(var items: MutableList<{{ name }}Item> = mutableListOf()) : ObjectSerialize {
        fun add(elem: {{ name }}Item) = items.add(elem)

        fun addAll(elements: Collection<{{ name }}Item>) = items.addAll(elements)

        val size get() = items.size

        fun clear() = items.clear()

        fun remove(element: {{ name }}Item) = items.remove(element)
        fun removeAll(elements: Collection<{{ name }}Item>) = items.removeAll(elements)
        fun removeAt(index: Int) = items.removeAt(index)

        fun set(index: Int, element: {{ name }}Item) = items.set(index, element)

        override fun intoSerializedObject(): String {
            var json = buildString {
                append("[")
                var first = true
                for (item in items) {
                    if (!first) {
                        append(",")
                    }
                    first = false
                    append(item.intoSerializedObject())
                }
                append("]")
            }
            return json
        }

        internal object Serializer : KSerializer<{{name}}> {
            @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
            override val descriptor = listSerialDescriptor<String>()

            override fun deserialize(decoder: Decoder): {{ name }} {
                val list = decoder.decodeSerializableValue(ListSerializer(serializer<{{name}}Item>()))
                    return {{name}}(items = list.toMutableList())
            }

            override fun serialize(encoder: Encoder, value: {{name}}) {
                encoder.encodeSerializableValue(ListSerializer(serializer<{{name}}Item>()), value.items)
            }
        }
    }

    {{ generate_structure(name ~ "Item", struct["items"]) }}

{%- elif struct.type == "object" -%}
    @Serializable
    data class {{ name }}(
        {% for itemname, val in struct.properties.items() %}
          {% if val.type == "array" %}
          var {{itemname|camelize}}: {{ name ~ itemname|Camelize }} = {{ name ~ itemname|Camelize }}(),
          {% elif val.type == "object" %}
          var {{itemname|camelize}}: {{ name ~ "Item" ~ itemname|Camelize ~ "Object" }}? = null,
          {% elif val.type == "oneof" %}
          var {{itemname|camelize}}: {{ name ~ itemname|Camelize ~ "Enum" }}? = null,
          {% else %}
          var {{itemname|camelize}}: {{val.type|structure_type_name}}? = null,
          {% endif %}
        {% endfor %}
    ): ObjectSerialize {
        override fun intoSerializedObject(): String {
            var __obj: MutableList<String> = mutableListOf()
            {% for itemname, val in struct.properties.items() %}
            {% if val.type == "array" %}
            if (this.{{itemname|camelize}}.size > 0) {
                val {{itemname|camelize}} = this.{{itemname|camelize}}
            {% else %}
            this.{{itemname|camelize}}?.let { {{itemname|camelize}} ->
            {% endif %}
                val __elem = buildString {
                    append("\"{{itemname}}\":")
                    append({{itemname|camelize}}.intoSerializedObject())
                }
                __obj.add(__elem)
            }
            {% endfor %}
            return buildString {
                append("{")
                append(__obj.joinToString(","))
                append("}")
            }
        }
    }

    {% for itemname, val in struct.properties.items() %}
        {% if val.type == "array" %}
        {% set nested_name = name ~ itemname|Camelize %}
        {{ generate_structure(nested_name, val) }}
        {% elif val.type == "object" %}
        {% set nested_name = name ~ "Item" ~ itemname|Camelize ~ "Object" %}
        {{ generate_structure(nested_name, val) }}
        {% elif val.type == "oneof" %}
        {% set nested_name = name ~ itemname|Camelize ~ "Enum" %}
        {{ generate_structure(nested_name, val) }}
        {% endif %}
    {% endfor %}

{% endif %}

{% endmacro %}

{%- macro generate_structure_typealias(name, struct) %}
{%- if struct.type == "array" -%}
    {{ generate_structure_typealias(name ~ "Item", struct["items"]) }}
{%- elif struct.type == "object" -%}
    {% for itemname, val in struct.properties.items() %}
        {% if val.type == "array" %}
        {% set nested_name = name ~ itemname|Camelize %}
        {{ generate_structure_typealias(nested_name, val) }}
        {% elif val.type == "object" %}
        {% set nested_name = name ~ "Item" ~ itemname|Camelize ~ "Object" %}
        {{ generate_structure_typealias(nested_name, val) }}
        {% endif %}
    {% endfor %}
{%- else -%}
    typealias {{ name }} = {{ struct.type|structure_type_name }}
{% endif %}
{% endmacro %}

/* ktlint-disable no-blank-line-before-rbrace */
@file:Suppress("PackageNaming", "MaxLineLength")
package {{ namespace }}

import {{ glean_namespace }}.private.CommonMetricData // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.EventExtras // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.HistogramType // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.Lifetime // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.MemoryUnit // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.NoExtras // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.NoReasonCodes // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.ObjectSerialize // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.ReasonCode // ktlint-disable import-ordering no-unused-imports
import {{ glean_namespace }}.private.TimeUnit // ktlint-disable import-ordering no-unused-imports
{% for obj_type in obj_types %}
import {{ glean_namespace }}.private.{{ obj_type }} // ktlint-disable import-ordering
{% endfor %}
{% if has_labeled_metrics %}
import {{ glean_namespace }}.private.LabeledMetricType // ktlint-disable import-ordering
{% endif %}
{% if has_object_metrics %}
import {{ glean_namespace }}.private.intoSerializedObject
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.buildSerialDescriptor
import kotlinx.serialization.descriptors.listSerialDescriptor
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
{% endif %}

{# HACK HACK HACK -- typealiases MUST BE top-level #}
{% for obj in objs.values() %}
{% if obj|attr("_generate_structure") %}
{{ generate_structure_typealias(obj.name|Camelize ~ "Object", obj._generate_structure) }}
{%- endif %}
{% endfor %}
{# HACK end #}
internal object {{ category_name|Camelize }} {
{% for obj in objs.values() %}
    {% if obj.type == "ping" %}
    {% if obj|attr("_generate_enums") %}
    {% for name, suffix in obj["_generate_enums"] %}
    {% if obj|attr(name)|length %}
    {{ reason_enum_decl(obj, name, suffix)|indent }}
    {% endif %}
    {% endfor %}
    {% endif %}
    {% else %}
    {% if obj|attr("_generate_structure") %}
    {{ generate_structure(obj.name|Camelize ~ "Object", obj._generate_structure) }}
    {%- endif %}
    {% if obj|attr("_generate_enums") %}
    {% for name, suffix in obj["_generate_enums"] %}
    {% if obj|attr(name)|length %}
    {{ struct_decl(obj, name, suffix)|indent }}
    {% endif %}
    {% endfor %}
    {% endif %}
    {% endif %}
{% endfor %}
{% for obj in objs.values() %}
    {% if obj.labeled %}
    {{ obj_declaration(obj, 'Label', 'private ') | indent }}
    /**
     * {{ obj.description|wordwrap() | replace('\n', '\n        * ') }}
     */
    val {{ obj.name|camelize }}: LabeledMetricType<{{ obj|type_name }}> by lazy { // generated from {{ obj.identifier() }}
        LabeledMetricType(
            category = {{ obj.category|kotlin }},
            name = {{ obj.name|kotlin }},
            subMetric = {{ obj.name|camelize }}Label,
            disabled = {{ obj.is_disabled()|kotlin }},
            lifetime = {{ obj.lifetime|kotlin }},
            sendInPings = {{ obj.send_in_pings|kotlin }},
            labels = {{ obj.labels|kotlin }}
        )
    }
    {% else %}
    /**
     * {{ obj.description|wordwrap() | replace('\n', '\n     * ') }}
     */
    {{ obj_declaration(obj, lazy=obj.type != 'ping') | indent }}
    {% endif %}
{%- endfor %}
}
