{% from 'templates/macros.tmpl' import print_if %}

{% macro encode(field, value) %}
{% if field.is_bit_field -%}
static_cast<unsigned>({{value}})
{%- else -%}
{{value}}
{%- endif %}
{% endmacro %}

{% macro decode(field, value) %}
{% if field.is_bit_field -%}
static_cast<{{field.type_name}}>({{value}})
{%- else -%}
{{value}}
{%- endif %}
{% endmacro %}

{% macro getter_method_name(field) -%}
{% if 'getter' not in field.computed_style_custom_functions -%}
{{field.getter_method_name}}
{%- else -%}
{{field.internal_getter_method_name}}
{%- endif %}
{%- endmacro %}

{% macro setter_method_name(field) -%}
{% if 'setter' not in field.computed_style_custom_functions -%}
{{field.setter_method_name}}
{%- else -%}
{{field.internal_setter_method_name}}
{%- endif %}
{%- endmacro %}

{% macro resetter_method_name(field) -%}
{% if 'resetter' not in field.computed_style_custom_functions -%}
{{field.resetter_method_name}}
{%- else -%}
{{field.internal_resetter_method_name}}
{%- endif %}
{%- endmacro %}

{% macro mutable_method_name(field) -%}
{{field.internal_mutable_method_name}}
{%- endmacro %}

{% macro bitfield_prefix() -%}
  data_.
{%- endmacro %}

