{%  for argument_name, argument in type.arguments.items() -%}
{%    set argument_type = argument.type | camel -%}
{%    if argument.type == 'location' %}
auto {{ c_type_name }}::{{argument_name}}() -> Location {
    clingo_location_t const *ret = nullptr;
    handle_error(clingo_ast_attribute_get_location(ast_, clingo_ast_attribute_{{argument_name}}, &ret));
    return Location{ret};
}
{%    elif argument.type == 'number' %}
auto {{ c_type_name }}::{{ argument_name }}() -> int {
    int ret = 0;
    handle_error(clingo_ast_attribute_get_number(ast_, clingo_ast_attribute_{{ argument_name }}, &ret));
    return ret;
}
{%    elif argument.type == 'bool' %}
auto {{ c_type_name }}::{{ argument_name }}() -> bool {
    int ret = 0;
    handle_error(clingo_ast_attribute_get_number(ast_, clingo_ast_attribute_{{ argument_name }}, &ret));
    return ret != 0;
}
{%    elif argument.type == 'symbol' %}
auto {{ c_type_name }}::{{ argument_name }}() -> Symbol {
    clingo_symbol_t ret = 0;
    handle_error(clingo_ast_attribute_get_symbol(ast_, clingo_ast_attribute_{{ argument_name }}, &ret));
    return Symbol{ret, true};
}
{%    elif argument.type == 'string' %}
auto {{ c_type_name }}::{{ argument_name }}() -> std::string_view {
    clingo_string_t ret;
    handle_error(clingo_ast_attribute_get_string(ast_, clingo_ast_attribute_{{ argument_name }}, &ret));
    return {ret.data, ret.size};
}
{%    elif argument.type == 'string_array' %}
auto {{ c_type_name }}::{{ argument_name }}() -> std::vector<std::string_view> {
    clingo_string_t const *value = nullptr;
    size_t size = 0;
    handle_error(clingo_ast_attribute_get_string_array(ast_, clingo_ast_attribute_{{ argument_name }}, &value, &size));
    std::vector<std::string_view> ret;
    ret.reserve(size);
    for (auto const &x : std::span{value, size}) {
        ret.emplace_back(x.data, x.size);
    }
    return ret;
}
{%    elif argument.type == 'symbol_array' %}
auto {{ c_type_name }}::{{ argument_name }}() -> SymbolArray {
    clingo_symbol_t const *value = nullptr;
    size_t size = 0;
    handle_error(clingo_ast_attribute_get_symbol_array(ast_, clingo_ast_attribute_{{ argument_name }}, &value, &size));
    SymbolArray ret;
    ret.reserve(size);
    for (auto const &x : std::span{value, size}) {
        ret.emplace_back(x, true);
    }
    return ret;
}
{%    elif types[argument.type].type == 'optional' %}
auto {{ c_type_name }}::{{ argument_name }}() -> {{ argument_type }} {
    clingo_ast_t *ast = nullptr;
    handle_error(clingo_ast_attribute_get_ast(ast_, clingo_ast_attribute_{{ argument_name }}, &ast));
    if (ast != nullptr) {
{%-     set value_type = types[argument.type].value_type %}
        // {{ value_type }}
{%-     if types[value_type].type == 'union' %}
        return construct_{{ value_type }}(ast);
{%-     else %}
        return {{ value_type | camel }}::acquire(ast);
{%-     endif %}
    }
    return std::nullopt;
}
{%    elif types[argument.type].type == 'enum' %}
auto {{ c_type_name }}::{{ argument_name }}() -> {{ argument_type }} {
    int ret = 0;
    handle_error(clingo_ast_attribute_get_number(ast_, clingo_ast_attribute_{{ argument_name }}, &ret));
    return static_cast<{{ argument_type }}>(ret);
}
{%    elif types[argument.type].type == 'array' %}
auto {{ c_type_name }}::{{ argument_name }}() -> {{ argument_type }} {
    clingo_ast_t **ast = nullptr;
    size_t size = 0;
    handle_error(clingo_ast_attribute_get_ast_array(ast_, clingo_ast_attribute_{{ argument_name }}, &ast, &size));
    return construct_{{ argument.type }}(ast, size);
}
{%    elif types[argument.type].type == 'union' %}
auto {{ c_type_name }}::{{ argument_name }}() -> {{ argument_type }} {
    clingo_ast_t *ast = nullptr;
    handle_error(clingo_ast_attribute_get_ast(ast_, clingo_ast_attribute_{{ argument_name }}, &ast));
    return construct_{{ argument.type }}(ast);
}
{%    elif types[argument.type].type == 'record' %}
auto {{ c_type_name }}::{{ argument_name }}() -> {{ argument_type }} {
    clingo_ast_t *ast = nullptr;
    handle_error(clingo_ast_attribute_get_ast(ast_, clingo_ast_attribute_{{ argument_name }}, &ast));
    return {{ argument_type }}::acquire(ast);
}
{%    endif %}
{%- endfor %}
auto {{ c_type_name }}::construct(Library &lib{% for argument_name, argument in type.arguments.items() %}, {{ argument.type | cpar }}{{ argument_name }}{% endfor %}) -> {{ c_type_name }} {
    clingo_ast_t *res_ = nullptr;
    handle_error(clingo_ast_construct(lib, clingo_ast_type_{{ type_name }}, &res_{% for argument in type.arguments | c_cast(types) %}, {{ argument }}{% endfor %}));
    return {{ c_type_name }}::acquire(res_);
}