{% macro accessor_expression_prefix(field) -%}
{#- Top-level non-tracable fields reside in the `data_` struct. -#}
{#- Derived flags should not be prefixed here, because they must always be read
    via a function. -#}
{%- if not field.group.parent and not field.requires_tracing and not field.derived_from -%}
  {{bitfield_prefix()}}
{%- endif -%}
{%- endmacro %}

{% macro getter_expression_without_prefix(field) %}
{% for group in field.group.path_without_root() -%}
{{group.member_name}}->
{%- endfor -%}
{# Derived flags compute their value lazily, so we can not access the field directly. #}
{% if field.derived_from -%}
{{getter_method_name(field)}}()
{%- else %}
{{field.name}}
{%- endif %}
{%- endmacro %}

{% macro access_flag(group) -%}
  access_.{{group.member_name}}
{%- endmacro %}

{#
  Generates nested calls to Access in preparation of modifying a field.

  Example: accessing the group of the 'baseline_shift_' field:

    Access(Access(svg_data_, access_.svg_data_)->misc_data_, access_.misc_data_)->baseline_shift_
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#}
{% macro nested_group_access(group) %}
{% if group.parent.name -%}
Access({{nested_group_access(group.parent)}}->{{group.member_name}}, {{access_flag(group)}})
{%- else -%}
Access({{group.member_name}}, {{access_flag(group)}})
{%- endif -%}
{%- endmacro %}

{% macro setter_expression_without_prefix(field) %}
{% if field.group.name -%}
{{nested_group_access(field.group)}}->
{%- endif -%}
{{field.name}}
{%- endmacro %}

{% macro getter_expression(field) %}
{{accessor_expression_prefix(field)}}{{getter_expression_without_prefix(field)}}
{%- endmacro %}

{% macro setter_expression(field) %}
{{accessor_expression_prefix(field)}}{{setter_expression_without_prefix(field)}}
{%- endmacro %}

{% macro set_if_changed(field, value) %}
{% if field.group.name -%}
if (!({{getter_expression(field)}} == {{value}}))
  {{setter_expression(field)}} = {{value}};
{%- else -%}
{{accessor_expression_prefix(field)}}{{field.name}} = {{value}};
{%- endif %}
{% endmacro %}

{% macro move_if_changed(field, value) %}
{% if field.group.name -%}
if (!({{getter_expression(field)}} == {{value}}))
  {{setter_expression(field)}} = std::move({{value}});
{%- else -%}
{{accessor_expression_prefix(field)}}{{field.name}} = std::move({{value}});
{%- endif %}
{% endmacro %}

{% macro nonconst_ref(field) %}
{% if field.is_bit_field or field.field_template == 'primitive' -%}
{{field.type_name}}
{%- else -%}
{{field.type_name}}&
{%- endif %}
{% endmacro %}

{% macro const_ref(field) %}
{% if field.is_bit_field or field.field_template == 'primitive' -%}
{{field.type_name}}
{%- else -%}
const {{field.type_name}}&
{%- endif %}
{% endmacro %}

{% macro rvalue_ref(field) -%}
{{field.type_name}}&&
{%- endmacro %}

{% macro group_getter_expression(group) %}
{% for group in group.path_without_root() -%}
{{group.member_name}}{{print_if(not loop.last, "->")}}
{%- endfor -%}
{% endmacro %}

{% macro declare_storage(field) %}
{% if field.is_bit_field %}
{{print_if(field.mutable, "mutable ")}}unsigned {{field.name}} : {{field.size}}; // {{field.type_name}}
{%- elif field.field_template == 'pointer' %}
{{field.wrapper_pointer_name}}<{{field.type_name}}> {{field.name}};
{%- else %}
{{field.type_name}} {{field.name}};
{%- endif %}
{% endmacro %}

{% macro compare(wrapper_pointer_name, expr, other_name) %}
{% if wrapper_pointer_name -%}
  base::ValuesEquivalent({{expr}}, {{other_name}}.{{expr}})
{%- else -%}
  {{expr}} == {{other_name}}.{{expr}}
{%- endif %}
{% endmacro %}

{% macro compare_expr(wrapper_pointer_name, expr, other_expr) %}
{% if wrapper_pointer_name -%}
  base::ValuesEquivalent({{expr}}, {{other_expr}})
{%- else -%}
  {{expr}} == {{other_expr}}
{%- endif %}
{% endmacro %}

{# Given a group and a list of fields to compare, this generates a set of
   equality comparisons on those fields. The generated comparisons take
   advantage of group sharing. #}
{% macro fieldwise_compare(group, fields_to_compare, boolean_and=None) %}
{% set boolean_and = boolean_and or joiner('&& ') %}
{% for subgroup in group.subgroups %}
  {# If every field in this subgroup is to be compared, we can compare the
     group pointer instead. #}
  {% set group_expression = group_getter_expression(subgroup) %}
  {% if subgroup.all_fields|rejectattr("custom_compare")|reject("in", fields_to_compare)|list|length == 0 -%}
    {{boolean_and()}}base::ValuesEquivalent({{group_expression}}, o.{{group_expression}})
  {# Otherwise, we would have to recursively generate comparison operations
     on fields in the subgroup. If the objects are the same, we can skip the
     per-field comparisons. #}
  {% elif subgroup.all_fields|rejectattr("custom_compare")|select("in", fields_to_compare)|list|length > 0 -%}
    {{boolean_and()}}({{group_expression}}.Get() == o.{{group_expression}}.Get()
|| ({{fieldwise_compare(subgroup, fields_to_compare)}}))
  {% endif %}
{% endfor %}
{% for field in group.fields|rejectattr("custom_compare")|select("in", fields_to_compare) -%}
  {{boolean_and()}}{{compare(field.wrapper_pointer_name, getter_expression(field), "o")}}
{% endfor %}
{% endmacro %}

{% macro fieldwise_debug_diff(group, fields_to_compare) %}
{% for subgroup in group.subgroups %}
  {{fieldwise_debug_diff(subgroup, fields_to_compare)}}
{% endfor %}
// Group: {{group.name}}
{% for field in group.fields|rejectattr("custom_compare")|select("in", fields_to_compare) -%}
const auto& {{field.name}}_expr = {{getter_expression(field)}};
const auto& o_{{field.name}}_expr = o.{{getter_expression(field)}};
if (!({{compare_expr(field.wrapper_pointer_name, field.name + "_expr", "o_" + field.name + "_expr")}})) {
  DebugDiff d;
  d.field = DebugField::{{field.name}};
  d.actual = DebugStringForField({{field.name}}_expr);
  d.correct = DebugStringForField(o_{{field.name}}_expr);
  diff.push_back(std::move(d));
}
{% endfor %}
{% endmacro %}

{% macro reset_group_access(group) -%}
  {{access_flag(group)}} = false;
  {% for subgroup in group.subgroups -%}
    {{reset_group_access(subgroup)}}
  {%- endfor %}
{%- endmacro %}

{#
  The `field_source_expression` and `group_source_expression` are used
  for the special two-argument constructor of ComputedStyleBuilderBase,
  and provide expressions suited to initialize the given field/group from
  one of two sources:

   - Non-inherited fields/groups are copied from `non_inherited_source`.
   - Inherited fields/groups are copied from `inherited_source`.
   - Fields with reset_on_new_style are instead set to their initial values.

  Subgroups with a mix of non-inherited and inherited fields are also
  initialized in the two-source fashion described above.
#}

{% macro field_source_expression(field, non_inherited_source, inherited_source) -%}
  {%- if field.reset_on_new_style -%}
    {{encode(field, field.default_value)}} /* {{field.name}}{{print_if(field.mutable, " (mutable)")}} */
  {%- elif field.is_inherited -%}
    {{inherited_source}}.{{accessor_expression_prefix(field)}}{{field.name}}
  {%- else -%}
    {{non_inherited_source}}.{{accessor_expression_prefix(field)}}{{field.name}}
  {%- endif -%}
{%- endmacro %}

{% macro group_source_expression(group, non_inherited_source, inherited_source) -%}
  {% set non_inherited = non_inherited_source + "." + group.member_name %}
  {% set inherited = inherited_source + "." + group.member_name %}
  {%- if group.all_fields|rejectattr("is_inherited")|list|length == 0 -%}
    {{inherited}}
  {%- elif group.all_fields|selectattr("is_inherited")|list|length == 0 -%}
    {{non_inherited}}
  {%- else -%}
    {#- Mix of non-inherited and inherited fields. -#}
    {{inherited}} == {{non_inherited}}
        ? {{inherited}}.Get()
        : MakeGarbageCollected<{{group.type_name}}>(*{{non_inherited}}, *{{inherited}})
  {%- endif -%}
{%- endmacro %}

{% macro diff_field(is_pointer_type, expr) %}
{% if is_pointer_type -%}
!base::ValuesEquivalent(a.{{expr}}, b.{{expr}})
{%- else -%}
a.{{expr}} != b.{{expr}}
{%- endif %}
{% endmacro %}

{% macro field_invalidation_diff(group_to_diff) %}
{% for subgroup in group_to_diff.subgroups|selectattr("needs_diff") %}
if (a.{{group_getter_expression(subgroup)}}.Get() != b.{{group_getter_expression(subgroup)}}.Get()) {
{{field_invalidation_diff(subgroup)|indent(2, true)}}
}
{% endfor %}
{% for field in group_to_diff.fields|selectattr("needs_diff") %}
if ({{diff_field(field.wrapper_pointer_name, getter_expression(field))}}) {
{% for invalidate in field.invalidate %}
  diff |= {{invalidate}};
{% endfor %}
}
{% endfor %}
{% endmacro %}

{% macro transition_all_diff(computed_style, properties, with_discrete) %}
  {% set filter_name = with_discrete and 'may_be_affected_by_transition_all_discrete' or 'may_be_affected_by_transition_all' %}
  {% set extra_properties = properties|selectattr('is_property')|selectattr(filter_name)|rejectattr('field_template')|rejectattr('legacy_overlapping')|rejectattr('is_logical')|list %}
  {% for property in extra_properties %}
    {% set _ = property.__setattr__('processed', False) %}
  {% endfor %}
  {{transition_all_diff_group(computed_style, properties, filter_name)}}

  {% for property in extra_properties|rejectattr('processed') %}
    {% if property.stored_on_extra_field and "crbug.com/40919412" in property.stored_on_extra_field %}
      // TODO(crbug.com/40919412): Add support for {{property.name}} in PropertyEquality.
    {% elif property.surrogate_for and property.surrogate_for.is_animation_affecting %}
      {# Surrogates of animation-affecting properties are not processed, but that is not an error. Ignore. #}
    {% else %}
      #error Property {{property.name}} has missing or invalid stored_on_extra_field (in css_properties.json5)
    {% endif %}
  {% endfor %}
{% endmacro %}

{% macro transition_all_diff_group(group_to_diff, properties, filter_name) %}
  {# Recurse into subgroups. We make local variables to tell the compiler that it
     does not have to keep reloading the pointers even after calling non-inlined
     comparison functions. #}
  {% for subgroup in group_to_diff.subgroups %}
    {% set subgroup_var_name = group_getter_expression(subgroup)|replace('->', '_') %}
    auto* a_{{subgroup_var_name}} = a.{{group_getter_expression(subgroup)}}.Get();
    auto* b_{{subgroup_var_name}} = b.{{group_getter_expression(subgroup)}}.Get();
    if (a_{{subgroup_var_name}} != b_{{subgroup_var_name}}) {
      {{transition_all_diff_group(subgroup, properties, filter_name)|
        replace('.' + group_getter_expression(subgroup), '_' + subgroup_var_name)|
        indent(2, true)}}
    }
  {% endfor %}

  {# Output tests for properties stored in this group's property fields. #}
  {% for field in group_to_diff.fields|selectattr(filter_name) %}
    if ({{diff_field(field.wrapper_pointer_name, getter_expression(field))}}) {
      diff.Set(CSSPropertyID::{{field.enum_name}});
      {# If there are any surrogates of this field, they probably also want
         their bits set, so that they get the proper events fired. #}
      {% for surrogate_property in properties|selectattr('surrogate_for', 'equalto', field.property_if_main_field) %}
         diff.Set(CSSPropertyID::{{surrogate_property.enum_key}});  // Surrogate.
         {% set _ = surrogate_property.__setattr__('processed', True) %}
      {% endfor %}
    }
  {% endfor %}

  {# Output tests for properties whose visited properties are stored
     in this group's property fields. #}
  {% for field in group_to_diff.fields|selectattr('property_if_main_field') %}
    {% set property = field.property_if_main_field.unvisited_property %}
    {% if property and property[filter_name] %}
      // {{property.name}} is partially stored on {{field.name}} in this group
      // (which may not be the same as its home group), so test it here too.
      if ({{diff_field(field.wrapper_pointer_name, getter_expression(field))}}) {
        diff.Set(CSSPropertyID::{{property.enum_key}});
      }
    {% endif %}
  {% endfor %}

  {# Output tests for properties that are stored in extra_fields
     stored in this group. #}
  {% for field in group_to_diff.fields|selectattr('is_extra_field') %}
    {% set properties_this_field = [] %}
    {% for property in properties|selectattr(filter_name)|selectattr('stored_on_extra_field') %}
      {% if field.property_name in property.stored_on_extra_field %}
        {% set _ = properties_this_field.append(property) %}
      {% endif %}
    {% endfor %}
    {% if properties_this_field %}
      if ({{diff_field(field.wrapper_pointer_name, getter_expression(field))}}) {
        {% for property in properties_this_field %}
          {% if property.field_template %}
            // {{property.name}} is partially stored on {{field.name}} in this group
            // (which may not be the same as its home group), so test it here too.
          {% else %}
            // {{property.name}} is stored on {{field.name}} in this group, so test it here.
          {% endif %}
            diff.Set(CSSPropertyID::{{property.enum_key}});
          {% set _ = property.__setattr__('processed', True) %}
        {% endfor %}
      }
    {% endif %}
  {% endfor %}
{% endmacro %}