void {{ c_type_name }}::visit([[maybe_unused]] py::handle visitor, [[maybe_unused]] py::args const &args, [[maybe_unused]] py::kwargs const &kwargs) {
{%- for argument_name, argument in type.arguments.items() -%}
{%-   if argument.type in types %}
{%-     if types[argument.type].type in ('union', 'record') %}
    visitor({{ argument_name }}(), *args, **kwargs);
{%-     elif types[argument.type].type == 'optional' %}
    if (auto opt = {{ argument_name }}()) {
        visitor(*opt, *args, **kwargs);
    }
{%-     elif types[argument.type].type == 'array' %}
{%-       set value_type = types[argument.type].value_type %}
    visit_array(ast_, clingo_ast_attribute_{{ argument_name }}, visitor, args, kwargs, {% if types[value_type].type == 'union' -%} construct_{{ value_type }} {%- else -%} {{ value_type | camel }}::acquire {%- endif %});
{%-     endif %}
{%-   endif %}
{%- endfor %}
}

auto {{ c_type_name }}::transform([[maybe_unused]] Library &lib, [[maybe_unused]] py::handle transform, [[maybe_unused]] py::args const &args, [[maybe_unused]] py::kwargs const &kwargs) -> std::optional<{{ c_type_name }}> {
{%- for argument_name, argument in type.arguments.items() -%}
{%-   if argument.type in types %}
{%-     if types[argument.type].type in ('union', 'record') %}
    auto [{{ argument_name }}_value, {{ argument_name }}_changed] = transform_value({{ argument_name }}(), transform, args, kwargs);
{%-     elif types[argument.type].type == 'optional' %}
    auto [{{ argument_name }}_value, {{ argument_name }}_changed] = transform_opt_value({{ argument_name }}(), transform, args, kwargs);
{%-     elif types[argument.type].type == 'array' %}
{%-       set value_type = types[argument.type].value_type %}
    auto [{{ argument_name }}_value, {{ argument_name }}_changed] = transform_array({{ argument_name }}(), transform, args, kwargs);
{%-     endif %}
{%-   endif %}
{%- endfor %}
{%- if type.arguments | transform_cond(types) != "false" %}
    if ({{ type.arguments | transform_cond(types) }}) {
        return {{ c_type_name }}::construct({{ type.arguments | transform_cons(types) }});
    }
{%- endif %}
    return std::nullopt;
}

auto {{ c_type_name }}::update(Library &lib, py::kwargs const &kwargs) -> {{ c_type_name }} {
    return {{ c_type_name }}::construct(lib
{%- for argument_name, argument in type.arguments.items() %}
        , update_value<{{ argument.type | cval }}>(this, &{{ c_type_name }}::{{ argument_name }}, kwargs, "{{ argument_name }}")
{%- endfor %});
}
