/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup RNA
 */

#include <cctype>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <sstream>

#include <fmt/format.h>

#include "MEM_guardedalloc.h"

#include "DNA_ID.h"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_windowmanager_types.h"

#include "BLI_dynstr.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_mutex.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"

#include "BLT_translation.hh"

#include "BKE_anim_data.hh"
#include "BKE_collection.hh"
#include "BKE_context.hh"
#include "BKE_fcurve.hh"
#include "BKE_global.hh"
#include "BKE_idprop.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_report.hh"

#include "CLG_log.h"

#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"

#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_path.hh"
#include "RNA_types.hh"

#include "UI_resources.hh"

#include "WM_api.hh"
#include "WM_message.hh"

/* flush updates */
#include "WM_types.hh"

#ifdef WITH_PYTHON
#  include "BPY_extern.hh"
#endif

#include "rna_access_internal.hh"
#include "rna_internal.hh"

static CLG_LogRef LOG = {"rna.access"};

/* Init/Exit */

/* NOTE: Initializing this object here is fine for now, as it should not allocate any memory. */
extern const PointerRNA PointerRNA_NULL = {};

void RNA_init()
{
  StructRNA *srna;

  BLENDER_RNA.structs_map = BLI_ghash_str_new_ex(__func__, 2048);
  BLENDER_RNA.structs_len = 0;

  for (srna = static_cast<StructRNA *>(BLENDER_RNA.structs.first); srna;
       srna = static_cast<StructRNA *>(srna->cont.next))
  {
    if (!srna->cont.prop_lookup_set) {
      srna->cont.prop_lookup_set =
          MEM_new<blender::CustomIDVectorSet<PropertyRNA *, PropertyRNAIdentifierGetter>>(
              __func__);

      LISTBASE_FOREACH (PropertyRNA *, prop, &srna->cont.properties) {
        if (!(prop->flag_internal & PROP_INTERN_BUILTIN)) {
          srna->cont.prop_lookup_set->add(prop);
        }
      }
    }
    BLI_assert(srna->flag & STRUCT_PUBLIC_NAMESPACE);
    BLI_ghash_insert(BLENDER_RNA.structs_map, (void *)srna->identifier, srna);
    BLENDER_RNA.structs_len += 1;
  }
}

void RNA_bpy_exit()
{
#ifdef WITH_PYTHON
  StructRNA *srna;

  for (srna = static_cast<StructRNA *>(BLENDER_RNA.structs.first); srna;
       srna = static_cast<StructRNA *>(srna->cont.next))
  {
    /* NOTE(@ideasman42): each call locks the Python's GIL. Only locking/unlocking once
     * is possible but gives barely measurable speedup (< ~1millisecond) so leave as-is. */
    BPY_free_srna_pytype(srna);
  }
#endif
}

void RNA_exit()
{
  StructRNA *srna;

  for (srna = static_cast<StructRNA *>(BLENDER_RNA.structs.first); srna;
       srna = static_cast<StructRNA *>(srna->cont.next))
  {
    MEM_SAFE_DELETE(srna->cont.prop_lookup_set);
  }

  RNA_free(&BLENDER_RNA);
}

/* Pointer */

BLI_INLINE void rna_pointer_refine(PointerRNA &r_ptr)
{
  while (r_ptr.type->refine) {
    StructRNA *type = r_ptr.type->refine(&r_ptr);
    if (type == r_ptr.type) {
      break;
    }
    r_ptr.type = type;
  }
}

void rna_pointer_create_with_ancestors(const PointerRNA &parent,
                                       StructRNA *type,
                                       void *data,
                                       PointerRNA &r_ptr)
{
  if (data) {
    if (type && type->flag & STRUCT_ID) {
      /* Currently, assume that an ID PointerRNA never has an ancestor.
       * NOTE: This may become an issue for embedded IDs in the future, see also
       * #PointerRNA::ancestors docs. */
      r_ptr = {static_cast<ID *>(data), type, data};
    }
    else {
      r_ptr = {parent.owner_id, type, data, parent};
    }
    rna_pointer_refine(r_ptr);
  }
  else {
    r_ptr = {};
  }
}

PointerRNA RNA_main_pointer_create(Main *main)
{
  return {nullptr, &RNA_BlendData, main};
}

PointerRNA RNA_id_pointer_create(ID *id)
{
  if (id) {
    PointerRNA ptr{id, ID_code_to_RNA_type(GS(id->name)), id};
    rna_pointer_refine(ptr);
    return ptr;
  }

  return PointerRNA_NULL;
}

PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
{
  PointerRNA ptr{id, type, data};

  if (data) {
    rna_pointer_refine(ptr);
  }

  return ptr;
}

PointerRNA RNA_pointer_create_with_parent(const PointerRNA &parent, StructRNA *type, void *data)
{
  PointerRNA result;
  rna_pointer_create_with_ancestors(parent, type, data, result);
  return result;
}

PointerRNA RNA_pointer_create_id_subdata(ID &id, StructRNA *type, void *data)
{
  PointerRNA parent = RNA_id_pointer_create(&id);
  PointerRNA result;
  rna_pointer_create_with_ancestors(parent, type, data, result);
  return result;
}

PointerRNA RNA_pointer_create_from_ancestor(const PointerRNA &ptr, const int ancestor_idx)
{
  if (ancestor_idx >= ptr.ancestors.size()) {
    BLI_assert_unreachable();
    return {};
  }

  /* NOTE: No call to `rna_pointer_refine` should be needed here, as ancestors info should have
   * been created from already refined PointerRNA data. */
  PointerRNA ancestor_ptr{ptr.owner_id,
                          ptr.ancestors[ancestor_idx].type,
                          ptr.ancestors[ancestor_idx].data,
                          ptr.ancestors.as_span().slice(0, ancestor_idx)};
#ifndef NDEBUG
  StructRNA *type = ancestor_ptr.type;
  rna_pointer_refine(ancestor_ptr);
  BLI_assert(type == ancestor_ptr.type);
#endif

  return ancestor_ptr;
}

bool RNA_pointer_is_null(const PointerRNA *ptr)
{
  return (ptr->data == nullptr) || (ptr->owner_id == nullptr) || (ptr->type == nullptr);
}

PointerRNA RNA_blender_rna_pointer_create()
{
  PointerRNA ptr = {};
  ptr.owner_id = nullptr;
  ptr.type = &RNA_BlenderRNA;
  ptr.data = &BLENDER_RNA;
  return ptr;
}

PointerRNA RNA_pointer_recast(PointerRNA *ptr)
{
#if 0 /* works but this case if covered by more general code below. */
  if (RNA_struct_is_ID(ptr->type)) {
    /* simple case */
    *r_ptr = RNA_id_pointer_create(ptr->owner_id);
  }
  else
#endif
  {
    PointerRNA r_ptr{*ptr};
    PointerRNA t_ptr{*ptr};
    StructRNA *base;

    for (base = ptr->type->base; base; base = base->base) {
      t_ptr.type = base;
      rna_pointer_refine(t_ptr);
      if (t_ptr.type && t_ptr.type != ptr->type) {
        r_ptr = t_ptr;
      }
    }
    return r_ptr;
  }
}

/* ID Properties */

void rna_idproperty_touch(IDProperty *idprop)
{
  /* so the property is seen as 'set' by rna */
  idprop->flag &= ~IDP_FLAG_GHOST;
}

IDProperty **RNA_struct_idprops_p(PointerRNA *ptr)
{
  StructRNA *type = ptr->type;
  if (type == nullptr) {
    return nullptr;
  }
  if (type->idproperties == nullptr) {
    return nullptr;
  }

  return type->idproperties(ptr);
}

IDProperty *RNA_struct_idprops(PointerRNA *ptr, bool create)
{
  IDProperty **property_ptr = RNA_struct_idprops_p(ptr);
  if (property_ptr == nullptr) {
    return nullptr;
  }

  if (create && *property_ptr == nullptr) {
    *property_ptr = blender::bke::idprop::create_group(__func__, IDP_FLAG_STATIC_TYPE).release();
  }

  return *property_ptr;
}

bool RNA_struct_idprops_check(const StructRNA *srna)
{
  return (srna && srna->idproperties);
}

IDProperty *rna_idproperty_find(PointerRNA *ptr, const char *name)
{
  IDProperty *group = RNA_struct_idprops(ptr, false);

  if (group) {
    if (group->type == IDP_GROUP) {
      return IDP_GetPropertyFromGroup(group, name);
    }
    /* Not sure why that happens sometimes, with nested properties... */
    /* Seems to be actually array prop, name is usually "0"... To be sorted out later. */
#if 0
    printf("Got unexpected IDProp container when trying to retrieve %s: %d\n", name, group->type);
#endif
  }

  return nullptr;
}

static void rna_idproperty_free(PointerRNA *ptr, const char *name)
{
  IDProperty *group = RNA_struct_idprops(ptr, false);

  if (group) {
    IDProperty *idprop = IDP_GetPropertyFromGroup(group, name);
    if (idprop) {
      IDP_FreeFromGroup(group, idprop);
    }
  }
}

static int rna_ensure_property_array_length(PointerRNA *ptr, PropertyRNA *prop)
{
  if (prop->magic == RNA_MAGIC) {
    int arraylen[RNA_MAX_ARRAY_DIMENSION];
    return (prop->getlength && ptr->data) ? prop->getlength(ptr, arraylen) :
                                            int(prop->totarraylength);
  }
  IDProperty *idprop = (IDProperty *)prop;

  if (idprop->type == IDP_ARRAY) {
    return idprop->len;
  }
  return 0;
}

static bool rna_ensure_property_array_check(PropertyRNA *prop)
{
  if (prop->magic == RNA_MAGIC) {
    return (prop->getlength || prop->totarraylength);
  }
  IDProperty *idprop = (IDProperty *)prop;

  return (idprop->type == IDP_ARRAY);
}

static void rna_ensure_property_multi_array_length(const PointerRNA *ptr,
                                                   PropertyRNA *prop,
                                                   int length[])
{
  if (prop->magic == RNA_MAGIC) {
    if (prop->getlength) {
      prop->getlength(ptr, length);
    }
    else {
      memcpy(length, prop->arraylength, prop->arraydimension * sizeof(int));
    }
  }
  else {
    IDProperty *idprop = (IDProperty *)prop;

    if (idprop->type == IDP_ARRAY) {
      length[0] = idprop->len;
    }
    else {
      length[0] = 0;
    }
  }
}

static bool rna_idproperty_verify_valid(PointerRNA *ptr, PropertyRNA *prop, IDProperty *idprop)
{
  /* this verifies if the idproperty actually matches the property
   * description and otherwise removes it. this is to ensure that
   * rna property access is type safe, e.g. if you defined the rna
   * to have a certain array length you can count on that staying so */

  switch (idprop->type) {
    case IDP_IDPARRAY:
      if (prop->type != PROP_COLLECTION) {
        return false;
      }
      break;
    case IDP_ARRAY:
      if (rna_ensure_property_array_length(ptr, prop) != idprop->len) {
        return false;
      }

      if (idprop->subtype == IDP_FLOAT && prop->type != PROP_FLOAT) {
        return false;
      }
      if (idprop->subtype == IDP_BOOLEAN && prop->type != PROP_BOOLEAN) {
        return false;
      }
      if (idprop->subtype == IDP_INT && !ELEM(prop->type, PROP_BOOLEAN, PROP_INT, PROP_ENUM)) {
        return false;
      }

      break;
    case IDP_INT:
      if (!ELEM(prop->type, PROP_BOOLEAN, PROP_INT, PROP_ENUM)) {
        return false;
      }
      break;
    case IDP_BOOLEAN:
      if (prop->type != PROP_BOOLEAN) {
        return false;
      }
      break;
    case IDP_FLOAT:
    case IDP_DOUBLE:
      if (prop->type != PROP_FLOAT) {
        return false;
      }
      break;
    case IDP_STRING:
      if (prop->type != PROP_STRING) {
        return false;
      }
      break;
    case IDP_GROUP:
    case IDP_ID:
      if (prop->type != PROP_POINTER) {
        return false;
      }
      break;
    default:
      return false;
  }

  return true;
}

static PropertyRNA *typemap[IDP_NUMTYPES] = {
    &rna_PropertyGroupItem_string,
    &rna_PropertyGroupItem_int,
    &rna_PropertyGroupItem_float,
    nullptr,
    nullptr,
    nullptr,
    &rna_PropertyGroupItem_group,
    &rna_PropertyGroupItem_id,
    &rna_PropertyGroupItem_double,
    &rna_PropertyGroupItem_idp_array,
    &rna_PropertyGroupItem_bool,
};

static PropertyRNA *arraytypemap[IDP_NUMTYPES] = {
    nullptr,
    &rna_PropertyGroupItem_int_array,
    &rna_PropertyGroupItem_float_array,
    nullptr,
    nullptr,
    nullptr,
    &rna_PropertyGroupItem_collection,
    nullptr,
    &rna_PropertyGroupItem_double_array,
    nullptr,
    &rna_PropertyGroupItem_bool_array,
};

void rna_property_rna_or_id_get(PropertyRNA *prop,
                                PointerRNA *ptr,
                                PropertyRNAOrID *r_prop_rna_or_id)
{
  /* This is quite a hack, but avoids some complexity in the API. we
   * pass IDProperty structs as PropertyRNA pointers to the outside.
   * We store some bytes in PropertyRNA structs that allows us to
   * distinguish it from IDProperty structs. If it is an ID property,
   * we look up an IDP PropertyRNA based on the type, and set the data
   * pointer to the IDProperty. */
  *r_prop_rna_or_id = {};

  r_prop_rna_or_id->ptr = ptr;
  r_prop_rna_or_id->rawprop = prop;

  if (prop->magic == RNA_MAGIC) {
    r_prop_rna_or_id->rnaprop = prop;
    r_prop_rna_or_id->identifier = prop->identifier;

    r_prop_rna_or_id->is_array = prop->getlength || prop->totarraylength;
    if (r_prop_rna_or_id->is_array) {
      int arraylen[RNA_MAX_ARRAY_DIMENSION];
      r_prop_rna_or_id->array_len = (prop->getlength && ptr->data) ?
                                        uint(prop->getlength(ptr, arraylen)) :
                                        prop->totarraylength;
    }

    if (prop->flag & PROP_IDPROPERTY) {
      IDProperty *idprop = rna_idproperty_find(ptr, prop->identifier);

      if (idprop != nullptr && !rna_idproperty_verify_valid(ptr, prop, idprop)) {
        IDProperty *group = RNA_struct_idprops(ptr, false);

        IDP_FreeFromGroup(group, idprop);
        idprop = nullptr;
      }

      r_prop_rna_or_id->idprop = idprop;
      r_prop_rna_or_id->is_rna_storage_idprop = true;
      r_prop_rna_or_id->is_set = idprop != nullptr && (idprop->flag & IDP_FLAG_GHOST) == 0;
    }
    else {
      /* Full static RNA properties are always set. */
      r_prop_rna_or_id->is_set = true;
    }
  }
  else {
    IDProperty *idprop = (IDProperty *)prop;
    /* Given prop may come from the custom properties of another data, ensure we get the one from
     * given data ptr. */
    IDProperty *idprop_evaluated = rna_idproperty_find(ptr, idprop->name);
    if (idprop_evaluated != nullptr && idprop->type != idprop_evaluated->type) {
      idprop_evaluated = nullptr;
    }

    r_prop_rna_or_id->idprop = idprop_evaluated;
    r_prop_rna_or_id->is_idprop = true;
    /* Full IDProperties are always set, if it exists. */
    r_prop_rna_or_id->is_set = (idprop_evaluated != nullptr);

    r_prop_rna_or_id->identifier = idprop->name;
    if (idprop->type == IDP_ARRAY) {
      r_prop_rna_or_id->rnaprop = arraytypemap[int(idprop->subtype)];
      r_prop_rna_or_id->is_array = true;
      r_prop_rna_or_id->array_len = idprop_evaluated != nullptr ? uint(idprop_evaluated->len) : 0;
    }
    else {
      /* Special case for int properties with enum items, these are displayed as a PROP_ENUM. */
      if (idprop->type == IDP_INT) {
        const IDPropertyUIDataInt *ui_data_int = reinterpret_cast<IDPropertyUIDataInt *>(
            idprop->ui_data);
        if (ui_data_int && ui_data_int->enum_items_num > 0) {
          r_prop_rna_or_id->rnaprop = &rna_PropertyGroupItem_enum;
          return;
        }
      }
      r_prop_rna_or_id->rnaprop = typemap[int(idprop->type)];
    }
  }
}

IDProperty *rna_idproperty_check(PropertyRNA **prop, PointerRNA *ptr)
{
  PropertyRNAOrID prop_rna_or_id;

  rna_property_rna_or_id_get(*prop, ptr, &prop_rna_or_id);

  *prop = prop_rna_or_id.rnaprop;
  return prop_rna_or_id.idprop;
}

PropertyRNA *rna_ensure_property(PropertyRNA *prop)
{
  /* the quick version if we don't need the idproperty */

  if (prop->magic == RNA_MAGIC) {
    return prop;
  }

  {
    IDProperty *idprop = (IDProperty *)prop;

    if (idprop->type == IDP_ARRAY) {
      return arraytypemap[int(idprop->subtype)];
    }
    /* Special case for int properties with enum items, these are displayed as a PROP_ENUM. */
    if (idprop->type == IDP_INT) {
      const IDPropertyUIDataInt *ui_data_int = reinterpret_cast<IDPropertyUIDataInt *>(
          idprop->ui_data);
      if (ui_data_int && ui_data_int->enum_items_num > 0) {
        return &rna_PropertyGroupItem_enum;
      }
    }
    return typemap[int(idprop->type)];
  }
}

static const char *rna_ensure_property_identifier(const PropertyRNA *prop)
{
  if (prop->magic == RNA_MAGIC) {
    return prop->identifier;
  }
  return ((const IDProperty *)prop)->name;
}

static const char *rna_ensure_property_description(const PropertyRNA *prop)
{
  if (prop->magic == RNA_MAGIC) {
    return prop->description;
  }

  const IDProperty *idprop = (const IDProperty *)prop;
  if (idprop->ui_data) {
    const IDPropertyUIData *ui_data = idprop->ui_data;
    return ui_data->description;
  }

  return "";
}

static const char *rna_ensure_property_name(const PropertyRNA *prop)
{
  const char *name;

  if (prop->magic == RNA_MAGIC) {
    name = prop->name;
  }
  else {
    name = ((const IDProperty *)prop)->name;
  }

  return name;
}

/* Structs */

StructRNA *RNA_struct_find(const char *identifier)
{
  return static_cast<StructRNA *>(BLI_ghash_lookup(BLENDER_RNA.structs_map, identifier));
}

const char *RNA_struct_identifier(const StructRNA *type)
{
  return type->identifier;
}

const char *RNA_struct_ui_name(const StructRNA *type)
{
  return CTX_IFACE_(type->translation_context, type->name);
}

const char *RNA_struct_ui_name_raw(const StructRNA *type)
{
  return type->name;
}

int RNA_struct_ui_icon(const StructRNA *type)
{
  if (type) {
    return type->icon;
  }
  return ICON_DOT;
}

const char *RNA_struct_ui_description(const StructRNA *type)
{
  return TIP_(type->description);
}

const char *RNA_struct_ui_description_raw(const StructRNA *type)
{
  return type->description;
}

const char *RNA_struct_translation_context(const StructRNA *type)
{
  return type->translation_context;
}

PropertyRNA *RNA_struct_name_property(const StructRNA *type)
{
  return type->nameproperty;
}

const EnumPropertyItem *RNA_struct_property_tag_defines(const StructRNA *type)
{
  return type->prop_tag_defines;
}

PropertyRNA *RNA_struct_iterator_property(StructRNA *type)
{
  return type->iteratorproperty;
}

StructRNA *RNA_struct_base(StructRNA *type)
{
  return type->base;
}

const StructRNA *RNA_struct_base_child_of(const StructRNA *type, const StructRNA *parent_type)
{
  while (type) {
    if (type->base == parent_type) {
      return type;
    }
    type = type->base;
  }
  return nullptr;
}

bool RNA_struct_is_ID(const StructRNA *type)
{
  return (type->flag & STRUCT_ID) != 0;
}

bool RNA_struct_undo_check(const StructRNA *type)
{
  return (type->flag & STRUCT_UNDO) != 0;
}

bool RNA_struct_idprops_register_check(const StructRNA *type)
{
  return (type->flag & STRUCT_NO_IDPROPERTIES) == 0;
}

bool RNA_struct_idprops_datablock_allowed(const StructRNA *type)
{
  return (type->flag & (STRUCT_NO_DATABLOCK_IDPROPERTIES | STRUCT_NO_IDPROPERTIES)) == 0;
}

bool RNA_struct_idprops_contains_datablock(const StructRNA *type)
{
  return (type->flag & (STRUCT_CONTAINS_DATABLOCK_IDPROPERTIES | STRUCT_ID)) != 0;
}

bool RNA_struct_idprops_unset(PointerRNA *ptr, const char *identifier)
{
  IDProperty *group = RNA_struct_idprops(ptr, false);

  if (group) {
    IDProperty *idp = IDP_GetPropertyFromGroup(group, identifier);
    if (idp) {
      IDP_FreeFromGroup(group, idp);

      return true;
    }
  }
  return false;
}

bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna)
{
  const StructRNA *base;

  if (srna == &RNA_AnyType) {
    return true;
  }

  if (!type) {
    return false;
  }

  /* ptr->type is always maximally refined */
  for (base = type; base; base = base->base) {
    if (base == srna) {
      return true;
    }
  }

  return false;
}

PropertyRNA *RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
{
  if (identifier[0] == '[' && identifier[1] == '"') {
    /* id prop lookup, not so common */
    PropertyRNA *r_prop = nullptr;
    PointerRNA r_ptr; /* only support single level props */
    if (RNA_path_resolve_property(ptr, identifier, &r_ptr, &r_prop) && (r_ptr.type == ptr->type) &&
        (r_ptr.data == ptr->data))
    {
      return r_prop;
    }
  }
  else {
    /* most common case */
    PropertyRNA *iterprop = RNA_struct_iterator_property(ptr->type);
    PointerRNA propptr;

    if (RNA_property_collection_lookup_string(ptr, iterprop, identifier, &propptr)) {
      return static_cast<PropertyRNA *>(propptr.data);
    }
  }

  return nullptr;
}

static const char *rna_property_type_identifier(PropertyType prop_type)
{
  switch (prop_type) {
    case PROP_BOOLEAN:
      return RNA_struct_identifier(&RNA_BoolProperty);
    case PROP_INT:
      return RNA_struct_identifier(&RNA_IntProperty);
    case PROP_FLOAT:
      return RNA_struct_identifier(&RNA_FloatProperty);
    case PROP_STRING:
      return RNA_struct_identifier(&RNA_StringProperty);
    case PROP_ENUM:
      return RNA_struct_identifier(&RNA_EnumProperty);
    case PROP_POINTER:
      return RNA_struct_identifier(&RNA_PointerProperty);
    case PROP_COLLECTION:
      return RNA_struct_identifier(&RNA_CollectionProperty);
    default:
      return RNA_struct_identifier(&RNA_Property);
  }
}

PropertyRNA *RNA_struct_find_property_check(PointerRNA &props,
                                            const char *name,
                                            const PropertyType property_type_check)
{
  PropertyRNA *prop = RNA_struct_find_property(&props, name);
  if (!prop) {
    return nullptr;
  }
  const PropertyType prop_type = RNA_property_type(prop);
  if (prop_type == property_type_check) {
    return prop;
  }
  CLOG_WARN(&LOG,
            "'%s : %s()' expected, got '%s : %s()'",
            name,
            rna_property_type_identifier(property_type_check),
            name,
            rna_property_type_identifier(prop_type));
  return nullptr;
}

PropertyRNA *RNA_struct_find_collection_property_check(PointerRNA &props,
                                                       const char *name,
                                                       const StructRNA *struct_type_check)
{
  PropertyRNA *prop = RNA_struct_find_property(&props, name);
  if (!prop) {
    return nullptr;
  }

  const PropertyType prop_type = RNA_property_type(prop);
  const StructRNA *prop_struct_type = RNA_property_pointer_type(&props, prop);
  if (prop_type == PROP_COLLECTION && prop_struct_type == struct_type_check) {
    return prop;
  }

  if (prop_type != PROP_COLLECTION) {
    CLOG_WARN(&LOG,
              "'%s : %s(type = %s)' expected, got '%s : %s()'",
              name,
              rna_property_type_identifier(PROP_COLLECTION),
              RNA_struct_identifier(struct_type_check),
              name,
              rna_property_type_identifier(prop_type));
    return nullptr;
  }

  CLOG_WARN(&LOG,
            "'%s : %s(type = %s)' expected, got '%s : %s(type = %s)'.",
            name,
            rna_property_type_identifier(PROP_COLLECTION),
            RNA_struct_identifier(struct_type_check),
            name,
            rna_property_type_identifier(PROP_COLLECTION),
            RNA_struct_identifier(prop_struct_type));
  return nullptr;
}

PropertyRNA *rna_struct_find_nested(PointerRNA *ptr, StructRNA *srna)
{
  PropertyRNA *prop = nullptr;

  RNA_STRUCT_BEGIN (ptr, iprop) {
    /* This assumes that there can only be one user of this nested struct */
    if (RNA_property_pointer_type(ptr, iprop) == srna) {
      prop = iprop;
      break;
    }
  }
  RNA_PROP_END;

  return prop;
}

bool RNA_struct_contains_property(PointerRNA *ptr, PropertyRNA *prop_test)
{
  /* NOTE: prop_test could be freed memory, only use for comparison. */

  /* validate the RNA is ok */
  PropertyRNA *iterprop;
  bool found = false;

  iterprop = RNA_struct_iterator_property(ptr->type);

  RNA_PROP_BEGIN (ptr, itemptr, iterprop) {
    // PropertyRNA *prop = itemptr.data;
    if (prop_test == (PropertyRNA *)itemptr.data) {
      found = true;
      break;
    }
  }
  RNA_PROP_END;

  return found;
}

uint RNA_struct_count_properties(StructRNA *srna)
{
  uint counter = 0;

  PointerRNA struct_ptr = RNA_pointer_create_discrete(nullptr, srna, nullptr);

  RNA_STRUCT_BEGIN (&struct_ptr, prop) {
    counter++;
    UNUSED_VARS(prop);
  }
  RNA_STRUCT_END;

  return counter;
}

const ListBase *RNA_struct_type_properties(StructRNA *srna)
{
  return &srna->cont.properties;
}

PropertyRNA *RNA_struct_type_find_property_no_base(StructRNA *srna, const char *identifier)
{
  return static_cast<PropertyRNA *>(
      BLI_findstring_ptr(&srna->cont.properties, identifier, offsetof(PropertyRNA, identifier)));
}

PropertyRNA *RNA_struct_type_find_property(StructRNA *srna, const char *identifier)
{
  for (; srna; srna = srna->base) {
    PropertyRNA *prop = RNA_struct_type_find_property_no_base(srna, identifier);
    if (prop != nullptr) {
      return prop;
    }
  }
  return nullptr;
}

FunctionRNA *RNA_struct_find_function(StructRNA *srna, const char *identifier)
{
#if 1
  FunctionRNA *func;
  for (; srna; srna = srna->base) {
    func = (FunctionRNA *)BLI_findstring_ptr(
        &srna->functions, identifier, offsetof(FunctionRNA, identifier));
    if (func) {
      return func;
    }
  }
  return nullptr;

  /* functional but slow */
#else
  PropertyRNA *iterprop;
  FunctionRNA *func;

  PointerRNA tptr = RNA_pointer_create_discrete(nullptr, &RNA_Struct, srna);
  iterprop = RNA_struct_find_property(&tptr, "functions");

  func = nullptr;

  RNA_PROP_BEGIN (&tptr, funcptr, iterprop) {
    if (STREQ(identifier, RNA_function_identifier(funcptr.data))) {
      func = funcptr.data;
      break;
    }
  }
  RNA_PROP_END;

  return func;
#endif
}

const ListBase *RNA_struct_type_functions(StructRNA *srna)
{
  return &srna->functions;
}

StructRegisterFunc RNA_struct_register(StructRNA *type)
{
  return type->reg;
}

StructUnregisterFunc RNA_struct_unregister(StructRNA *type)
{
  do {
    if (type->unreg) {
      return type->unreg;
    }
  } while ((type = type->base));

  return nullptr;
}

void **RNA_struct_instance(PointerRNA *ptr)
{
  StructRNA *type = ptr->type;

  do {
    if (type->instance) {
      return type->instance(ptr);
    }
  } while ((type = type->base));

  return nullptr;
}

void *RNA_struct_py_type_get(StructRNA *srna)
{
  return srna->py_type;
}

void RNA_struct_py_type_set(StructRNA *srna, void *py_type)
{
  srna->py_type = py_type;
}

void *RNA_struct_blender_type_get(StructRNA *srna)
{
  return srna->blender_type;
}

void RNA_struct_blender_type_set(StructRNA *srna, void *blender_type)
{
  srna->blender_type = blender_type;
}

char *RNA_struct_name_get_alloc(PointerRNA *ptr, char *fixedbuf, int fixedlen, int *r_len)
{
  PropertyRNA *nameprop;

  if (ptr->data && (nameprop = RNA_struct_name_property(ptr->type))) {
    return RNA_property_string_get_alloc(ptr, nameprop, fixedbuf, fixedlen, r_len);
  }

  return nullptr;
}

bool RNA_struct_available_or_report(ReportList *reports, const char *identifier)
{
  const StructRNA *srna_exists = RNA_struct_find(identifier);
  if (UNLIKELY(srna_exists != nullptr)) {
    /* Use comprehensive string construction since this is such a rare occurrence
     * and information here may cut down time troubleshooting. */
    DynStr *dynstr = BLI_dynstr_new();
    BLI_dynstr_appendf(dynstr, "Type identifier '%s' is already in use: '", identifier);
    BLI_dynstr_append(dynstr, srna_exists->identifier);
    int i = 0;
    if (srna_exists->base) {
      for (const StructRNA *base = srna_exists->base; base; base = base->base) {
        BLI_dynstr_append(dynstr, "(");
        BLI_dynstr_append(dynstr, base->identifier);
        i += 1;
      }
      while (i--) {
        BLI_dynstr_append(dynstr, ")");
      }
    }
    BLI_dynstr_append(dynstr, "'.");
    char *result = BLI_dynstr_get_cstring(dynstr);
    BLI_dynstr_free(dynstr);
    BKE_report(reports, RPT_ERROR, result);
    MEM_freeN(result);
    return false;
  }
  return true;
}

bool RNA_struct_bl_idname_ok_or_report(ReportList *reports,
                                       const char *identifier,
                                       const char *sep)
{
  const int len_sep = strlen(sep);
  const int len_id = strlen(identifier);
  const char *p = strstr(identifier, sep);
  /* TODO: make error, for now warning until add-ons update. */
#if 1
  const int report_level = RPT_WARNING;
  const bool failure = true;
#else
  const int report_level = RPT_ERROR;
  const bool failure = false;
#endif
  if (p == nullptr || p == identifier || p + len_sep >= identifier + len_id) {
    BKE_reportf(reports,
                eReportType(report_level),
                "'%s' does not contain '%s' with prefix and suffix",
                identifier,
                sep);
    return failure;
  }

  const char *c, *start, *end, *last;
  start = identifier;
  end = p;
  last = end - 1;
  for (c = start; c != end; c++) {
    if (((*c >= 'A' && *c <= 'Z') || ((c != start) && (*c >= '0' && *c <= '9')) ||
         ((c != start) && (c != last) && (*c == '_'))) == 0)
    {
      BKE_reportf(reports,
                  eReportType(report_level),
                  "'%s' doesn't have upper case alpha-numeric prefix",
                  identifier);
      return failure;
    }
  }

  start = p + len_sep;
  end = identifier + len_id;
  last = end - 1;
  for (c = start; c != end; c++) {
    if (((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9') ||
         ((c != start) && (c != last) && (*c == '_'))) == 0)
    {
      BKE_reportf(reports,
                  eReportType(report_level),
                  "'%s' doesn't have an alpha-numeric suffix",
                  identifier);
      return failure;
    }
  }
  return true;
}

/* Property Information */

const char *RNA_property_identifier(const PropertyRNA *prop)
{
  return rna_ensure_property_identifier(prop);
}

const char *RNA_property_description(PropertyRNA *prop)
{
  return TIP_(rna_ensure_property_description(prop));
}

PropertyType RNA_property_type(PropertyRNA *prop)
{
  return rna_ensure_property(prop)->type;
}

PropertySubType RNA_property_subtype(PropertyRNA *prop)
{
  PropertyRNA *rna_prop = rna_ensure_property(prop);

  /* For custom properties, find and parse the 'subtype' metadata field. */
  if (prop->magic != RNA_MAGIC) {
    IDProperty *idprop = (IDProperty *)prop;

    if (idprop->type == IDP_STRING && idprop->subtype == IDP_STRING_SUB_BYTE) {
      return PROP_BYTESTRING;
    }

    if (idprop->ui_data) {
      IDPropertyUIData *ui_data = idprop->ui_data;
      return (PropertySubType)ui_data->rna_subtype;
    }
  }

  return rna_prop->subtype;
}

PropertyUnit RNA_property_unit(PropertyRNA *prop)
{
  return PropertyUnit(RNA_SUBTYPE_UNIT(RNA_property_subtype(prop)));
}

PropertyScaleType RNA_property_ui_scale(PropertyRNA *prop)
{
  PropertyRNA *rna_prop = rna_ensure_property(prop);

  switch (rna_prop->type) {
    case PROP_INT: {
      IntPropertyRNA *iprop = (IntPropertyRNA *)rna_prop;
      return iprop->ui_scale_type;
    }
    case PROP_FLOAT: {
      FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_prop;
      return fprop->ui_scale_type;
    }
    default:
      return PROP_SCALE_LINEAR;
  }
}

int RNA_property_flag(PropertyRNA *prop)
{
  return rna_ensure_property(prop)->flag;
}

int RNA_property_tags(PropertyRNA *prop)
{
  return rna_ensure_property(prop)->tags;
}

PropertyPathTemplateType RNA_property_path_template_type(PropertyRNA *prop)
{
  return rna_ensure_property(prop)->path_template_type;
}

bool RNA_property_builtin(PropertyRNA *prop)
{
  return (rna_ensure_property(prop)->flag_internal & PROP_INTERN_BUILTIN) != 0;
}

void *RNA_property_py_data_get(PropertyRNA *prop)
{
  /* This is only called in isolated situations (mainly by the Python API),
   * so skip check for ID properties. Callers must use #RNA_property_is_idprop
   * when it's not known if the property might be an ID Property. */
  BLI_assert(prop->magic == RNA_MAGIC);
  return prop->py_data;
}

int RNA_property_array_length(PointerRNA *ptr, PropertyRNA *prop)
{
  return rna_ensure_property_array_length(ptr, prop);
}

bool RNA_property_array_check(PropertyRNA *prop)
{
  return rna_ensure_property_array_check(prop);
}

int RNA_property_array_dimension(const PointerRNA *ptr, PropertyRNA *prop, int length[])
{
  PropertyRNA *rprop = rna_ensure_property(prop);

  if (length) {
    rna_ensure_property_multi_array_length(ptr, prop, length);
  }

  return rprop->arraydimension;
}

int RNA_property_multi_array_length(PointerRNA *ptr, PropertyRNA *prop, int dimension)
{
  int len[RNA_MAX_ARRAY_DIMENSION];

  rna_ensure_property_multi_array_length(ptr, prop, len);

  return len[dimension];
}

char RNA_property_array_item_char(PropertyRNA *prop, int index)
{
  const char *vectoritem = "XYZW";
  const char *quatitem = "WXYZ";
  const char *coloritem = "RGBA";
  PropertySubType subtype = RNA_property_subtype(prop);

  BLI_assert(index >= 0);

  /* get string to use for array index */
  if ((index < 4) && ELEM(subtype, PROP_QUATERNION, PROP_AXISANGLE)) {
    return quatitem[index];
  }
  if ((index < 4) && ELEM(subtype,
                          PROP_TRANSLATION,
                          PROP_DIRECTION,
                          PROP_XYZ,
                          PROP_XYZ_LENGTH,
                          PROP_EULER,
                          PROP_VELOCITY,
                          PROP_ACCELERATION,
                          PROP_COORDS))
  {
    return vectoritem[index];
  }
  if ((index < 4) && ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA)) {
    return coloritem[index];
  }

  return '\0';
}

int RNA_property_array_item_index(PropertyRNA *prop, char name)
{
  /* Don't use custom property sub-types in RNA path lookup. */
  PropertySubType subtype = rna_ensure_property(prop)->subtype;

  /* get index based on string name/alias */
  /* maybe a function to find char index in string would be better than all the switches */
  if (ELEM(subtype, PROP_QUATERNION, PROP_AXISANGLE)) {
    switch (name) {
      case 'w':
        return 0;
      case 'x':
        return 1;
      case 'y':
        return 2;
      case 'z':
        return 3;
    }
  }
  else if (ELEM(subtype,
                PROP_TRANSLATION,
                PROP_DIRECTION,
                PROP_XYZ,
                PROP_XYZ_LENGTH,
                PROP_EULER,
                PROP_VELOCITY,
                PROP_ACCELERATION))
  {
    switch (name) {
      case 'x':
        return 0;
      case 'y':
        return 1;
      case 'z':
        return 2;
      case 'w':
        return 3;
    }
  }
  else if (ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA)) {
    switch (name) {
      case 'r':
        return 0;
      case 'g':
        return 1;
      case 'b':
        return 2;
      case 'a':
        return 3;
    }
  }

  return -1;
}

void RNA_property_int_range(PointerRNA *ptr, PropertyRNA *prop, int *hardmin, int *hardmax)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
  int softmin, softmax;

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->ui_data) {
      IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)idprop->ui_data;
      *hardmin = ui_data->min;
      *hardmax = ui_data->max;
    }
    else {
      *hardmin = INT_MIN;
      *hardmax = INT_MAX;
    }
    return;
  }

  if (iprop->range) {
    *hardmin = INT_MIN;
    *hardmax = INT_MAX;

    iprop->range(ptr, hardmin, hardmax, &softmin, &softmax);
  }
  else if (iprop->range_ex) {
    *hardmin = INT_MIN;
    *hardmax = INT_MAX;

    iprop->range_ex(ptr, prop, hardmin, hardmax, &softmin, &softmax);
  }
  else {
    *hardmin = iprop->hardmin;
    *hardmax = iprop->hardmax;
  }
}

void RNA_property_int_ui_range(
    PointerRNA *ptr, PropertyRNA *prop, int *softmin, int *softmax, int *step)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);
  int hardmin, hardmax;

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->ui_data) {
      IDPropertyUIDataInt *ui_data_int = (IDPropertyUIDataInt *)idprop->ui_data;
      *softmin = ui_data_int->soft_min;
      *softmax = ui_data_int->soft_max;
      *step = ui_data_int->step;
    }
    else {
      *softmin = INT_MIN;
      *softmax = INT_MAX;
      *step = 1;
    }
    return;
  }

  *softmin = iprop->softmin;
  *softmax = iprop->softmax;

  if (iprop->range) {
    hardmin = INT_MIN;
    hardmax = INT_MAX;

    iprop->range(ptr, &hardmin, &hardmax, softmin, softmax);

    *softmin = max_ii(*softmin, hardmin);
    *softmax = min_ii(*softmax, hardmax);
  }
  else if (iprop->range_ex) {
    hardmin = INT_MIN;
    hardmax = INT_MAX;

    iprop->range_ex(ptr, prop, &hardmin, &hardmax, softmin, softmax);

    *softmin = max_ii(*softmin, hardmin);
    *softmax = min_ii(*softmax, hardmax);
  }

  *step = iprop->step;
}

void RNA_property_float_range(PointerRNA *ptr, PropertyRNA *prop, float *hardmin, float *hardmax)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_ensure_property(prop);
  float softmin, softmax;

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->ui_data) {
      IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
      *hardmin = float(ui_data->min);
      *hardmax = float(ui_data->max);
    }
    else {
      *hardmin = -FLT_MAX;
      *hardmax = FLT_MAX;
    }
    return;
  }

  if (fprop->range) {
    *hardmin = -FLT_MAX;
    *hardmax = FLT_MAX;

    fprop->range(ptr, hardmin, hardmax, &softmin, &softmax);
  }
  else if (fprop->range_ex) {
    *hardmin = -FLT_MAX;
    *hardmax = FLT_MAX;

    fprop->range_ex(ptr, prop, hardmin, hardmax, &softmin, &softmax);
  }
  else {
    *hardmin = fprop->hardmin;
    *hardmax = fprop->hardmax;
  }
}

void RNA_property_float_ui_range(PointerRNA *ptr,
                                 PropertyRNA *prop,
                                 float *softmin,
                                 float *softmax,
                                 float *step,
                                 float *precision)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_ensure_property(prop);
  float hardmin, hardmax;

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->ui_data) {
      IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)idprop->ui_data;
      *softmin = float(ui_data->soft_min);
      *softmax = float(ui_data->soft_max);
      *step = ui_data->step;
      *precision = float(ui_data->precision);
    }
    else {
      *softmin = -FLT_MAX;
      *softmax = FLT_MAX;
      *step = 1.0f;
      *precision = 3.0f;
    }
    return;
  }

  *softmin = fprop->softmin;
  *softmax = fprop->softmax;

  if (fprop->range) {
    hardmin = -FLT_MAX;
    hardmax = FLT_MAX;

    fprop->range(ptr, &hardmin, &hardmax, softmin, softmax);

    *softmin = max_ff(*softmin, hardmin);
    *softmax = min_ff(*softmax, hardmax);
  }
  else if (fprop->range_ex) {
    hardmin = -FLT_MAX;
    hardmax = FLT_MAX;

    fprop->range_ex(ptr, prop, &hardmin, &hardmax, softmin, softmax);

    *softmin = max_ff(*softmin, hardmin);
    *softmax = min_ff(*softmax, hardmax);
  }

  *step = fprop->step;
  *precision = float(fprop->precision);
}

int RNA_property_float_clamp(PointerRNA *ptr, PropertyRNA *prop, float *value)
{
  float min, max;

  RNA_property_float_range(ptr, prop, &min, &max);

  if (*value < min) {
    *value = min;
    return -1;
  }
  if (*value > max) {
    *value = max;
    return 1;
  }
  return 0;
}

int RNA_property_int_clamp(PointerRNA *ptr, PropertyRNA *prop, int *value)
{
  int min, max;

  RNA_property_int_range(ptr, prop, &min, &max);

  if (*value < min) {
    *value = min;
    return -1;
  }
  if (*value > max) {
    *value = max;
    return 1;
  }
  return 0;
}

int RNA_property_string_maxlength(PropertyRNA *prop)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
  return sprop->maxlength;
}

StructRNA *RNA_property_pointer_type(PointerRNA *ptr, PropertyRNA *prop)
{
  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->type == IDP_ID) {
      const IDPropertyUIDataID *ui_data = (const IDPropertyUIDataID *)idprop->ui_data;
      if (ui_data) {
        return ID_code_to_RNA_type(ui_data->id_type);
      }
    }
  }

  prop = rna_ensure_property(prop);

  if (prop->type == PROP_POINTER) {
    PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;

    if (pprop->type_fn) {
      return pprop->type_fn(ptr);
    }
    if (pprop->type) {
      return pprop->type;
    }
  }
  else if (prop->type == PROP_COLLECTION) {
    CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;

    if (cprop->item_type) {
      return cprop->item_type;
    }
  }
  /* ignore other types, rna_struct_find_nested calls with unchecked props */

  return &RNA_UnknownType;
}

bool RNA_property_pointer_poll(PointerRNA *ptr, PropertyRNA *prop, PointerRNA *value)
{
  prop = rna_ensure_property(prop);

  if (prop->type != PROP_POINTER) {
    printf("%s: %s is not a pointer property.\n", __func__, prop->identifier);
    return false;
  }

  PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;

  /* Can't point from linked to local datablock. */
  if (ptr->owner_id && value->owner_id && !BKE_id_can_use_id(*ptr->owner_id, *value->owner_id)) {
    return false;
  }

  /* Check custom poll function. */
  if (!pprop->poll) {
    return true;
  }

  if (rna_idproperty_check(&prop, ptr)) {
    return reinterpret_cast<PropPointerPollFuncPy>(reinterpret_cast<void *>(pprop->poll))(
        ptr, *value, prop);
  }
  return pprop->poll(ptr, *value);
}

void RNA_property_enum_items_ex(bContext *C,
                                PointerRNA *ptr,
                                PropertyRNA *prop,
                                const bool use_static,
                                const EnumPropertyItem **r_item,
                                int *r_totitem,
                                bool *r_free)
{
  if (!use_static && prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (IDProperty *)prop;
    if (idprop->type == IDP_INT) {
      IDPropertyUIDataInt *ui_data = reinterpret_cast<IDPropertyUIDataInt *>(idprop->ui_data);

      int totitem = 0;
      EnumPropertyItem *result = nullptr;
      if (ui_data) {
        for (const IDPropertyUIDataEnumItem &idprop_item :
             blender::Span(ui_data->enum_items, ui_data->enum_items_num))
        {
          BLI_assert(idprop_item.identifier != nullptr);
          BLI_assert(idprop_item.name != nullptr);
          const EnumPropertyItem item = {idprop_item.value,
                                         idprop_item.identifier,
                                         idprop_item.icon,
                                         idprop_item.name,
                                         idprop_item.description ? idprop_item.description : ""};
          RNA_enum_item_add(&result, &totitem, &item);
        }
      }

      RNA_enum_item_end(&result, &totitem);
      *r_item = result;
      if (r_totitem) {
        /* Exclude the terminator item. */
        *r_totitem = totitem - 1;
      }
      *r_free = true;
      return;
    }
  }

  EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop);

  *r_free = false;

  if (!use_static && (eprop->item_fn != nullptr)) {
    const bool no_context = (prop->flag & PROP_ENUM_NO_CONTEXT) ||
                            ((ptr->type->flag & STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID) &&
                             (ptr->owner_id == nullptr));
    if (C != nullptr || no_context) {
      const EnumPropertyItem *item;

      item = eprop->item_fn(no_context ? nullptr : C, ptr, prop, r_free);

      /* any callbacks returning nullptr should be fixed */
      BLI_assert(item != nullptr);

      if (r_totitem) {
        int tot;
        for (tot = 0; item[tot].identifier; tot++) {
          /* pass */
        }
        *r_totitem = tot;
      }

      *r_item = item;
      return;
    }
  }

  *r_item = eprop->item;
  if (r_totitem) {
    *r_totitem = eprop->totitem;
  }
}

void RNA_property_enum_items(bContext *C,
                             PointerRNA *ptr,
                             PropertyRNA *prop,
                             const EnumPropertyItem **r_item,
                             int *r_totitem,
                             bool *r_free)
{
  RNA_property_enum_items_ex(C, ptr, prop, false, r_item, r_totitem, r_free);
}

#ifdef WITH_INTERNATIONAL
static void property_enum_translate(PropertyRNA *prop,
                                    EnumPropertyItem **r_item,
                                    const int *totitem,
                                    bool *r_free)
{
  if (!(RNA_property_flag(prop) & PROP_ENUM_NO_TRANSLATE)) {
    int i;

    /* NOTE: Only do those tests once, and then use BLT_pgettext. */
    bool do_iface = BLT_translate_iface();
    bool do_tooltip = BLT_translate_tooltips();
    EnumPropertyItem *nitem;

    if (!(do_iface || do_tooltip)) {
      return;
    }

    if (*r_free) {
      nitem = *r_item;
    }
    else {
      const EnumPropertyItem *item = *r_item;
      int tot;

      if (totitem) {
        tot = *totitem;
      }
      else {
        /* count */
        for (tot = 0; item[tot].identifier; tot++) {
          /* pass */
        }
      }

      nitem = MEM_malloc_arrayN<EnumPropertyItem>(size_t(tot) + 1, __func__);
      memcpy(nitem, item, sizeof(EnumPropertyItem) * (tot + 1));

      *r_free = true;
    }

    const char *translation_context = RNA_property_translation_context(prop);

    for (i = 0; nitem[i].identifier; i++) {
      if (nitem[i].name && do_iface) {
        nitem[i].name = BLT_pgettext(translation_context, nitem[i].name);
      }
      if (nitem[i].description && do_tooltip) {
        nitem[i].description = BLT_pgettext(nullptr, nitem[i].description);
      }
    }

    *r_item = nitem;
  }
}
#endif

void RNA_property_enum_items_gettexted(bContext *C,
                                       PointerRNA *ptr,
                                       PropertyRNA *prop,
                                       const EnumPropertyItem **r_item,
                                       int *r_totitem,
                                       bool *r_free)
{
  RNA_property_enum_items(C, ptr, prop, r_item, r_totitem, r_free);

#ifdef WITH_INTERNATIONAL
  /* Normally dropping 'const' is _not_ ok, in this case it's only modified if we own the memory
   * so allow the exception (callers are creating new arrays in this case). */
  property_enum_translate(prop, (EnumPropertyItem **)r_item, r_totitem, r_free);
#endif
}

void RNA_property_enum_items_gettexted_all(bContext *C,
                                           PointerRNA *ptr,
                                           PropertyRNA *prop,
                                           const EnumPropertyItem **r_item,
                                           int *r_totitem,
                                           bool *r_free)
{
  EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop);
  size_t mem_size = sizeof(EnumPropertyItem) * (size_t(eprop->totitem) + 1);
  /* first return all items */
  EnumPropertyItem *item_array = MEM_malloc_arrayN<EnumPropertyItem>(size_t(eprop->totitem) + 1,
                                                                     "enum_gettext_all");
  *r_free = true;
  memcpy(item_array, eprop->item, mem_size);

  if (r_totitem) {
    *r_totitem = eprop->totitem;
  }

  if (eprop->item_fn != nullptr) {
    const bool no_context = (eprop->property.flag & PROP_ENUM_NO_CONTEXT) ||
                            ((ptr->type->flag & STRUCT_NO_CONTEXT_WITHOUT_OWNER_ID) &&
                             (ptr->owner_id == nullptr));
    if (C != nullptr || no_context) {
      const EnumPropertyItem *item;
      int i;
      bool free = false;

      item = eprop->item_fn(no_context ? nullptr : nullptr, ptr, prop, &free);

      /* any callbacks returning nullptr should be fixed */
      BLI_assert(item != nullptr);

      for (i = 0; i < eprop->totitem; i++) {
        bool exists = false;
        int i_fixed;

        /* Items that do not exist on list are returned,
         * but have their names/identifiers null'ed out. */
        for (i_fixed = 0; item[i_fixed].identifier; i_fixed++) {
          if (STREQ(item[i_fixed].identifier, item_array[i].identifier)) {
            exists = true;
            break;
          }
        }

        if (!exists) {
          item_array[i].name = nullptr;
          item_array[i].identifier = "";
        }
      }

      if (free) {
        MEM_freeN(item);
      }
    }
  }

#ifdef WITH_INTERNATIONAL
  property_enum_translate(prop, &item_array, r_totitem, r_free);
#endif
  *r_item = item_array;
}

bool RNA_property_enum_value(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const char *identifier, int *r_value)
{
  const EnumPropertyItem *item;
  bool free;
  bool found;

  RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);

  if (item) {
    const int i = RNA_enum_from_identifier(item, identifier);
    if (i != -1) {
      *r_value = item[i].value;
      found = true;
    }
    else {
      found = false;
    }

    if (free) {
      MEM_freeN(item);
    }
  }
  else {
    found = false;
  }
  return found;
}

bool RNA_enum_identifier(const EnumPropertyItem *item, const int value, const char **r_identifier)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_identifier = item[i].identifier;
    return true;
  }
  return false;
}

int RNA_enum_bitflag_identifiers(const EnumPropertyItem *item,
                                 const int value,
                                 const char **r_identifier)
{
  int index = 0;
  for (; item->identifier; item++) {
    if (item->identifier[0] && item->value & value) {
      r_identifier[index++] = item->identifier;
    }
  }
  r_identifier[index] = nullptr;
  return index;
}

bool RNA_enum_name(const EnumPropertyItem *item, const int value, const char **r_name)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_name = item[i].name;
    return true;
  }
  return false;
}

bool RNA_enum_name_gettexted(const EnumPropertyItem *item,
                             int value,
                             const char *translation_context,
                             const char **r_name)
{
  bool result;

  result = RNA_enum_name(item, value, r_name);

  if (result) {
    *r_name = BLT_translate_do_iface(translation_context, *r_name);
  }

  return result;
};

bool RNA_enum_description(const EnumPropertyItem *item,
                          const int value,
                          const char **r_description)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_description = item[i].description;
    return true;
  }
  return false;
}

int RNA_enum_from_identifier(const EnumPropertyItem *item, const char *identifier)
{
  int i = 0;
  for (; item->identifier; item++, i++) {
    if (item->identifier[0] && STREQ(item->identifier, identifier)) {
      return i;
    }
  }
  return -1;
}

bool RNA_enum_value_from_identifier(const EnumPropertyItem *item,
                                    const char *identifier,
                                    int *r_value)
{
  const int i = RNA_enum_from_identifier(item, identifier);
  if (i == -1) {
    return false;
  }
  *r_value = item[i].value;
  return true;
}

int RNA_enum_from_name(const EnumPropertyItem *item, const char *name)
{
  int i = 0;
  for (; item->identifier; item++, i++) {
    if (item->identifier[0] && STREQ(item->name, name)) {
      return i;
    }
  }
  return -1;
}

int RNA_enum_from_value(const EnumPropertyItem *item, const int value)
{
  int i = 0;
  for (; item->identifier; item++, i++) {
    if (item->identifier[0] && item->value == value) {
      return i;
    }
  }
  return -1;
}

uint RNA_enum_items_count(const EnumPropertyItem *item)
{
  uint i = 0;

  while (item->identifier) {
    item++;
    i++;
  }

  return i;
}

bool RNA_property_enum_identifier(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **r_identifier)
{
  const EnumPropertyItem *item = nullptr;
  bool free;

  RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);
  if (item) {
    bool result;
    result = RNA_enum_identifier(item, value, r_identifier);
    if (free) {
      MEM_freeN(item);
    }
    return result;
  }
  return false;
}

bool RNA_property_enum_name(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **r_name)
{
  const EnumPropertyItem *item = nullptr;
  bool free;

  RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);
  if (item) {
    bool result;
    result = RNA_enum_name(item, value, r_name);
    if (free) {
      MEM_freeN(item);
    }

    return result;
  }
  return false;
}

bool RNA_property_enum_name_gettexted(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **r_name)
{
  bool result;

  result = RNA_property_enum_name(C, ptr, prop, value, r_name);

  if (result) {
    if (!(prop->flag & PROP_ENUM_NO_TRANSLATE)) {
      *r_name = BLT_translate_do_iface(RNA_property_translation_context(prop), *r_name);
    }
  }

  return result;
}

bool RNA_property_enum_item_from_value(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, EnumPropertyItem *r_item)
{
  const EnumPropertyItem *item = nullptr;
  bool free;

  RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);
  if (item) {
    const int i = RNA_enum_from_value(item, value);
    bool result;

    if (i != -1) {
      *r_item = item[i];
      result = true;
    }
    else {
      result = false;
    }

    if (free) {
      MEM_freeN(item);
    }

    return result;
  }
  return false;
}

bool RNA_property_enum_item_from_value_gettexted(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, EnumPropertyItem *r_item)
{
  const bool result = RNA_property_enum_item_from_value(C, ptr, prop, value, r_item);

  if (result && !(RNA_property_flag(prop) & PROP_ENUM_NO_TRANSLATE)) {
    r_item->name = BLT_translate_do_iface(RNA_property_translation_context(prop), r_item->name);
    r_item->description = BLT_translate_do_tooltip(nullptr, r_item->description);
  }

  return result;
}

int RNA_property_enum_bitflag_identifiers(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, const int value, const char **r_identifier)
{
  const EnumPropertyItem *item = nullptr;
  bool free;

  RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);
  if (item) {
    int result;
    result = RNA_enum_bitflag_identifiers(item, value, r_identifier);
    if (free) {
      MEM_freeN(item);
    }

    return result;
  }
  return 0;
}

const char *RNA_property_ui_name(const PropertyRNA *prop)
{
  return CTX_IFACE_(RNA_property_translation_context(prop), rna_ensure_property_name(prop));
}

const char *RNA_property_ui_name_raw(const PropertyRNA *prop)
{
  return rna_ensure_property_name(prop);
}

const char *RNA_property_ui_description(const PropertyRNA *prop)
{
  return TIP_(rna_ensure_property_description(prop));
}

const char *RNA_property_ui_description_raw(const PropertyRNA *prop)
{
  return rna_ensure_property_description(prop);
}

const char *RNA_property_translation_context(const PropertyRNA *prop)
{
  return rna_ensure_property((PropertyRNA *)prop)->translation_context;
}

int RNA_property_ui_icon(const PropertyRNA *prop)
{
  return rna_ensure_property((PropertyRNA *)prop)->icon;
}

static bool rna_property_editable_do(const PointerRNA *ptr,
                                     PropertyRNA *prop_orig,
                                     const int index,
                                     const char **r_info)
{
  ID *id = ptr->owner_id;

  PropertyRNA *prop = rna_ensure_property(prop_orig);

  const char *info = "";
  const int flag = (prop->itemeditable != nullptr && index >= 0) ?
                       prop->itemeditable(ptr, index) :
                       (prop->editable != nullptr ? prop->editable(ptr, &info) : prop->flag);
  if (r_info != nullptr) {
    *r_info = info;
  }

  /* Early return if the property itself is not editable. */
  if ((flag & PROP_EDITABLE) == 0) {
    return false;
  }
  /* Only considered registerable properties "internal"
   * because regular properties may not be editable and still be displayed. */
  if (flag & PROP_REGISTER) {
    if (r_info != nullptr && (*r_info)[0] == '\0') {
      *r_info = N_("This property is for internal use only and can't be edited");
    }
    return false;
  }

  /* If there is no owning ID, the property is editable at this point. */
  if (id == nullptr) {
    return true;
  }

  /* Handle linked or liboverride ID cases. */
  const bool is_linked_prop_exception = (prop->flag & PROP_LIB_EXCEPTION) != 0;
  if (!ID_IS_EDITABLE(id)) {
    if (is_linked_prop_exception) {
      return true;
    }
    if (r_info != nullptr && (*r_info)[0] == '\0') {
      *r_info = N_("Can't edit this property from a linked data-block");
    }
    return false;
  }
  if (ID_IS_OVERRIDE_LIBRARY(id)) {
    const bool is_liboverride_system = BKE_lib_override_library_is_system_defined(G_MAIN, id);
    if (!RNA_property_overridable_get(ptr, prop_orig)) {
      if (r_info != nullptr && (*r_info)[0] == '\0') {
        *r_info = N_("Can't edit this property from an override data-block");
      }
      return false;
    }
    if (is_liboverride_system && !is_linked_prop_exception) {
      if (r_info != nullptr && (*r_info)[0] == '\0') {
        *r_info = N_("Can't edit this property from a system override data-block");
      }
      return false;
    }
  }

  /* At this point, property is owned by a local ID and therefore fully editable. */
  return true;
}

bool RNA_property_is_runtime(const PropertyRNA *prop)
{
  return prop->flag_internal & PROP_INTERN_RUNTIME;
}

bool RNA_property_editable(const PointerRNA *ptr, PropertyRNA *prop)
{
  return rna_property_editable_do(ptr, prop, -1, nullptr);
}

bool RNA_property_editable_info(const PointerRNA *ptr, PropertyRNA *prop, const char **r_info)
{
  return rna_property_editable_do(ptr, prop, -1, r_info);
}

bool RNA_property_editable_flag(const PointerRNA *ptr, PropertyRNA *prop)
{
  int flag;
  const char *dummy_info;

  prop = rna_ensure_property(prop);
  flag = prop->editable ? prop->editable(ptr, &dummy_info) : prop->flag;
  return (flag & PROP_EDITABLE) != 0;
}

bool RNA_property_editable_index(const PointerRNA *ptr, PropertyRNA *prop, const int index)
{
  BLI_assert(index >= 0);

  return rna_property_editable_do(ptr, prop, index, nullptr);
}

bool RNA_property_animateable(const PointerRNA *ptr, PropertyRNA *prop_orig)
{
  /* check that base ID-block can support animation data */
  if (!id_can_have_animdata(ptr->owner_id)) {
    return false;
  }

  PropertyRNA *prop_ensured = rna_ensure_property(prop_orig);

  if (!(prop_ensured->flag & PROP_ANIMATABLE)) {
    return false;
  }

  return true;
}

bool RNA_property_anim_editable(const PointerRNA *ptr, PropertyRNA *prop_orig)
{
  /* check that base ID-block can support animation data */
  if (!RNA_property_animateable(ptr, prop_orig)) {
    return false;
  }

  /* Linked or LibOverride Action IDs are not editable at the FCurve level. */
  if (ptr->owner_id) {
    AnimData *anim_data = BKE_animdata_from_id(ptr->owner_id);
    if (anim_data && anim_data->action &&
        (!ID_IS_EDITABLE(anim_data->action) || ID_IS_OVERRIDE_LIBRARY(anim_data->action)))
    {
      return false;
    }
  }

  return rna_property_editable_do(ptr, prop_orig, -1, nullptr);
}

bool RNA_property_driver_editable(const PointerRNA *ptr, PropertyRNA *prop)
{
  if (!RNA_property_anim_editable(ptr, prop)) {
    return false;
  }

  /* LibOverrides can only get drivers if their animdata (if any) was created for the local
   * liboverride, and there is none in the linked reference.
   *
   * See also #rna_AnimaData_override_apply. */
  if (ptr->owner_id && ID_IS_OVERRIDE_LIBRARY(ptr->owner_id)) {
    IDOverrideLibrary *liboverride = BKE_lib_override_library_get(
        nullptr, ptr->owner_id, nullptr, nullptr);
    AnimData *linked_reference_anim_data = BKE_animdata_from_id(liboverride->reference);
    if (linked_reference_anim_data) {
      return false;
    }
  }

  return true;
}

bool RNA_property_animated(PointerRNA *ptr, PropertyRNA *prop)
{
  int len = 1, index;
  bool driven, special;

  if (!prop) {
    return false;
  }

  if (RNA_property_array_check(prop)) {
    len = RNA_property_array_length(ptr, prop);
  }

  for (index = 0; index < len; index++) {
    if (BKE_fcurve_find_by_rna(ptr, prop, index, nullptr, nullptr, &driven, &special)) {
      return true;
    }
  }

  return false;
}
bool RNA_property_path_from_ID_check(PointerRNA *ptr, PropertyRNA *prop)
{
  const std::optional<std::string> path = RNA_path_from_ID_to_property(ptr, prop);
  bool ret = false;

  if (path) {
    PointerRNA r_ptr;
    PropertyRNA *r_prop;

    PointerRNA id_ptr = RNA_id_pointer_create(ptr->owner_id);
    if (RNA_path_resolve(&id_ptr, path->c_str(), &r_ptr, &r_prop) == true) {
      ret = (prop == r_prop);
    }
  }

  return ret;
}

static void rna_property_update(
    bContext *C, Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop)
{
  const bool is_rna = (prop->magic == RNA_MAGIC);
  prop = rna_ensure_property(prop);

  if (is_rna) {
    if (prop->update) {
      /* ideally no context would be needed for update, but there's some
       * parts of the code that need it still, so we have this exception */
      if (prop->flag & PROP_CONTEXT_UPDATE) {
        if (C) {
          if ((prop->flag & PROP_CONTEXT_PROPERTY_UPDATE) == PROP_CONTEXT_PROPERTY_UPDATE) {
            ((ContextPropUpdateFunc)prop->update)(C, ptr, prop);
          }
          else {
            reinterpret_cast<ContextUpdateFunc>(reinterpret_cast<void *>(prop->update))(C, ptr);
          }
        }
      }
      else {
        prop->update(bmain, scene, ptr);
      }
    }

#if 1
    /* TODO(@ideasman42): Should eventually be replaced entirely by message bus (below)
     * for now keep since copy-on-eval, bugs are hard to track when we have other missing updates.
     */
    if (prop->noteflag) {
      WM_main_add_notifier(prop->noteflag, ptr->owner_id);
    }
#endif

    /* if C is nullptr, we're updating from animation.
     * avoid slow-down from f-curves by not publishing (for now). */
    if (C != nullptr) {
      wmMsgBus *mbus = CTX_wm_message_bus(C);
      /* we could add nullptr check, for now don't */
      WM_msg_publish_rna(mbus, ptr, prop);
    }
    if (ptr->owner_id != nullptr && ((prop->flag & PROP_NO_DEG_UPDATE) == 0)) {
      const short id_type = GS(ptr->owner_id->name);
      if (ID_TYPE_USE_COPY_ON_EVAL(id_type)) {
        if (prop->flag & PROP_DEG_SYNC_ONLY) {
          DEG_id_tag_update(ptr->owner_id, ID_RECALC_SYNC_TO_EVAL);
        }
        else {
          DEG_id_tag_update(ptr->owner_id, ID_RECALC_SYNC_TO_EVAL | ID_RECALC_PARAMETERS);
        }
      }
    }
    /* End message bus. */
  }

  if (!is_rna || (prop->flag & PROP_IDPROPERTY)) {

    /* Disclaimer: this logic is not applied consistently, causing some confusing behavior.
     *
     * - When animated (which skips update functions).
     * - When ID-properties are edited via Python (since RNA properties aren't used in this case).
     *
     * Adding updates will add a lot of overhead in the case of animation.
     * For Python it may cause unexpected slow-downs for developers using ID-properties
     * for data storage. Further, the root ID isn't available with nested data-structures.
     *
     * So editing custom properties only causes updates in the UI,
     * keep this exception because it happens to be useful for driving settings.
     * Python developers on the other hand will need to manually 'update_tag', see: #74000. */
    DEG_id_tag_update(ptr->owner_id,
                      ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_PARAMETERS);

    /* When updating an ID pointer property, tag depsgraph for update. */
    if (prop->type == PROP_POINTER && RNA_struct_is_ID(RNA_property_pointer_type(ptr, prop))) {
      DEG_relations_tag_update(bmain);
    }

    WM_main_add_notifier(NC_WINDOW, nullptr);
    if (ptr->owner_id) {
      WM_main_add_notifier(NC_ID | NA_EDITED, nullptr);
    }
    /* Not nice as well, but the only way to make sure material preview
     * is updated with custom nodes.
     */
    if ((prop->flag & PROP_IDPROPERTY) != 0 && (ptr->owner_id != nullptr) &&
        (GS(ptr->owner_id->name) == ID_NT))
    {
      WM_main_add_notifier(NC_MATERIAL | ND_SHADING, nullptr);
    }
  }
}

bool RNA_property_update_check(PropertyRNA *prop)
{
  /* NOTE: must keep in sync with #rna_property_update. */
  return (prop->magic != RNA_MAGIC || prop->update || prop->noteflag);
}

void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
{
  rna_property_update(C, CTX_data_main(C), CTX_data_scene(C), ptr, prop);
}

void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop)
{
  BLI_assert(bmain != nullptr);
  rna_property_update(nullptr, bmain, scene, ptr, prop);
}

/* ---------------------------------------------------------------------- */

/* Property Data */

bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
{
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop;
  IDProperty *idprop;
  bool value;

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) == false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    value = IDP_Bool(idprop);
  }
  else if (bprop->get) {
    value = bprop->get(ptr);
  }
  else if (bprop->get_ex) {
    value = bprop->get_ex(ptr, prop);
  }
  else {
    value = bprop->defaultvalue;
  }

  BLI_assert(ELEM(value, false, true));

  return value;
}

/**
 * The boolean IDProperty type isn't supported in old versions. In order to keep forward
 * compatibility for a period of time (until 4.0), save boolean RNA properties as integer
 * IDProperties.
 */
#define USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP

void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
{
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) == false);
  BLI_assert(ELEM(value, false, true));

  /* just in case other values are passed */
  BLI_assert(ELEM(value, true, false));

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDP_Bool(idprop) = value;
    rna_idproperty_touch(idprop);
  }
  else if (bprop->set) {
    bprop->set(ptr, value);
  }
  else if (bprop->set_ex) {
    bprop->set_ex(ptr, prop, value);
  }
  else if (prop->flag & PROP_EDITABLE) {
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
#ifdef USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP
      IDP_AddToGroup(
          group,
          blender::bke::idprop::create(prop->identifier, int(value), IDP_FLAG_STATIC_TYPE)
              .release());
#else
      IDP_AddToGroup(
          group,
          blender::bke::idprop::create_bool(prop->identifier, value, IDP_FLAG_STATIC_TYPE)
              .release());
#endif
    }
  }
}

static void rna_property_boolean_fill_default_array_values(
    const bool *defarr, int defarr_length, bool defvalue, int out_length, bool *r_values)
{
  if (defarr && defarr_length > 0) {
    defarr_length = std::min(defarr_length, out_length);
    memcpy(r_values, defarr, sizeof(bool) * defarr_length);
  }
  else {
    defarr_length = 0;
  }

  for (int i = defarr_length; i < out_length; i++) {
    r_values[i] = defvalue;
  }
}

static void rna_property_boolean_fill_default_array_values_from_ints(
    const int *defarr, int defarr_length, bool defvalue, int out_length, bool *r_values)
{
  if (defarr && defarr_length > 0) {
    defarr_length = std::min(defarr_length, out_length);
    for (int i = 0; i < defarr_length; i++) {
      r_values[i] = defarr[i] != 0;
    }
  }
  else {
    defarr_length = 0;
  }

  for (int i = defarr_length; i < out_length; i++) {
    r_values[i] = defvalue;
  }
}

static void rna_property_boolean_get_default_array_values(PointerRNA *ptr,
                                                          BoolPropertyRNA *bprop,
                                                          bool *r_values)
{
  if (ptr->data && bprop->get_default_array) {
    bprop->get_default_array(ptr, &bprop->property, r_values);
    return;
  }

  int length = bprop->property.totarraylength;
  int out_length = RNA_property_array_length(ptr, (PropertyRNA *)bprop);

  rna_property_boolean_fill_default_array_values(
      bprop->defaultarray, length, bprop->defaultvalue, out_length, r_values);
}

void RNA_property_boolean_get_array(PointerRNA *ptr, PropertyRNA *prop, bool *values)
{
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    if (prop->arraydimension == 0) {
      values[0] = RNA_property_boolean_get(ptr, prop);
    }
    else if (idprop->subtype == IDP_INT) {
      /* Some boolean IDProperty arrays might be saved in files as an integer
       * array property, since the boolean IDProperty type was added later. */
      int *values_src = static_cast<int *>(IDP_Array(idprop));
      for (uint i = 0; i < idprop->len; i++) {
        values[i] = bool(values_src[i]);
      }
    }
    else if (idprop->subtype == IDP_BOOLEAN) {
      bool *values_src = static_cast<bool *>(IDP_Array(idprop));
      for (int i = 0; i < idprop->len; i++) {
        values[i] = values_src[i];
      }
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = RNA_property_boolean_get(ptr, prop);
  }
  else if (bprop->getarray) {
    bprop->getarray(ptr, values);
  }
  else if (bprop->getarray_ex) {
    bprop->getarray_ex(ptr, prop, values);
  }
  else {
    rna_property_boolean_get_default_array_values(ptr, bprop, values);
  }
}
void RNA_property_boolean_get_array_at_most(PointerRNA *ptr,
                                            PropertyRNA *prop,
                                            bool *values,
                                            int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_boolean_get_array(ptr, prop, values);
    return;
  }

  blender::Array<bool, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_boolean_get_array(ptr, prop, value_buf.data());
  memcpy(values, value_buf.data(), sizeof(*values) * values_num);
}

bool RNA_property_boolean_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  bool tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);
  bool value;

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_boolean_get_array(ptr, prop, tmp);
    value = tmp[index];
  }
  else {
    bool *tmparray;

    tmparray = MEM_malloc_arrayN<bool>(size_t(len), __func__);
    RNA_property_boolean_get_array(ptr, prop, tmparray);
    value = tmparray[index];
    MEM_freeN(tmparray);
  }

  BLI_assert(ELEM(value, false, true));

  return value;
}

void RNA_property_boolean_set_array(PointerRNA *ptr, PropertyRNA *prop, const bool *values)
{
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    if (prop->arraydimension == 0) {
      IDP_Int(idprop) = values[0];
    }
    else {
      BLI_assert(idprop->type = IDP_ARRAY);
      if (idprop->subtype == IDP_BOOLEAN) {
        memcpy(IDP_Array(idprop), values, sizeof(int8_t) * idprop->len);
      }
      else if (idprop->subtype == IDP_INT) {
        /* Support writing to integer and boolean IDProperties, since boolean
         * RNA properties used to be stored with integer IDProperties. */
        int *values_dst = static_cast<int *>(IDP_Array(idprop));
        for (uint i = 0; i < idprop->len; i++) {
          values_dst[i] = int(values[i]);
        }
      }
    }
    rna_idproperty_touch(idprop);
  }
  else if (prop->arraydimension == 0) {
    RNA_property_boolean_set(ptr, prop, values[0]);
  }
  else if (bprop->setarray) {
    bprop->setarray(ptr, values);
  }
  else if (bprop->setarray_ex) {
    bprop->setarray_ex(ptr, prop, values);
  }
  else if (prop->flag & PROP_EDITABLE) {
    IDPropertyTemplate val = {0};

    val.array.len = prop->totarraylength;
#ifdef USE_INT_IDPROPS_FOR_BOOLEAN_RNA_PROP
    val.array.type = IDP_INT;
#else
    val.array.type = IDP_BOOLEAN;
#endif

    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      idprop = IDP_New(IDP_ARRAY, &val, prop->identifier, IDP_FLAG_STATIC_TYPE);
      IDP_AddToGroup(group, idprop);
      int *values_dst = static_cast<int *>(IDP_Array(idprop));
      for (uint i = 0; i < idprop->len; i++) {
        values_dst[i] = values[i];
      }
    }
  }
}
void RNA_property_boolean_set_array_at_most(PointerRNA *ptr,
                                            PropertyRNA *prop,
                                            const bool *values,
                                            int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_boolean_set_array(ptr, prop, values);
    return;
  }

  blender::Array<bool, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_boolean_get_array(ptr, prop, value_buf.data());
  memcpy(value_buf.data(), values, sizeof(*values) * values_num);
  RNA_property_boolean_set_array(ptr, prop, value_buf.data());
}

void RNA_property_boolean_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, bool value)
{
  bool tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);
  BLI_assert(ELEM(value, false, true));

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_boolean_get_array(ptr, prop, tmp);
    tmp[index] = value;
    RNA_property_boolean_set_array(ptr, prop, tmp);
  }
  else {
    bool *tmparray;

    tmparray = MEM_malloc_arrayN<bool>(size_t(len), __func__);
    RNA_property_boolean_get_array(ptr, prop, tmparray);
    tmparray[index] = value;
    RNA_property_boolean_set_array(ptr, prop, tmparray);
    MEM_freeN(tmparray);
  }
}

bool RNA_property_boolean_get_default(PointerRNA *ptr, PropertyRNA *prop)
{
  /* TODO: Make defaults work for IDProperties. */
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) == false);
  BLI_assert(ELEM(bprop->defaultvalue, false, true));

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      switch (IDP_ui_data_type(idprop)) {
        case IDP_UI_DATA_TYPE_BOOLEAN: {
          const IDPropertyUIDataBool *ui_data = (const IDPropertyUIDataBool *)idprop->ui_data;
          return ui_data->default_value;
        }
        case IDP_UI_DATA_TYPE_INT: {
          const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
          return ui_data->default_value != 0;
        }
        default:
          BLI_assert_unreachable();
      }
    }
    return false;
  }
  if (bprop->get_default) {
    return bprop->get_default(ptr, prop);
  }

  return bprop->defaultvalue;
}

void RNA_property_boolean_get_default_array(PointerRNA *ptr, PropertyRNA *prop, bool *values)
{
  BoolPropertyRNA *bprop = (BoolPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_ARRAY);
      switch (IDP_ui_data_type(idprop)) {
        case IDP_UI_DATA_TYPE_BOOLEAN: {
          const IDPropertyUIDataBool *ui_data = (const IDPropertyUIDataBool *)idprop->ui_data;
          if (ui_data->default_array) {
            rna_property_boolean_fill_default_array_values((const bool *)ui_data->default_array,
                                                           ui_data->default_array_len,
                                                           ui_data->default_value,
                                                           idprop->len,
                                                           values);
          }
          else {
            rna_property_boolean_fill_default_array_values(
                nullptr, 0, ui_data->default_value, idprop->len, values);
          }
          break;
        }
        case IDP_UI_DATA_TYPE_INT: {
          const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
          if (ui_data->default_array) {
            rna_property_boolean_fill_default_array_values_from_ints(ui_data->default_array,
                                                                     ui_data->default_array_len,
                                                                     ui_data->default_value,
                                                                     idprop->len,
                                                                     values);
          }
          else {
            rna_property_boolean_fill_default_array_values(
                nullptr, 0, ui_data->default_value, idprop->len, values);
          }
          break;
        }
        default:
          BLI_assert_unreachable();
          break;
      }
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = bprop->defaultvalue;
  }
  else {
    rna_property_boolean_get_default_array_values(ptr, bprop, values);
  }
}

bool RNA_property_boolean_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  bool tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_BOOLEAN);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_boolean_get_default_array(ptr, prop, tmp);
    return tmp[index];
  }
  bool *tmparray, value;

  tmparray = MEM_malloc_arrayN<bool>(size_t(len), __func__);
  RNA_property_boolean_get_default_array(ptr, prop, tmparray);
  value = tmparray[index];
  MEM_freeN(tmparray);

  return value;
}

int RNA_property_int_get(PointerRNA *ptr, PropertyRNA *prop)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) == false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    return IDP_Int(idprop);
  }
  if (iprop->get) {
    return iprop->get(ptr);
  }
  if (iprop->get_ex) {
    return iprop->get_ex(ptr, prop);
  }
  return iprop->defaultvalue;
}

void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) == false);
  /* useful to check on bad values but set function should clamp */
  // BLI_assert(RNA_property_int_clamp(ptr, prop, &value) == 0);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    RNA_property_int_clamp(ptr, prop, &value);
    IDP_Int(idprop) = value;
    rna_idproperty_touch(idprop);
  }
  else if (iprop->set) {
    iprop->set(ptr, value);
  }
  else if (iprop->set_ex) {
    iprop->set_ex(ptr, prop, value);
  }
  else if (prop->flag & PROP_EDITABLE) {
    RNA_property_int_clamp(ptr, prop, &value);
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      IDP_AddToGroup(
          group,
          blender::bke::idprop::create(prop->identifier, value, IDP_FLAG_STATIC_TYPE).release());
    }
  }
}

static void rna_property_int_fill_default_array_values(
    const int *defarr, int defarr_length, int defvalue, int out_length, int *r_values)
{
  if (defarr && defarr_length > 0) {
    defarr_length = std::min(defarr_length, out_length);
    memcpy(r_values, defarr, sizeof(int) * defarr_length);
  }
  else {
    defarr_length = 0;
  }

  for (int i = defarr_length; i < out_length; i++) {
    r_values[i] = defvalue;
  }
}

static void rna_property_int_get_default_array_values(PointerRNA *ptr,
                                                      IntPropertyRNA *iprop,
                                                      int *r_values)
{
  if (ptr->data && iprop->get_default_array) {
    iprop->get_default_array(ptr, &iprop->property, r_values);
    return;
  }

  int length = iprop->property.totarraylength;
  int out_length = RNA_property_array_length(ptr, (PropertyRNA *)iprop);

  rna_property_int_fill_default_array_values(
      iprop->defaultarray, length, iprop->defaultvalue, out_length, r_values);
}

void RNA_property_int_get_array(PointerRNA *ptr, PropertyRNA *prop, int *values)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) ||
               (prop->flag & PROP_IDPROPERTY));
    if (prop->arraydimension == 0) {
      values[0] = RNA_property_int_get(ptr, prop);
    }
    else {
      memcpy(values, IDP_Array(idprop), sizeof(int) * idprop->len);
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = RNA_property_int_get(ptr, prop);
  }
  else if (iprop->getarray) {
    iprop->getarray(ptr, values);
  }
  else if (iprop->getarray_ex) {
    iprop->getarray_ex(ptr, prop, values);
  }
  else {
    rna_property_int_get_default_array_values(ptr, iprop, values);
  }
}
void RNA_property_int_get_array_at_most(PointerRNA *ptr,
                                        PropertyRNA *prop,
                                        int *values,
                                        int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_int_get_array(ptr, prop, values);
    return;
  }

  blender::Array<int, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_int_get_array(ptr, prop, value_buf.data());
  memcpy(values, value_buf.data(), sizeof(*values) * values_num);
}

void RNA_property_int_get_array_range(PointerRNA *ptr, PropertyRNA *prop, int values[2])
{
  const int array_len = RNA_property_array_length(ptr, prop);

  if (array_len <= 0) {
    values[0] = 0;
    values[1] = 0;
  }
  else if (array_len == 1) {
    RNA_property_int_get_array(ptr, prop, values);
    values[1] = values[0];
  }
  else {
    int arr_stack[32];
    int *arr;
    int i;

    if (array_len > 32) {
      arr = MEM_malloc_arrayN<int>(size_t(array_len), __func__);
    }
    else {
      arr = arr_stack;
    }

    RNA_property_int_get_array(ptr, prop, arr);
    values[0] = values[1] = arr[0];
    for (i = 1; i < array_len; i++) {
      values[0] = std::min(values[0], arr[i]);
      values[1] = std::max(values[1], arr[i]);
    }

    if (arr != arr_stack) {
      MEM_freeN(arr);
    }
  }
}

int RNA_property_int_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  int tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_int_get_array(ptr, prop, tmp);
    return tmp[index];
  }
  int *tmparray, value;

  tmparray = MEM_malloc_arrayN<int>(size_t(len), __func__);
  RNA_property_int_get_array(ptr, prop, tmparray);
  value = tmparray[index];
  MEM_freeN(tmparray);

  return value;
}

void RNA_property_int_set_array(PointerRNA *ptr, PropertyRNA *prop, const int *values)
{
  using namespace blender;
  IntPropertyRNA *iprop = (IntPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) ||
               (prop->flag & PROP_IDPROPERTY));
    if (prop->arraydimension == 0) {
      IDP_Int(idprop) = values[0];
    }
    else {
      memcpy(IDP_Array(idprop), values, sizeof(int) * idprop->len);
    }

    rna_idproperty_touch(idprop);
  }
  else if (prop->arraydimension == 0) {
    RNA_property_int_set(ptr, prop, values[0]);
  }
  else if (iprop->setarray) {
    iprop->setarray(ptr, values);
  }
  else if (iprop->setarray_ex) {
    iprop->setarray_ex(ptr, prop, values);
  }
  else if (prop->flag & PROP_EDITABLE) {
    // RNA_property_int_clamp_array(ptr, prop, &value); /* TODO. */
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      IDP_AddToGroup(group,
                     bke::idprop::create(prop->identifier,
                                         Span(values, prop->totarraylength),
                                         IDP_FLAG_STATIC_TYPE)
                         .release());
    }
  }
}
void RNA_property_int_set_array_at_most(PointerRNA *ptr,
                                        PropertyRNA *prop,
                                        const int *values,
                                        int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_int_set_array(ptr, prop, values);
    return;
  }

  blender::Array<int, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_int_get_array(ptr, prop, value_buf.data());
  memcpy(value_buf.data(), values, sizeof(*values) * values_num);
  RNA_property_int_set_array(ptr, prop, value_buf.data());
}

void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value)
{
  int tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_int_get_array(ptr, prop, tmp);
    tmp[index] = value;
    RNA_property_int_set_array(ptr, prop, tmp);
  }
  else {
    int *tmparray;

    tmparray = MEM_malloc_arrayN<int>(size_t(len), __func__);
    RNA_property_int_get_array(ptr, prop, tmparray);
    tmparray[index] = value;
    RNA_property_int_set_array(ptr, prop, tmparray);
    MEM_freeN(tmparray);
  }
}

int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
      return ui_data->default_value;
    }
  }
  if (iprop->get_default) {
    return iprop->get_default(ptr, prop);
  }

  return iprop->defaultvalue;
}

bool RNA_property_int_set_default(PropertyRNA *prop, int value)
{
  if (prop->magic == RNA_MAGIC) {
    return false;
  }

  IDProperty *idprop = (IDProperty *)prop;
  BLI_assert(idprop->type == IDP_INT);

  IDPropertyUIDataInt *ui_data = (IDPropertyUIDataInt *)IDP_ui_data_ensure(idprop);
  ui_data->default_value = value;
  return true;
}

void RNA_property_int_get_default_array(PointerRNA *ptr, PropertyRNA *prop, int *values)
{
  IntPropertyRNA *iprop = (IntPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if (prop->magic != RNA_MAGIC) {
    int length = rna_ensure_property_array_length(ptr, prop);

    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_ARRAY);
      BLI_assert(idprop->subtype == IDP_INT);
      const IDPropertyUIDataInt *ui_data = (const IDPropertyUIDataInt *)idprop->ui_data;
      if (ui_data->default_array) {
        rna_property_int_fill_default_array_values(ui_data->default_array,
                                                   ui_data->default_array_len,
                                                   ui_data->default_value,
                                                   length,
                                                   values);
      }
      else {
        rna_property_int_fill_default_array_values(
            nullptr, 0, ui_data->default_value, length, values);
      }
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = iprop->defaultvalue;
  }
  else {
    rna_property_int_get_default_array_values(ptr, iprop, values);
  }
}

int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  int tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_INT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_int_get_default_array(ptr, prop, tmp);
    return tmp[index];
  }
  int *tmparray, value;

  tmparray = MEM_malloc_arrayN<int>(size_t(len), __func__);
  RNA_property_int_get_default_array(ptr, prop, tmparray);
  value = tmparray[index];
  MEM_freeN(tmparray);

  return value;
}

float RNA_property_float_get(PointerRNA *ptr, PropertyRNA *prop)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) == false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    if (idprop->type == IDP_FLOAT) {
      return IDP_Float(idprop);
    }
    return float(IDP_Double(idprop));
  }
  if (fprop->get) {
    return fprop->get(ptr);
  }
  if (fprop->get_ex) {
    return fprop->get_ex(ptr, prop);
  }
  return fprop->defaultvalue;
}

void RNA_property_float_set(PointerRNA *ptr, PropertyRNA *prop, float value)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) == false);
  /* useful to check on bad values but set function should clamp */
  // BLI_assert(RNA_property_float_clamp(ptr, prop, &value) == 0);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    RNA_property_float_clamp(ptr, prop, &value);
    if (idprop->type == IDP_FLOAT) {
      IDP_Float(idprop) = value;
    }
    else {
      IDP_Double(idprop) = value;
    }

    rna_idproperty_touch(idprop);
  }
  else if (fprop->set) {
    fprop->set(ptr, value);
  }
  else if (fprop->set_ex) {
    fprop->set_ex(ptr, prop, value);
  }
  else if (prop->flag & PROP_EDITABLE) {
    RNA_property_float_clamp(ptr, prop, &value);
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      IDP_AddToGroup(
          group,
          blender::bke::idprop::create(prop->identifier, value, IDP_FLAG_STATIC_TYPE).release());
    }
  }
}

static void rna_property_float_fill_default_array_values(
    const float *defarr, int defarr_length, float defvalue, int out_length, float *r_values)
{
  if (defarr && defarr_length > 0) {
    defarr_length = std::min(defarr_length, out_length);
    memcpy(r_values, defarr, sizeof(float) * defarr_length);
  }
  else {
    defarr_length = 0;
  }

  for (int i = defarr_length; i < out_length; i++) {
    r_values[i] = defvalue;
  }
}

/**
 * The same logic as #rna_property_float_fill_default_array_values for a double array.
 */
static void rna_property_float_fill_default_array_values_double(const double *default_array,
                                                                const int default_array_len,
                                                                const double default_value,
                                                                const int out_length,
                                                                float *r_values)
{
  const int array_copy_len = std::min(out_length, default_array_len);

  for (int i = 0; i < array_copy_len; i++) {
    r_values[i] = float(default_array[i]);
  }

  for (int i = array_copy_len; i < out_length; i++) {
    r_values[i] = float(default_value);
  }
}

static void rna_property_float_get_default_array_values(PointerRNA *ptr,
                                                        FloatPropertyRNA *fprop,
                                                        float *r_values)
{
  if (ptr->data && fprop->get_default_array) {
    fprop->get_default_array(ptr, &fprop->property, r_values);
    return;
  }

  int length = fprop->property.totarraylength;
  int out_length = RNA_property_array_length(ptr, (PropertyRNA *)fprop);

  rna_property_float_fill_default_array_values(
      fprop->defaultarray, length, fprop->defaultvalue, out_length, r_values);
}

void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop;
  IDProperty *idprop;
  int i;

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) ||
               (prop->flag & PROP_IDPROPERTY));
    if (prop->arraydimension == 0) {
      values[0] = RNA_property_float_get(ptr, prop);
    }
    else if (idprop->subtype == IDP_FLOAT) {
      memcpy(values, IDP_Array(idprop), sizeof(float) * idprop->len);
    }
    else {
      for (i = 0; i < idprop->len; i++) {
        values[i] = float(((double *)IDP_Array(idprop))[i]);
      }
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = RNA_property_float_get(ptr, prop);
  }
  else if (fprop->getarray) {
    fprop->getarray(ptr, values);
  }
  else if (fprop->getarray_ex) {
    fprop->getarray_ex(ptr, prop, values);
  }
  else {
    rna_property_float_get_default_array_values(ptr, fprop, values);
  }
}
void RNA_property_float_get_array_at_most(PointerRNA *ptr,
                                          PropertyRNA *prop,
                                          float *values,
                                          int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_float_get_array(ptr, prop, values);
    return;
  }

  blender::Array<float, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_float_get_array(ptr, prop, value_buf.data());
  memcpy(values, value_buf.data(), sizeof(*values) * values_num);
}

void RNA_property_float_get_array_range(PointerRNA *ptr, PropertyRNA *prop, float values[2])
{
  const int array_len = RNA_property_array_length(ptr, prop);

  if (array_len <= 0) {
    values[0] = 0.0f;
    values[1] = 0.0f;
  }
  else if (array_len == 1) {
    RNA_property_float_get_array(ptr, prop, values);
    values[1] = values[0];
  }
  else {
    float arr_stack[32];
    float *arr;
    int i;

    if (array_len > 32) {
      arr = MEM_malloc_arrayN<float>(size_t(array_len), __func__);
    }
    else {
      arr = arr_stack;
    }

    RNA_property_float_get_array(ptr, prop, arr);
    values[0] = values[1] = arr[0];
    for (i = 1; i < array_len; i++) {
      values[0] = std::min(values[0], arr[i]);
      values[1] = std::max(values[1], arr[i]);
    }

    if (arr != arr_stack) {
      MEM_freeN(arr);
    }
  }
}

float RNA_property_float_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  float tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_float_get_array(ptr, prop, tmp);
    return tmp[index];
  }
  float *tmparray, value;

  tmparray = MEM_malloc_arrayN<float>(size_t(len), __func__);
  RNA_property_float_get_array(ptr, prop, tmparray);
  value = tmparray[index];
  MEM_freeN(tmparray);

  return value;
}

void RNA_property_float_set_array(PointerRNA *ptr, PropertyRNA *prop, const float *values)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop;
  IDProperty *idprop;
  int i;

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    BLI_assert(idprop->len == RNA_property_array_length(ptr, prop) ||
               (prop->flag & PROP_IDPROPERTY));
    if (prop->arraydimension == 0) {
      if (idprop->type == IDP_FLOAT) {
        IDP_Float(idprop) = values[0];
      }
      else {
        IDP_Double(idprop) = values[0];
      }
    }
    else if (idprop->subtype == IDP_FLOAT) {
      memcpy(IDP_Array(idprop), values, sizeof(float) * idprop->len);
    }
    else {
      for (i = 0; i < idprop->len; i++) {
        ((double *)IDP_Array(idprop))[i] = values[i];
      }
    }

    rna_idproperty_touch(idprop);
  }
  else if (prop->arraydimension == 0) {
    RNA_property_float_set(ptr, prop, values[0]);
  }
  else if (fprop->setarray) {
    fprop->setarray(ptr, values);
  }
  else if (fprop->setarray_ex) {
    fprop->setarray_ex(ptr, prop, values);
  }
  else if (prop->flag & PROP_EDITABLE) {
    // RNA_property_float_clamp_array(ptr, prop, &value); /* TODO. */
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      IDP_AddToGroup(group,
                     blender::bke::idprop::create(prop->identifier,
                                                  blender::Span(values, prop->totarraylength),
                                                  IDP_FLAG_STATIC_TYPE)
                         .release());
    }
  }
}
void RNA_property_float_set_array_at_most(PointerRNA *ptr,
                                          PropertyRNA *prop,
                                          const float *values,
                                          int values_num)
{
  BLI_assert(values_num >= 0);
  const int array_num = RNA_property_array_length(ptr, prop);
  if (values_num >= array_num) {
    RNA_property_float_set_array(ptr, prop, values);
    return;
  }

  blender::Array<float, RNA_STACK_ARRAY> value_buf(array_num);
  RNA_property_float_get_array(ptr, prop, value_buf.data());
  memcpy(value_buf.data(), values, sizeof(*values) * values_num);
  RNA_property_float_set_array(ptr, prop, value_buf.data());
}

void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value)
{
  float tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_float_get_array(ptr, prop, tmp);
    tmp[index] = value;
    RNA_property_float_set_array(ptr, prop, tmp);
  }
  else {
    float *tmparray;

    tmparray = MEM_malloc_arrayN<float>(size_t(len), __func__);
    RNA_property_float_get_array(ptr, prop, tmparray);
    tmparray[index] = value;
    RNA_property_float_set_array(ptr, prop, tmparray);
    MEM_freeN(tmparray);
  }
}

float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) == false);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(ELEM(idprop->type, IDP_FLOAT, IDP_DOUBLE));
      const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
      return float(ui_data->default_value);
    }
  }
  if (fprop->get_default) {
    return fprop->get_default(ptr, prop);
  }

  return fprop->defaultvalue;
}

bool RNA_property_float_set_default(PropertyRNA *prop, float value)
{
  if (prop->magic == RNA_MAGIC) {
    return false;
  }

  IDProperty *idprop = (IDProperty *)prop;
  BLI_assert(idprop->type == IDP_FLOAT);

  IDPropertyUIDataFloat *ui_data = (IDPropertyUIDataFloat *)IDP_ui_data_ensure(idprop);
  ui_data->default_value = double(value);
  return true;
}

void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
{
  FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);

  if (prop->magic != RNA_MAGIC) {
    int length = rna_ensure_property_array_length(ptr, prop);

    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_ARRAY);
      BLI_assert(ELEM(idprop->subtype, IDP_FLOAT, IDP_DOUBLE));
      const IDPropertyUIDataFloat *ui_data = (const IDPropertyUIDataFloat *)idprop->ui_data;
      rna_property_float_fill_default_array_values_double(ui_data->default_array,
                                                          ui_data->default_array_len,
                                                          ui_data->default_value,
                                                          length,
                                                          values);
    }
  }
  else if (prop->arraydimension == 0) {
    values[0] = fprop->defaultvalue;
  }
  else {
    rna_property_float_get_default_array_values(ptr, fprop, values);
  }
}

float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  float tmp[RNA_MAX_ARRAY_LENGTH];
  int len = rna_ensure_property_array_length(ptr, prop);

  BLI_assert(RNA_property_type(prop) == PROP_FLOAT);
  BLI_assert(RNA_property_array_check(prop) != false);
  BLI_assert(index >= 0);
  BLI_assert(index < len);

  if (len <= RNA_MAX_ARRAY_LENGTH) {
    RNA_property_float_get_default_array(ptr, prop, tmp);
    return tmp[index];
  }
  float *tmparray, value;

  tmparray = MEM_malloc_arrayN<float>(size_t(len), __func__);
  RNA_property_float_get_default_array(ptr, prop, tmparray);
  value = tmparray[index];
  MEM_freeN(tmparray);

  return value;
}

std::string RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop)
{
  StringPropertyRNA *sprop = reinterpret_cast<StringPropertyRNA *>(prop);
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    /* NOTE: `std::string` does support NULL char in its data. */
    return std::string{IDP_String(idprop), size_t(idprop->len)};
  }

  if (!sprop->get && !sprop->get_ex) {
    return std::string{sprop->defaultvalue};
  }

  size_t length = size_t(RNA_property_string_length(ptr, prop));
  std::string string_ret{};
  /* Note: after `resize()` the underlying buffer is actually at least `length +
   * 1` bytes long, because (since C++11) `std::string` guarantees a terminating
   * null byte, but that is not considered part of the length. */
  string_ret.resize(length);

  if (sprop->get) {
    sprop->get(ptr, string_ret.data());
  }
  else { /* if (sprop->get_ex) */
    sprop->get_ex(ptr, prop, string_ret.data());
  }

  return string_ret;
}

void RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop, char *value)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    /* editing bytes is not 100% supported
     * since they can contain NIL chars */
    if (idprop->subtype == IDP_STRING_SUB_BYTE) {
      memcpy(value, IDP_String(idprop), idprop->len);
      value[idprop->len] = '\0';
    }
    else {
      memcpy(value, IDP_String(idprop), idprop->len);
    }
  }
  else if (sprop->get) {
    sprop->get(ptr, value);
  }
  else if (sprop->get_ex) {
    sprop->get_ex(ptr, prop, value);
  }
  else {
    strcpy(value, sprop->defaultvalue);
  }
}

char *RNA_property_string_get_alloc(
    PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len)
{
  if (fixedbuf) {
    BLI_string_debug_size(fixedbuf, fixedlen);
  }

  char *buf;
  int length;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  length = RNA_property_string_length(ptr, prop);

  if (length + 1 < fixedlen) {
    buf = fixedbuf;
  }
  else {
    buf = MEM_malloc_arrayN<char>(size_t(length) + 1, __func__);
  }

#ifndef NDEBUG
  /* safety check to ensure the string is actually set */
  buf[length] = 255;
#endif

  RNA_property_string_get(ptr, prop, buf);

#ifndef NDEBUG
  BLI_assert(buf[length] == '\0');
#endif

  if (r_len) {
    *r_len = length;
  }

  return buf;
}

int RNA_property_string_length(PointerRNA *ptr, PropertyRNA *prop)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    if (idprop->subtype == IDP_STRING_SUB_BYTE) {
      return idprop->len;
    }
#ifndef NDEBUG
    /* these _must_ stay in sync */
    BLI_assert(strlen(IDP_String(idprop)) == idprop->len - 1);
#endif
    return idprop->len - 1;
  }
  if (sprop->length) {
    return sprop->length(ptr);
  }
  if (sprop->length_ex) {
    return sprop->length_ex(ptr, prop);
  }
  return strlen(sprop->defaultvalue);
}

void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    /* both IDP_STRING_SUB_BYTE / IDP_STRING_SUB_UTF8 */
    IDP_AssignStringMaxSize(idprop, value, RNA_property_string_maxlength(prop));
    rna_idproperty_touch(idprop);
  }
  else if (sprop->set) {
    sprop->set(ptr, value); /* set function needs to clamp itself */
  }
  else if (sprop->set_ex) {
    sprop->set_ex(ptr, prop, value); /* set function needs to clamp itself */
  }
  else if (prop->flag & PROP_EDITABLE) {
    IDProperty *group;

    group = RNA_struct_idprops(ptr, true);
    if (group) {
      IDP_AddToGroup(
          group,
          IDP_NewStringMaxSize(
              value, RNA_property_string_maxlength(prop), prop->identifier, IDP_FLAG_STATIC_TYPE));
    }
  }
}

void RNA_property_string_set_bytes(PointerRNA *ptr, PropertyRNA *prop, const char *value, int len)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);
  BLI_assert(RNA_property_subtype(prop) == PROP_BYTESTRING);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDP_ResizeArray(idprop, len);
    memcpy(idprop->data.pointer, value, size_t(len));

    rna_idproperty_touch(idprop);
  }
  else if (sprop->set) {
    /* XXX, should take length argument (currently not used). */
    sprop->set(ptr, value); /* set function needs to clamp itself */
  }
  else if (sprop->set_ex) {
    /* XXX, should take length argument (currently not used). */
    sprop->set_ex(ptr, prop, value); /* set function needs to clamp itself */
  }
  else if (prop->flag & PROP_EDITABLE) {
    IDProperty *group;

    group = RNA_struct_idprops(ptr, true);
    if (group) {
      IDPropertyTemplate val = {0};
      val.string.str = value;
      val.string.len = len;
      val.string.subtype = IDP_STRING_SUB_BYTE;
      IDP_AddToGroup(group, IDP_New(IDP_STRING, &val, prop->identifier, IDP_FLAG_STATIC_TYPE));
    }
  }
}

void RNA_property_string_get_default(PropertyRNA *prop, char *value, const int value_maxncpy)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_STRING);
      const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
      BLI_strncpy(value, ui_data->default_value, value_maxncpy);
      return;
    }

    strcpy(value, "");
    return;
  }

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  strcpy(value, sprop->defaultvalue);
}

char *RNA_property_string_get_default_alloc(
    PointerRNA *ptr, PropertyRNA *prop, char *fixedbuf, int fixedlen, int *r_len)
{
  char *buf;
  int length;

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  length = RNA_property_string_default_length(ptr, prop);

  if (length + 1 < fixedlen) {
    buf = fixedbuf;
  }
  else {
    buf = MEM_calloc_arrayN<char>(size_t(length) + 1, __func__);
  }

  RNA_property_string_get_default(prop, buf, length + 1);

  if (r_len) {
    *r_len = length;
  }

  return buf;
}

int RNA_property_string_default_length(PointerRNA * /*ptr*/, PropertyRNA *prop)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = (const IDProperty *)prop;
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_STRING);
      const IDPropertyUIDataString *ui_data = (const IDPropertyUIDataString *)idprop->ui_data;
      if (ui_data->default_value != nullptr) {
        return strlen(ui_data->default_value);
      }
    }

    return 0;
  }

  BLI_assert(RNA_property_type(prop) == PROP_STRING);

  return strlen(sprop->defaultvalue);
}

eStringPropertySearchFlag RNA_property_string_search_flag(PropertyRNA *prop)
{
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
  if (prop->magic != RNA_MAGIC) {
    return eStringPropertySearchFlag(0);
  }
  BLI_assert(RNA_property_type(prop) == PROP_STRING);
  if (sprop->search) {
    BLI_assert(sprop->search_flag & PROP_STRING_SEARCH_SUPPORTED);
  }
  else {
    BLI_assert(sprop->search_flag == 0);
  }
  return sprop->search_flag;
}

void RNA_property_string_search(
    const bContext *C,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const char *edit_text,
    blender::FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
  BLI_assert(RNA_property_string_search_flag(prop) & PROP_STRING_SEARCH_SUPPORTED);
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_ensure_property(prop);
  sprop->search(C, ptr, prop, edit_text, visit_fn);
}

std::optional<std::string> RNA_property_string_path_filter(const bContext *C,
                                                           PointerRNA *ptr,
                                                           PropertyRNA *prop)
{
  BLI_assert(RNA_property_type(prop) == PROP_STRING);
  PropertyRNA *rna_prop = rna_ensure_property(prop);
  StringPropertyRNA *sprop = (StringPropertyRNA *)rna_prop;
  if (!sprop->path_filter) {
    return std::nullopt;
  }
  return sprop->path_filter(C, ptr, rna_prop);
}

int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop)
{
  EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_ENUM);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    return IDP_Int(idprop);
  }
  if (eprop->get) {
    return eprop->get(ptr);
  }
  if (eprop->get_ex) {
    return eprop->get_ex(ptr, prop);
  }
  return eprop->defaultvalue;
}

void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value)
{
  EnumPropertyRNA *eprop = (EnumPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_ENUM);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDP_Int(idprop) = value;
    rna_idproperty_touch(idprop);
  }
  else if (eprop->set) {
    eprop->set(ptr, value);
  }
  else if (eprop->set_ex) {
    eprop->set_ex(ptr, prop, value);
  }
  else if (prop->flag & PROP_EDITABLE) {
    IDPropertyTemplate val = {0};
    IDProperty *group;

    val.i = value;

    group = RNA_struct_idprops(ptr, true);
    if (group) {
      IDP_AddToGroup(group, IDP_New(IDP_INT, &val, prop->identifier, IDP_FLAG_STATIC_TYPE));
    }
  }
}

int RNA_property_enum_get_default(PointerRNA *ptr, PropertyRNA *prop)
{
  EnumPropertyRNA *eprop = (EnumPropertyRNA *)rna_ensure_property(prop);
  BLI_assert(RNA_property_type(prop) == PROP_ENUM);

  if (prop->magic != RNA_MAGIC) {
    const IDProperty *idprop = reinterpret_cast<const IDProperty *>(prop);
    if (idprop->ui_data) {
      BLI_assert(idprop->type == IDP_INT);
      const IDPropertyUIDataInt *ui_data = reinterpret_cast<const IDPropertyUIDataInt *>(
          idprop->ui_data);
      return ui_data->default_value;
    }
  }
  if (eprop->get_default) {
    return eprop->get_default(ptr, prop);
  }

  return eprop->defaultvalue;
}

int RNA_property_enum_step(
    const bContext *C, PointerRNA *ptr, PropertyRNA *prop, int from_value, int step)
{
  const EnumPropertyItem *item_array;
  int totitem;
  bool free;
  int result_value = from_value;
  int i, i_init;
  int single_step = (step < 0) ? -1 : 1;
  int step_tot = 0;

  RNA_property_enum_items((bContext *)C, ptr, prop, &item_array, &totitem, &free);
  i = RNA_enum_from_value(item_array, from_value);
  i_init = i;

  do {
    i = mod_i(i + single_step, totitem);
    if (item_array[i].identifier[0]) {
      step_tot += single_step;
    }
  } while ((i != i_init) && (step_tot != step));

  if (i != i_init) {
    result_value = item_array[i].value;
  }

  if (free) {
    MEM_freeN(item_array);
  }

  return result_value;
}

PointerRNA RNA_property_pointer_get(PointerRNA *ptr, PropertyRNA *prop)
{
  PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;
  IDProperty *idprop;

  static blender::Mutex mutex;

  BLI_assert(RNA_property_type(prop) == PROP_POINTER);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    pprop = (PointerPropertyRNA *)prop;

    if (RNA_struct_is_ID(pprop->type)) {
      /* ID PointerRNA should not have ancestors currently. */
      return RNA_id_pointer_create(IDP_Id(idprop));
    }

    /* for groups, data is idprop itself */
    if (pprop->type_fn) {
      return RNA_pointer_create_with_parent(*ptr, pprop->type_fn(ptr), idprop);
    }
    return RNA_pointer_create_with_parent(*ptr, pprop->type, idprop);
  }
  if (pprop->get) {
    return pprop->get(ptr);
  }
  if (prop->flag & PROP_IDPROPERTY) {
    /* NOTE: While creating/writing data in an accessor is really bad design-wise, this is
     * currently very difficult to avoid in that case. So a global mutex is used to keep ensuring
     * thread safety. */
    std::scoped_lock lock(mutex);
    /* NOTE: We do not need to check again for existence of the pointer after locking here, since
     * this is also done in #RNA_property_pointer_add itself. */
    RNA_property_pointer_add(ptr, prop);
    return RNA_property_pointer_get(ptr, prop);
  }
  return PointerRNA_NULL;
}

void RNA_property_pointer_set(PointerRNA *ptr,
                              PropertyRNA *prop,
                              PointerRNA ptr_value,
                              ReportList *reports)
{
  /* Detect IDProperty and retrieve the actual PropertyRNA pointer before cast. */
  IDProperty *idprop = rna_idproperty_check(&prop, ptr);

  PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;
  BLI_assert(RNA_property_type(prop) == PROP_POINTER);

  /* This is a 'real' RNA property, not an IDProperty or a dynamic RNA property using an IDProperty
   * as backend storage. */
  if (pprop->set) {
    if (ptr_value.type != nullptr && !RNA_struct_is_a(ptr_value.type, pprop->type)) {
      BKE_reportf(reports,
                  RPT_ERROR,
                  "%s: expected %s type, not %s",
                  __func__,
                  pprop->type->identifier,
                  ptr_value.type->identifier);
      return;
    }
    if (!((prop->flag & PROP_NEVER_NULL) && ptr_value.data == nullptr) &&
        !((prop->flag & PROP_ID_SELF_CHECK) && ptr->owner_id == ptr_value.owner_id))
    {
      pprop->set(ptr, ptr_value, reports);
    }
    return;
  }

  /* Assigning to an IDProperty. */
  ID *value = static_cast<ID *>(ptr_value.data);

  if (ptr_value.type != nullptr && !RNA_struct_is_a(ptr_value.type, &RNA_ID)) {
    BKE_reportf(
        reports, RPT_ERROR, "%s: expected ID type, not %s", __func__, ptr_value.type->identifier);
    return;
  }
  if (value && (value->flag & ID_FLAG_EMBEDDED_DATA) != 0) {
    BKE_reportf(reports, RPT_ERROR, "%s: cannot assign an embedded ID to an IDProperty", __func__);
    return;
  }

  /* We got an existing IDProperty. */
  if (idprop != nullptr) {
    /* Not-yet-defined ID IDProps have an IDP_GROUP type, not an IDP_ID one - because of reasons?
     * XXX This has to be investigated fully - there might be a good reason for it, but off hands
     * this seems really weird... */
    if (idprop->type == IDP_ID) {
      IDP_AssignID(idprop, value, 0);
      rna_idproperty_touch(idprop);
    }
    else {
      BLI_assert(idprop->type == IDP_GROUP);
      IDProperty *group = RNA_struct_idprops(ptr, true);
      BLI_assert(group != nullptr);

      IDP_ReplaceInGroup_ex(
          group,
          blender::bke::idprop::create(idprop->name, value, IDP_FLAG_STATIC_TYPE).release(),
          idprop,
          0);
    }
  }
  /* IDProperty disguised as RNA property (and not yet defined in ptr). */
  else if (prop->flag & PROP_EDITABLE) {
    if (IDProperty *group = RNA_struct_idprops(ptr, true)) {
      IDP_ReplaceInGroup(
          group,
          blender::bke::idprop::create(prop->identifier, value, IDP_FLAG_STATIC_TYPE).release());
    }
  }
}

PointerRNA RNA_property_pointer_get_default(PointerRNA * /*ptr*/, PropertyRNA * /*prop*/)
{
  // PointerPropertyRNA *pprop = (PointerPropertyRNA *)prop;

  // BLI_assert(RNA_property_type(prop) == PROP_POINTER);

  return PointerRNA_NULL; /* FIXME: there has to be a way... */
}

void RNA_property_pointer_add(PointerRNA *ptr, PropertyRNA *prop)
{
  // IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_POINTER);

  if (/*idprop=*/rna_idproperty_check(&prop, ptr)) {
    /* already exists */
  }
  else if (prop->flag & PROP_IDPROPERTY) {
    IDProperty *group;

    group = RNA_struct_idprops(ptr, true);
    if (group) {
      IDP_AddToGroup(
          group,
          blender::bke::idprop::create_group(prop->identifier, IDP_FLAG_STATIC_TYPE).release());
    }
  }
  else {
    printf("%s %s.%s: only supported for id properties.\n",
           __func__,
           ptr->type->identifier,
           prop->identifier);
  }
}

void RNA_property_pointer_remove(PointerRNA *ptr, PropertyRNA *prop)
{
  IDProperty *idprop, *group;

  BLI_assert(RNA_property_type(prop) == PROP_POINTER);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    group = RNA_struct_idprops(ptr, false);

    if (group) {
      IDP_FreeFromGroup(group, idprop);
    }
  }
  else {
    printf("%s %s.%s: only supported for id properties.\n",
           __func__,
           ptr->type->identifier,
           prop->identifier);
  }
}

static void rna_property_collection_get_idp(CollectionPropertyIterator *iter)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)iter->prop;

  rna_pointer_create_with_ancestors(
      iter->parent, cprop->item_type, rna_iterator_array_get(iter), iter->ptr);
}

void RNA_property_collection_begin(PointerRNA *ptr,
                                   PropertyRNA *prop,
                                   CollectionPropertyIterator *iter)
{
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  *iter = {};

  if ((idprop = rna_idproperty_check(&prop, ptr)) || (prop->flag & PROP_IDPROPERTY)) {
    iter->parent = *ptr;
    iter->prop = prop;

    if (idprop) {
      rna_iterator_array_begin(
          iter, ptr, IDP_IDPArray(idprop), sizeof(IDProperty), idprop->len, false, nullptr);
    }
    else {
      rna_iterator_array_begin(iter, ptr, nullptr, sizeof(IDProperty), 0, false, nullptr);
    }

    if (iter->valid) {
      rna_property_collection_get_idp(iter);
    }

    iter->idprop = 1;
  }
  else {
    CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;
    cprop->begin(iter, ptr);
  }
}

void RNA_property_collection_next(CollectionPropertyIterator *iter)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(iter->prop);

  if (iter->idprop) {
    rna_iterator_array_next(iter);

    if (iter->valid) {
      rna_property_collection_get_idp(iter);
    }
  }
  else {
    cprop->next(iter);
  }
}

void RNA_property_collection_skip(CollectionPropertyIterator *iter, int num)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(iter->prop);
  int i;

  if (num > 1 && (iter->idprop || (cprop->property.flag_internal & PROP_INTERN_RAW_ARRAY))) {
    /* fast skip for array */
    ArrayIterator *internal = &iter->internal.array;

    if (!internal->skip) {
      internal->ptr += internal->itemsize * (num - 1);
      iter->valid = (internal->ptr < internal->endptr);
      if (iter->valid) {
        RNA_property_collection_next(iter);
      }
      return;
    }
  }

  /* slow iteration otherwise */
  for (i = 0; i < num && iter->valid; i++) {
    RNA_property_collection_next(iter);
  }
}

void RNA_property_collection_end(CollectionPropertyIterator *iter)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(iter->prop);

  if (iter->idprop) {
    rna_iterator_array_end(iter);
  }
  else {
    cprop->end(iter);
  }
}

int RNA_property_collection_length(PointerRNA *ptr, PropertyRNA *prop)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    return idprop->len;
  }
  if (cprop->length) {
    return cprop->length(ptr);
  }
  CollectionPropertyIterator iter;
  int length = 0;

  RNA_property_collection_begin(ptr, prop, &iter);
  for (; iter.valid; RNA_property_collection_next(&iter)) {
    length++;
  }
  RNA_property_collection_end(&iter);

  return length;
}

bool RNA_property_collection_is_empty(PointerRNA *ptr, PropertyRNA *prop)
{
  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);
  CollectionPropertyIterator iter;
  RNA_property_collection_begin(ptr, prop, &iter);
  bool test = iter.valid;
  RNA_property_collection_end(&iter);
  return !test;
}

/* This helper checks whether given collection property itself is editable (we only currently
 * support a limited set of operations, insertion of new items, and re-ordering of those new items
 * exclusively). */
static bool property_collection_liboverride_editable(PointerRNA *ptr,
                                                     PropertyRNA *prop,
                                                     bool *r_is_liboverride)
{
  ID *id = ptr->owner_id;
  if (id == nullptr) {
    *r_is_liboverride = false;
    return true;
  }

  const bool is_liboverride = *r_is_liboverride = ID_IS_OVERRIDE_LIBRARY(id);

  if (!is_liboverride) {
    /* We return True also for linked data, as it allows tricks like py scripts 'overriding' data
     * of those. */
    return true;
  }

  if (!RNA_property_overridable_get(ptr, prop)) {
    return false;
  }

  if (prop->magic != RNA_MAGIC || (prop->flag & PROP_IDPROPERTY) == 0) {
    /* Insertion and such not supported for pure IDProperties for now, nor for pure RNA/DNA ones.
     */
    return false;
  }
  if ((prop->flag_override & PROPOVERRIDE_LIBRARY_INSERTION) == 0) {
    return false;
  }

  /* No more checks to do, this collections is overridable. */
  return true;
}

void RNA_property_collection_add(PointerRNA *ptr, PropertyRNA *prop, PointerRNA *r_ptr)
{
  IDProperty *idprop;
  // CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  bool is_liboverride;
  if (!property_collection_liboverride_editable(ptr, prop, &is_liboverride)) {
    if (r_ptr) {
      *r_ptr = {};
    }
    return;
  }

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDProperty *item;

    item = blender::bke::idprop::create_group("", IDP_FLAG_STATIC_TYPE).release();
    if (is_liboverride) {
      item->flag |= IDP_FLAG_OVERRIDELIBRARY_LOCAL;
    }
    IDP_AppendArray(idprop, item);
    /* IDP_AppendArray does a shallow copy (memcpy), only free memory. */
    // IDP_FreePropertyContent(item);
    MEM_freeN(item);
    rna_idproperty_touch(idprop);
  }
  else if (prop->flag & PROP_IDPROPERTY) {
    IDProperty *group, *item;

    group = RNA_struct_idprops(ptr, true);
    if (group) {
      idprop = IDP_NewIDPArray(prop->identifier);
      IDP_AddToGroup(group, idprop);

      item = blender::bke::idprop::create_group("", IDP_FLAG_STATIC_TYPE).release();
      if (is_liboverride) {
        item->flag |= IDP_FLAG_OVERRIDELIBRARY_LOCAL;
      }
      IDP_AppendArray(idprop, item);
      /* #IDP_AppendArray does a shallow copy (memcpy), only free memory. */
      // IDP_FreePropertyContent(item);
      MEM_freeN(item);
    }
  }

  if (r_ptr) {
    if (idprop) {
      CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;

      rna_pointer_create_with_ancestors(
          *ptr, cprop->item_type, IDP_GetIndexArray(idprop, idprop->len - 1), *r_ptr);
    }
    else {
      *r_ptr = {};
    }
  }
}

bool RNA_property_collection_remove(PointerRNA *ptr, PropertyRNA *prop, int key)
{
  IDProperty *idprop;
  // CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)prop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  bool is_liboverride;
  if (!property_collection_liboverride_editable(ptr, prop, &is_liboverride)) {
    return false;
  }

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDProperty tmp, *array;
    int len;

    len = idprop->len;
    array = IDP_IDPArray(idprop);

    if (key >= 0 && key < len) {
      if (is_liboverride && (array[key].flag & IDP_FLAG_OVERRIDELIBRARY_LOCAL) == 0) {
        /* We can only remove items that we actually inserted in the local override. */
        return false;
      }

      if (key + 1 < len) {
        /* move element to be removed to the back */
        memcpy(&tmp, &array[key], sizeof(IDProperty));
        memmove(array + key, array + key + 1, sizeof(IDProperty) * (len - (key + 1)));
        memcpy(&array[len - 1], &tmp, sizeof(IDProperty));
      }

      IDP_ResizeIDPArray(idprop, len - 1);
    }

    return true;
  }
  if (prop->flag & PROP_IDPROPERTY) {
    return true;
  }

  return false;
}

bool RNA_property_collection_move(PointerRNA *ptr, PropertyRNA *prop, int key, int pos)
{
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  bool is_liboverride;
  if (!property_collection_liboverride_editable(ptr, prop, &is_liboverride)) {
    return false;
  }

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    IDProperty tmp, *array;
    int len;

    len = idprop->len;
    array = IDP_IDPArray(idprop);

    if (key >= 0 && key < len && pos >= 0 && pos < len && key != pos) {
      if (is_liboverride && (array[key].flag & IDP_FLAG_OVERRIDELIBRARY_LOCAL) == 0) {
        /* We can only move items that we actually inserted in the local override. */
        return false;
      }

      memcpy(&tmp, &array[key], sizeof(IDProperty));
      if (pos < key) {
        memmove(array + pos + 1, array + pos, sizeof(IDProperty) * (key - pos));
      }
      else {
        memmove(array + key, array + key + 1, sizeof(IDProperty) * (pos - key));
      }
      memcpy(&array[pos], &tmp, sizeof(IDProperty));
    }

    return true;
  }
  if (prop->flag & PROP_IDPROPERTY) {
    return true;
  }

  return false;
}

void RNA_property_collection_clear(PointerRNA *ptr, PropertyRNA *prop)
{
  IDProperty *idprop;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  bool is_liboverride;
  if (!property_collection_liboverride_editable(ptr, prop, &is_liboverride)) {
    return;
  }

  if ((idprop = rna_idproperty_check(&prop, ptr))) {
    if (is_liboverride) {
      /* We can only move items that we actually inserted in the local override. */
      int len = idprop->len;
      IDProperty tmp, *array = IDP_IDPArray(idprop);
      for (int i = 0; i < len; i++) {
        if ((array[i].flag & IDP_FLAG_OVERRIDELIBRARY_LOCAL) != 0) {
          memcpy(&tmp, &array[i], sizeof(IDProperty));
          memmove(array + i, array + i + 1, sizeof(IDProperty) * (len - (i + 1)));
          memcpy(&array[len - 1], &tmp, sizeof(IDProperty));
          IDP_ResizeIDPArray(idprop, --len);
          i--;
        }
      }
    }
    else {
      IDP_ResizeIDPArray(idprop, 0);
    }
    rna_idproperty_touch(idprop);
  }
}

int RNA_property_collection_lookup_index(PointerRNA *ptr,
                                         PropertyRNA *prop,
                                         const PointerRNA *t_ptr)
{
  CollectionPropertyIterator iter;
  int index = 0;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  RNA_property_collection_begin(ptr, prop, &iter);
  for (index = 0; iter.valid; RNA_property_collection_next(&iter), index++) {
    if (iter.ptr.data == t_ptr->data) {
      break;
    }
  }
  RNA_property_collection_end(&iter);

  /* did we find it? */
  if (iter.valid) {
    return index;
  }
  return -1;
}

bool RNA_property_collection_lookup_int_has_fn(PropertyRNA *prop)
{
  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);
  return cprop->lookupint != nullptr;
}

bool RNA_property_collection_lookup_string_has_fn(PropertyRNA *prop)
{
  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);
  return cprop->lookupstring != nullptr;
}

bool RNA_property_collection_lookup_string_has_nameprop(PropertyRNA *prop)
{
  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);
  return (cprop->item_type && cprop->item_type->nameproperty);
}

bool RNA_property_collection_lookup_string_supported(PropertyRNA *prop)
{
  return (RNA_property_collection_lookup_string_has_fn(prop) ||
          RNA_property_collection_lookup_string_has_nameprop(prop));
}

bool RNA_property_collection_lookup_int(PointerRNA *ptr,
                                        PropertyRNA *prop,
                                        int key,
                                        PointerRNA *r_ptr)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  if (cprop->lookupint) {
    /* we have a callback defined, use it */
    return cprop->lookupint(ptr, key, r_ptr);
  }
  /* no callback defined, just iterate and find the nth item */
  CollectionPropertyIterator iter;
  int i;

  RNA_property_collection_begin(ptr, prop, &iter);
  for (i = 0; iter.valid; RNA_property_collection_next(&iter), i++) {
    if (i == key) {
      *r_ptr = iter.ptr;
      break;
    }
  }
  RNA_property_collection_end(&iter);

  if (!iter.valid) {
    *r_ptr = {};
  }

  return iter.valid;
}

bool RNA_property_collection_lookup_string_index(
    PointerRNA *ptr, PropertyRNA *prop, const char *key, PointerRNA *r_ptr, int *r_index)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  if (!key) {
    *r_index = -1;
    *r_ptr = PointerRNA_NULL;
    return false;
  }

  if (cprop->lookupstring) {
    /* we have a callback defined, use it */
    return cprop->lookupstring(ptr, key, r_ptr);
  }
  /* no callback defined, compare with name properties if they exist */
  CollectionPropertyIterator iter;
  PropertyRNA *nameprop;
  char name_buf[256], *name;
  bool found = false;
  int keylen = strlen(key);
  int namelen;
  int index = 0;

  RNA_property_collection_begin(ptr, prop, &iter);
  for (; iter.valid; RNA_property_collection_next(&iter), index++) {
    if (iter.ptr.data && iter.ptr.type->nameproperty) {
      nameprop = iter.ptr.type->nameproperty;

      name = RNA_property_string_get_alloc(
          &iter.ptr, nameprop, name_buf, sizeof(name_buf), &namelen);

      if ((keylen == namelen) && STREQ(name, key)) {
        *r_ptr = iter.ptr;
        found = true;
      }

      if (name != name_buf) {
        MEM_freeN(name);
      }

      if (found) {
        break;
      }
    }
  }
  RNA_property_collection_end(&iter);

  if (!iter.valid) {
    *r_ptr = {};
    *r_index = -1;
  }
  else {
    *r_index = index;
  }

  return iter.valid;
}

bool RNA_property_collection_lookup_string(PointerRNA *ptr,
                                           PropertyRNA *prop,
                                           const char *key,
                                           PointerRNA *r_ptr)
{
  int index;
  return RNA_property_collection_lookup_string_index(ptr, prop, key, r_ptr, &index);
}

bool RNA_property_collection_assign_int(PointerRNA *ptr,
                                        PropertyRNA *prop,
                                        const int key,
                                        const PointerRNA *assign_ptr)
{
  CollectionPropertyRNA *cprop = (CollectionPropertyRNA *)rna_ensure_property(prop);

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  if (cprop->assignint) {
    /* we have a callback defined, use it */
    return cprop->assignint(ptr, key, assign_ptr);
  }

  return false;
}

bool RNA_property_collection_type_get(PointerRNA *ptr, PropertyRNA *prop, PointerRNA *r_ptr)
{
  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  *r_ptr = *ptr;
  return ((r_ptr->type = rna_ensure_property(prop)->srna) ? 1 : 0);
}

int RNA_property_collection_raw_array(
    PointerRNA *ptr, PropertyRNA *prop, PropertyRNA *itemprop, bool set, RawArray *array)
{
  CollectionPropertyIterator iter;
  ArrayIterator *internal;
  char *arrayp;

  BLI_assert(RNA_property_type(prop) == PROP_COLLECTION);

  if (!(prop->flag_internal & PROP_INTERN_RAW_ARRAY) ||
      !(itemprop->flag_internal & PROP_INTERN_RAW_ACCESS))
  {
    return 0;
  }

  RNA_property_collection_begin(ptr, prop, &iter);

  if (iter.valid) {
    /* get data from array iterator and item property */
    internal = &iter.internal.array;
    arrayp = (iter.valid) ? static_cast<char *>(iter.ptr.data) : nullptr;

    if (internal->skip || (set && !RNA_property_editable(&iter.ptr, itemprop))) {
      /* we might skip some items, so it's not a proper array */
      RNA_property_collection_end(&iter);
      return 0;
    }

    array->array = arrayp + itemprop->rawoffset;
    array->stride = internal->itemsize;
    array->len = ((char *)internal->endptr - arrayp) / internal->itemsize;
    array->type = itemprop->rawtype;
  }
  else {
    memset(array, 0, sizeof(RawArray));
  }

  RNA_property_collection_end(&iter);

  return 1;
}

#define RAW_GET(dtype, var, raw, a) \
  { \
    switch (raw.type) { \
      case PROP_RAW_CHAR: \
        var = (dtype)((char *)raw.array)[a]; \
        break; \
      case PROP_RAW_INT8: \
        var = (dtype)((int8_t *)raw.array)[a]; \
        break; \
      case PROP_RAW_UINT8: \
        var = (dtype)((uint8_t *)raw.array)[a]; \
        break; \
      case PROP_RAW_SHORT: \
        var = (dtype)((short *)raw.array)[a]; \
        break; \
      case PROP_RAW_UINT16: \
        var = (dtype)((uint16_t *)raw.array)[a]; \
        break; \
      case PROP_RAW_INT: \
        var = (dtype)((int *)raw.array)[a]; \
        break; \
      case PROP_RAW_BOOLEAN: \
        var = (dtype)((bool *)raw.array)[a]; \
        break; \
      case PROP_RAW_FLOAT: \
        var = (dtype)((float *)raw.array)[a]; \
        break; \
      case PROP_RAW_DOUBLE: \
        var = (dtype)((double *)raw.array)[a]; \
        break; \
      case PROP_RAW_INT64: \
        var = (dtype)((int64_t *)raw.array)[a]; \
        break; \
      case PROP_RAW_UINT64: \
        var = (dtype)((uint64_t *)raw.array)[a]; \
        break; \
      default: \
        var = (dtype)0; \
    } \
  } \
  (void)0

#define RAW_SET(dtype, raw, a, var) \
  { \
    switch (raw.type) { \
      case PROP_RAW_CHAR: \
        ((char *)raw.array)[a] = char(var); \
        break; \
      case PROP_RAW_INT8: \
        ((int8_t *)raw.array)[a] = int8_t(var); \
        break; \
      case PROP_RAW_UINT8: \
        ((uint8_t *)raw.array)[a] = uint8_t(var); \
        break; \
      case PROP_RAW_SHORT: \
        ((short *)raw.array)[a] = short(var); \
        break; \
      case PROP_RAW_UINT16: \
        ((uint16_t *)raw.array)[a] = uint16_t(var); \
        break; \
      case PROP_RAW_INT: \
        ((int *)raw.array)[a] = int(var); \
        break; \
      case PROP_RAW_BOOLEAN: \
        ((bool *)raw.array)[a] = bool(var); \
        break; \
      case PROP_RAW_FLOAT: \
        ((float *)raw.array)[a] = float(var); \
        break; \
      case PROP_RAW_DOUBLE: \
        ((double *)raw.array)[a] = double(var); \
        break; \
      case PROP_RAW_INT64: \
        ((int64_t *)raw.array)[a] = int64_t(var); \
        break; \
      case PROP_RAW_UINT64: \
        ((uint64_t *)raw.array)[a] = uint64_t(var); \
        break; \
      default: \
        break; \
    } \
  } \
  (void)0

size_t RNA_raw_type_sizeof(RawPropertyType type)
{
  switch (type) {
    case PROP_RAW_CHAR:
      return sizeof(char);
    case PROP_RAW_INT8:
      return sizeof(int8_t);
    case PROP_RAW_UINT8:
      return sizeof(uint8_t);
    case PROP_RAW_SHORT:
      return sizeof(short);
    case PROP_RAW_UINT16:
      return sizeof(uint16_t);
    case PROP_RAW_INT:
      return sizeof(int);
    case PROP_RAW_BOOLEAN:
      return sizeof(bool);
    case PROP_RAW_FLOAT:
      return sizeof(float);
    case PROP_RAW_DOUBLE:
      return sizeof(double);
    case PROP_RAW_INT64:
      return sizeof(int64_t);
    case PROP_RAW_UINT64:
      return sizeof(uint64_t);
    default:
      return 0;
  }
}

static int rna_property_array_length_all_dimensions(PointerRNA *ptr, PropertyRNA *prop)
{
  int i, len[RNA_MAX_ARRAY_DIMENSION];
  const int dim = RNA_property_array_dimension(ptr, prop, len);
  int size;

  if (dim == 0) {
    return 0;
  }

  for (size = 1, i = 0; i < dim; i++) {
    size *= len[i];
  }

  return size;
}

static int rna_raw_access(ReportList *reports,
                          PointerRNA *ptr,
                          PropertyRNA *prop,
                          const char *propname,
                          void *inarray,
                          RawPropertyType intype,
                          int inlen,
                          int set)
{
  StructRNA *ptype;
  PropertyRNA *itemprop, *iprop;
  PropertyType itemtype = PropertyType(0);
  RawArray in;
  int itemlen = 0;

  /* initialize in array, stride assumed 0 in following code */
  in.array = inarray;
  in.type = intype;
  in.len = inlen;
  in.stride = 0;

  ptype = RNA_property_pointer_type(ptr, prop);

  /* try to get item property pointer */
  PointerRNA itemptr_base = RNA_pointer_create_discrete(nullptr, ptype, nullptr);
  itemprop = RNA_struct_find_property(&itemptr_base, propname);

  if (itemprop) {
    /* we have item property pointer */
    RawArray out;

    /* check type */
    itemtype = RNA_property_type(itemprop);

    if (!ELEM(itemtype, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
      BKE_report(reports, RPT_ERROR, "Only boolean, int, float, and enum properties supported");
      return 0;
    }

    /* check item array */
    itemlen = RNA_property_array_length(&itemptr_base, itemprop);

    /* dynamic array? need to get length per item */
    if (itemprop->getlength) {
      itemprop = nullptr;
    }
    /* try to access as raw array */
    else if (RNA_property_collection_raw_array(ptr, prop, itemprop, set, &out)) {
      int arraylen = (itemlen == 0) ? 1 : itemlen;
      if (in.len != arraylen * out.len) {
        BKE_reportf(reports,
                    RPT_ERROR,
                    "Array length mismatch (expected %d, got %d)",
                    out.len * arraylen,
                    in.len);
        return 0;
      }

      /* matching raw types */
      if (out.type == in.type) {
        void *inp = in.array;
        void *outp = out.array;
        size_t size;

        size = RNA_raw_type_sizeof(out.type) * arraylen;

        if (size == out.stride) {
          /* The property is stored contiguously so the entire array can be copied at once. */
          if (set) {
            memcpy(outp, inp, size * out.len);
          }
          else {
            memcpy(inp, outp, size * out.len);
          }
        }
        else {
          for (int a = 0; a < out.len; a++) {
            if (set) {
              memcpy(outp, inp, size);
            }
            else {
              memcpy(inp, outp, size);
            }

            inp = (char *)inp + size;
            outp = (char *)outp + out.stride;
          }
        }

        return 1;
      }

      /* Could also be faster with non-matching types,
       * for now we just do slower loop. */
    }
    BLI_assert_msg(itemlen == 0 || itemtype != PROP_ENUM,
                   "Enum array properties should not exist");
  }

  {
    void *tmparray = nullptr;
    int tmplen = 0;
    int err = 0, j, a = 0;
    int needconv = 1;

    if (((itemtype == PROP_INT) && (in.type == PROP_RAW_INT)) ||
        ((itemtype == PROP_BOOLEAN) && (in.type == PROP_RAW_BOOLEAN)) ||
        ((itemtype == PROP_FLOAT) && (in.type == PROP_RAW_FLOAT)))
    {
      /* avoid creating temporary buffer if the data type match */
      needconv = 0;
    }
    /* no item property pointer, can still be id property, or
     * property of a type derived from the collection pointer type */
    RNA_PROP_BEGIN (ptr, itemptr, prop) {
      if (itemptr.data) {
        if (itemprop) {
          /* we got the property already */
          iprop = itemprop;
        }
        else {
          /* not yet, look it up and verify if it is valid */
          iprop = RNA_struct_find_property(&itemptr, propname);

          if (iprop) {
            itemlen = rna_property_array_length_all_dimensions(&itemptr, iprop);
            itemtype = RNA_property_type(iprop);
          }
          else {
            BKE_reportf(reports, RPT_ERROR, "Property named '%s' not found", propname);
            err = 1;
            break;
          }

          if (!ELEM(itemtype, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
            BKE_report(
                reports, RPT_ERROR, "Only boolean, int, float and enum properties supported");
            err = 1;
            break;
          }
          BLI_assert_msg(itemlen == 0 || itemtype != PROP_ENUM,
                         "Enum array properties should not exist");
        }

        /* editable check */
        if (!set || RNA_property_editable(&itemptr, iprop)) {
          if (a + itemlen > in.len) {
            BKE_reportf(
                reports, RPT_ERROR, "Array length mismatch (got %d, expected more)", in.len);
            err = 1;
            break;
          }

          if (itemlen == 0) {
            /* handle conversions */
            if (set) {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  int b;
                  RAW_GET(bool, b, in, a);
                  RNA_property_boolean_set(&itemptr, iprop, b);
                  break;
                }
                case PROP_INT: {
                  int i;
                  RAW_GET(int, i, in, a);
                  RNA_property_int_set(&itemptr, iprop, i);
                  break;
                }
                case PROP_FLOAT: {
                  float f;
                  RAW_GET(float, f, in, a);
                  RNA_property_float_set(&itemptr, iprop, f);
                  break;
                }
                case PROP_ENUM: {
                  int i;
                  RAW_GET(int, i, in, a);
                  RNA_property_enum_set(&itemptr, iprop, i);
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
            else {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  int b = RNA_property_boolean_get(&itemptr, iprop);
                  RAW_SET(bool, in, a, b);
                  break;
                }
                case PROP_INT: {
                  int i = RNA_property_int_get(&itemptr, iprop);
                  RAW_SET(int, in, a, i);
                  break;
                }
                case PROP_FLOAT: {
                  float f = RNA_property_float_get(&itemptr, iprop);
                  RAW_SET(float, in, a, f);
                  break;
                }
                case PROP_ENUM: {
                  int i = RNA_property_enum_get(&itemptr, iprop);
                  RAW_SET(int, in, a, i);
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
            a++;
          }
          else if (needconv == 1) {
            /* allocate temporary array if needed */
            if (tmparray && tmplen != itemlen) {
              MEM_freeN(tmparray);
              tmparray = nullptr;
            }
            if (!tmparray) {
              tmparray = MEM_calloc_arrayN<float>(itemlen, "RNA tmparray");
              tmplen = itemlen;
            }

            /* handle conversions */
            if (set) {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  bool *array = static_cast<bool *>(tmparray);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_GET(bool, array[j], in, a);
                  }
                  RNA_property_boolean_set_array(&itemptr, iprop, array);
                  break;
                }
                case PROP_INT: {
                  int *array = static_cast<int *>(tmparray);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_GET(int, array[j], in, a);
                  }
                  RNA_property_int_set_array(&itemptr, iprop, array);
                  break;
                }
                case PROP_FLOAT: {
                  float *array = static_cast<float *>(tmparray);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_GET(float, array[j], in, a);
                  }
                  RNA_property_float_set_array(&itemptr, iprop, array);
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
            else {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  bool *array = static_cast<bool *>(tmparray);
                  RNA_property_boolean_get_array(&itemptr, iprop, array);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_SET(int, in, a, ((bool *)tmparray)[j]);
                  }
                  break;
                }
                case PROP_INT: {
                  int *array = static_cast<int *>(tmparray);
                  RNA_property_int_get_array(&itemptr, iprop, array);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_SET(int, in, a, array[j]);
                  }
                  break;
                }
                case PROP_FLOAT: {
                  float *array = static_cast<float *>(tmparray);
                  RNA_property_float_get_array(&itemptr, iprop, array);
                  for (j = 0; j < itemlen; j++, a++) {
                    RAW_SET(float, in, a, array[j]);
                  }
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
          }
          else {
            if (set) {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  RNA_property_boolean_set_array(&itemptr, iprop, &((bool *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                case PROP_INT: {
                  RNA_property_int_set_array(&itemptr, iprop, &((int *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                case PROP_FLOAT: {
                  RNA_property_float_set_array(&itemptr, iprop, &((float *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
            else {
              switch (itemtype) {
                case PROP_BOOLEAN: {
                  RNA_property_boolean_get_array(&itemptr, iprop, &((bool *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                case PROP_INT: {
                  RNA_property_int_get_array(&itemptr, iprop, &((int *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                case PROP_FLOAT: {
                  RNA_property_float_get_array(&itemptr, iprop, &((float *)in.array)[a]);
                  a += itemlen;
                  break;
                }
                default:
                  BLI_assert_unreachable();
                  break;
              }
            }
          }
        }
      }
    }
    RNA_PROP_END;

    if (tmparray) {
      MEM_freeN(tmparray);
    }

    return !err;
  }
}

RawPropertyType RNA_property_raw_type(PropertyRNA *prop)
{
  if (prop->rawtype == PROP_RAW_UNSET) {
    /* this property has no raw access,
     * yet we try to provide a raw type to help building the array. */
    switch (prop->type) {
      case PROP_BOOLEAN:
        return PROP_RAW_BOOLEAN;
      case PROP_INT:
        return PROP_RAW_INT;
      case PROP_FLOAT:
        return PROP_RAW_FLOAT;
      case PROP_ENUM:
        return PROP_RAW_INT;
      default:
        break;
    }
  }
  return prop->rawtype;
}

int RNA_property_collection_raw_get(ReportList *reports,
                                    PointerRNA *ptr,
                                    PropertyRNA *prop,
                                    const char *propname,
                                    void *array,
                                    RawPropertyType type,
                                    int len)
{
  return rna_raw_access(reports, ptr, prop, propname, array, type, len, 0);
}

int RNA_property_collection_raw_set(ReportList *reports,
                                    PointerRNA *ptr,
                                    PropertyRNA *prop,
                                    const char *propname,
                                    void *array,
                                    RawPropertyType type,
                                    int len)
{
  return rna_raw_access(reports, ptr, prop, propname, array, type, len, 1);
}

/* Standard iterator functions */

void rna_iterator_listbase_begin(CollectionPropertyIterator *iter,
                                 PointerRNA *ptr,
                                 ListBase *lb,
                                 IteratorSkipFunc skip)
{
  iter->parent = *ptr;

  ListBaseIterator *internal = &iter->internal.listbase;

  internal->link = (lb) ? static_cast<Link *>(lb->first) : nullptr;
  internal->skip = skip;

  iter->valid = (internal->link != nullptr);

  if (skip && iter->valid && skip(iter, internal->link)) {
    rna_iterator_listbase_next(iter);
  }
}

void rna_iterator_listbase_next(CollectionPropertyIterator *iter)
{
  ListBaseIterator *internal = &iter->internal.listbase;

  if (internal->skip) {
    do {
      internal->link = internal->link->next;
      iter->valid = (internal->link != nullptr);
    } while (iter->valid && internal->skip(iter, internal->link));
  }
  else {
    internal->link = internal->link->next;
    iter->valid = (internal->link != nullptr);
  }
}

void *rna_iterator_listbase_get(CollectionPropertyIterator *iter)
{
  ListBaseIterator *internal = &iter->internal.listbase;

  return internal->link;
}

void rna_iterator_listbase_end(CollectionPropertyIterator * /*iter*/) {}

PointerRNA rna_listbase_lookup_int(PointerRNA *ptr, StructRNA *type, ListBase *lb, int index)
{
  void *data = BLI_findlink(lb, index);
  return RNA_pointer_create_with_parent(*ptr, type, data);
}

void rna_iterator_array_begin(CollectionPropertyIterator *iter,
                              PointerRNA *ptr,
                              void *data,
                              int itemsize,
                              int length,
                              bool free_ptr,
                              IteratorSkipFunc skip)
{
  iter->parent = *ptr;

  ArrayIterator *internal;

  if (data == nullptr) {
    length = 0;
  }
  else if (length == 0) {
    data = nullptr;
    itemsize = 0;
  }

  internal = &iter->internal.array;
  internal->ptr = static_cast<char *>(data);
  internal->free_ptr = free_ptr ? data : nullptr;
  internal->endptr = ((char *)data) + length * itemsize;
  internal->itemsize = itemsize;
  internal->skip = skip;
  internal->length = length;

  iter->valid = (internal->ptr != internal->endptr);

  if (skip && iter->valid && skip(iter, internal->ptr)) {
    rna_iterator_array_next(iter);
  }
}

void rna_iterator_array_next(CollectionPropertyIterator *iter)
{
  ArrayIterator *internal = &iter->internal.array;

  if (internal->skip) {
    do {
      internal->ptr += internal->itemsize;
      iter->valid = (internal->ptr != internal->endptr);
    } while (iter->valid && internal->skip(iter, internal->ptr));
  }
  else {
    internal->ptr += internal->itemsize;
    iter->valid = (internal->ptr != internal->endptr);
  }
}

void *rna_iterator_array_get(CollectionPropertyIterator *iter)
{
  ArrayIterator *internal = &iter->internal.array;

  return internal->ptr;
}

void *rna_iterator_array_dereference_get(CollectionPropertyIterator *iter)
{
  ArrayIterator *internal = &iter->internal.array;

  /* for ** arrays */
  return *(void **)(internal->ptr);
}

void rna_iterator_array_end(CollectionPropertyIterator *iter)
{
  ArrayIterator *internal = &iter->internal.array;

  MEM_SAFE_FREE(internal->free_ptr);
}

PointerRNA rna_array_lookup_int(
    PointerRNA *ptr, StructRNA *type, void *data, int itemsize, int length, int index)
{
  if (index < 0 || index >= length) {
    return PointerRNA_NULL;
  }

  return RNA_pointer_create_with_parent(*ptr, type, ((char *)data) + index * itemsize);
}

/* Quick name based property access */

bool RNA_boolean_get(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_boolean_get(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return false;
}

void RNA_boolean_set(PointerRNA *ptr, const char *name, bool value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_boolean_set(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_boolean_get_array(PointerRNA *ptr, const char *name, bool *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_boolean_get_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_boolean_set_array(PointerRNA *ptr, const char *name, const bool *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_boolean_set_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

int RNA_int_get(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_int_get(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return 0;
}

void RNA_int_set(PointerRNA *ptr, const char *name, int value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_int_set(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_int_get_array(PointerRNA *ptr, const char *name, int *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_int_get_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_int_set_array(PointerRNA *ptr, const char *name, const int *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_int_set_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

float RNA_float_get(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_float_get(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return 0;
}

void RNA_float_set(PointerRNA *ptr, const char *name, float value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_float_set(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_float_get_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_float_set_array(ptr, prop, values);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

int RNA_enum_get(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_enum_get(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return 0;
}

void RNA_enum_set(PointerRNA *ptr, const char *name, int value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_enum_set(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_enum_set_identifier(bContext *C, PointerRNA *ptr, const char *name, const char *id)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    int value;
    if (RNA_property_enum_value(C, ptr, prop, id, &value)) {
      RNA_property_enum_set(ptr, prop, value);
    }
    else {
      printf("%s: %s.%s has no enum id '%s'.\n", __func__, ptr->type->identifier, name, id);
    }
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

bool RNA_enum_is_equal(bContext *C, PointerRNA *ptr, const char *name, const char *enumname)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);
  const EnumPropertyItem *item;
  bool free;

  if (prop) {
    int i;
    bool cmp = false;

    RNA_property_enum_items(C, ptr, prop, &item, nullptr, &free);
    i = RNA_enum_from_identifier(item, enumname);
    if (i != -1) {
      cmp = (item[i].value == RNA_property_enum_get(ptr, prop));
    }

    if (free) {
      MEM_freeN(item);
    }

    if (i != -1) {
      return cmp;
    }

    printf("%s: %s.%s item %s not found.\n", __func__, ptr->type->identifier, name, enumname);
    return false;
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return false;
}

bool RNA_enum_value_from_id(const EnumPropertyItem *item, const char *identifier, int *r_value)
{
  const int i = RNA_enum_from_identifier(item, identifier);
  if (i != -1) {
    *r_value = item[i].value;
    return true;
  }
  return false;
}

bool RNA_enum_id_from_value(const EnumPropertyItem *item, int value, const char **r_identifier)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_identifier = item[i].identifier;
    return true;
  }
  return false;
}

bool RNA_enum_icon_from_value(const EnumPropertyItem *item, int value, int *r_icon)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_icon = item[i].icon;
    return true;
  }
  return false;
}

bool RNA_enum_name_from_value(const EnumPropertyItem *item, int value, const char **r_name)
{
  const int i = RNA_enum_from_value(item, value);
  if (i != -1) {
    *r_name = item[i].name;
    return true;
  }
  return false;
}

void RNA_string_get(PointerRNA *ptr, const char *name, char *value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_string_get(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
    value[0] = '\0';
  }
}

char *RNA_string_get_alloc(
    PointerRNA *ptr, const char *name, char *fixedbuf, int fixedlen, int *r_len)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_string_get_alloc(ptr, prop, fixedbuf, fixedlen, r_len);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  if (r_len != nullptr) {
    *r_len = 0;
  }
  return nullptr;
}

int RNA_string_length(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_string_length(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return 0;
}

void RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_string_set(ptr, prop, value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

PointerRNA RNA_pointer_get(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_pointer_get(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);

  return PointerRNA_NULL;
}

void RNA_pointer_set(PointerRNA *ptr, const char *name, PointerRNA ptr_value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_pointer_set(ptr, prop, ptr_value, nullptr);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_pointer_add(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_pointer_add(ptr, prop);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_collection_begin(PointerRNA *ptr, const char *name, CollectionPropertyIterator *iter)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_collection_begin(ptr, prop, iter);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_collection_add(PointerRNA *ptr, const char *name, PointerRNA *r_value)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_collection_add(ptr, prop, r_value);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

void RNA_collection_clear(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    RNA_property_collection_clear(ptr, prop);
  }
  else {
    printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  }
}

int RNA_collection_length(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_collection_length(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return 0;
}

bool RNA_collection_is_empty(PointerRNA *ptr, const char *name)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, name);

  if (prop) {
    return RNA_property_collection_is_empty(ptr, prop);
  }
  printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return false;
}

bool RNA_property_is_set_ex(PointerRNA *ptr, PropertyRNA *prop, bool use_ghost)
{
  prop = rna_ensure_property(prop);
  if (prop->flag & PROP_IDPROPERTY) {
    IDProperty *idprop = rna_idproperty_find(ptr, prop->identifier);
    return ((idprop != nullptr) && (use_ghost == false || !(idprop->flag & IDP_FLAG_GHOST)));
  }
  return true;
}

bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
{
  prop = rna_ensure_property(prop);
  if (prop->flag & PROP_IDPROPERTY) {
    IDProperty *idprop = rna_idproperty_find(ptr, prop->identifier);
    return ((idprop != nullptr) && !(idprop->flag & IDP_FLAG_GHOST));
  }
  return true;
}

void RNA_property_unset(PointerRNA *ptr, PropertyRNA *prop)
{
  prop = rna_ensure_property(prop);
  if (prop->flag & PROP_IDPROPERTY) {
    rna_idproperty_free(ptr, prop->identifier);
  }
}

bool RNA_struct_property_is_set_ex(PointerRNA *ptr, const char *identifier, bool use_ghost)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, identifier);

  if (prop) {
    return RNA_property_is_set_ex(ptr, prop, use_ghost);
  }
  /* python raises an error */
  // printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return false;
}

bool RNA_struct_property_is_set(PointerRNA *ptr, const char *identifier)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, identifier);

  if (prop) {
    return RNA_property_is_set(ptr, prop);
  }
  /* python raises an error */
  // printf("%s: %s.%s not found.\n", __func__, ptr->type->identifier, name);
  return false;
}

void RNA_struct_property_unset(PointerRNA *ptr, const char *identifier)
{
  PropertyRNA *prop = RNA_struct_find_property(ptr, identifier);

  if (prop) {
    RNA_property_unset(ptr, prop);
  }
}

bool RNA_property_is_idprop(const PropertyRNA *prop)
{
  return (prop->magic != RNA_MAGIC);
}

bool RNA_property_is_unlink(PropertyRNA *prop)
{
  const int flag = RNA_property_flag(prop);
  if (RNA_property_type(prop) == PROP_STRING) {
    return (flag & PROP_NEVER_UNLINK) == 0;
  }
  return (flag & (PROP_NEVER_UNLINK | PROP_NEVER_NULL)) == 0;
}

std::string RNA_pointer_as_string_id(bContext *C, PointerRNA *ptr)
{
  std::stringstream ss;

  const char *propname;
  int first_time = 1;

  ss << '{';

  RNA_STRUCT_BEGIN (ptr, prop) {
    propname = RNA_property_identifier(prop);

    if (STREQ(propname, "rna_type")) {
      continue;
    }

    if (first_time == 0) {
      ss << ", ";
    }
    first_time = 0;

    const std::string str = RNA_property_as_string(C, ptr, prop, -1, INT_MAX);
    ss << fmt::format("\"{}\":{}", propname, str);
  }
  RNA_STRUCT_END;

  ss << '}';

  return ss.str();
}

static std::optional<std::string> rna_pointer_as_string__bldata(PointerRNA *ptr)
{
  if (ptr->type == nullptr || ptr->owner_id == nullptr) {
    return "None";
  }
  if (RNA_struct_is_ID(ptr->type)) {
    return RNA_path_full_ID_py(ptr->owner_id);
  }
  return RNA_path_full_struct_py(ptr);
}

std::optional<std::string> RNA_pointer_as_string(bContext *C,
                                                 PointerRNA *ptr,
                                                 PropertyRNA *prop_ptr,
                                                 PointerRNA *ptr_prop)
{
  IDProperty *prop;
  if (ptr_prop->data == nullptr) {
    return "None";
  }
  if ((prop = rna_idproperty_check(&prop_ptr, ptr)) && prop->type != IDP_ID) {
    return RNA_pointer_as_string_id(C, ptr_prop);
  }
  return rna_pointer_as_string__bldata(ptr_prop);
}

std::string RNA_pointer_as_string_keywords_ex(bContext *C,
                                              PointerRNA *ptr,
                                              const bool as_function,
                                              const bool all_args,
                                              const bool nested_args,
                                              const int max_prop_length,
                                              PropertyRNA *iterprop)
{
  const char *arg_name = nullptr;

  PropertyRNA *prop;

  std::stringstream ss;

  bool first_iter = true;
  int flag, flag_parameter;

  RNA_PROP_BEGIN (ptr, propptr, iterprop) {
    prop = static_cast<PropertyRNA *>(propptr.data);

    flag = RNA_property_flag(prop);
    flag_parameter = RNA_parameter_flag(prop);

    if (as_function && (flag_parameter & PARM_OUTPUT)) {
      continue;
    }

    arg_name = RNA_property_identifier(prop);

    if (STREQ(arg_name, "rna_type")) {
      continue;
    }

    if ((nested_args == false) && (RNA_property_type(prop) == PROP_POINTER)) {
      continue;
    }

    if (as_function && (prop->flag_parameter & PARM_REQUIRED)) {
      /* required args don't have useful defaults */
      ss << fmt::format(fmt::runtime(first_iter ? "{}" : ", {}"), arg_name);
      first_iter = false;
    }
    else {
      bool ok = true;

      if (all_args == true) {
        /* pass */
      }
      else if (RNA_struct_idprops_check(ptr->type)) {
        ok = RNA_property_is_set(ptr, prop);
      }

      if (ok) {
        std::string buf;
        if (as_function && RNA_property_type(prop) == PROP_POINTER) {
          /* don't expand pointers for functions */
          if (flag & PROP_NEVER_NULL) {
            /* we can't really do the right thing here. arg=arg?, hrmf! */
            buf = arg_name;
          }
          else {
            buf = "None";
          }
        }
        else {
          buf = RNA_property_as_string(C, ptr, prop, -1, max_prop_length);
        }

        ss << fmt::format(fmt::runtime(first_iter ? "{}={}" : ", {}={}"), arg_name, buf);
        first_iter = false;
      }
    }
  }
  RNA_PROP_END;

  return ss.str();
}

std::string RNA_pointer_as_string_keywords(bContext *C,
                                           PointerRNA *ptr,
                                           const bool as_function,
                                           const bool all_args,
                                           const bool nested_args,
                                           const int max_prop_length)
{
  PropertyRNA *iterprop;

  iterprop = RNA_struct_iterator_property(ptr->type);

  return RNA_pointer_as_string_keywords_ex(
      C, ptr, as_function, all_args, nested_args, max_prop_length, iterprop);
}

std::string RNA_function_as_string_keywords(bContext *C,
                                            FunctionRNA *func,
                                            const bool as_function,
                                            const bool all_args,
                                            const int max_prop_length)
{
  PointerRNA funcptr = RNA_pointer_create_discrete(nullptr, &RNA_Function, func);

  PropertyRNA *iterprop = RNA_struct_find_property(&funcptr, "parameters");

  RNA_struct_iterator_property(funcptr.type);

  return RNA_pointer_as_string_keywords_ex(
      C, &funcptr, as_function, all_args, true, max_prop_length, iterprop);
}

static const char *bool_as_py_string(const int var)
{
  return var ? "True" : "False";
}

static void *rna_array_as_string_alloc(
    int type, int len, PointerRNA *ptr, PropertyRNA *prop, void **r_buf_end)
{
  switch (type) {
    case PROP_BOOLEAN: {
      bool *buf = MEM_malloc_arrayN<bool>(size_t(len), __func__);
      RNA_property_boolean_get_array(ptr, prop, buf);
      *r_buf_end = buf + len;
      return buf;
    }
    case PROP_INT: {
      int *buf = MEM_malloc_arrayN<int>(size_t(len), __func__);
      RNA_property_int_get_array(ptr, prop, buf);
      *r_buf_end = buf + len;
      return buf;
    }
    case PROP_FLOAT: {
      float *buf = MEM_malloc_arrayN<float>(size_t(len), __func__);
      RNA_property_float_get_array(ptr, prop, buf);
      *r_buf_end = buf + len;
      return buf;
    }
    default:
      BLI_assert_unreachable();
      return nullptr;
  }
}

static void rna_array_as_string_elem(int type, void **buf_p, int len, std::stringstream &ss)
{
  /* This will print a comma separated string of the array elements from
   * buf start to len. We will add a comma if len == 1 to preserve tuples. */
  const int end = len - 1;
  switch (type) {
    case PROP_BOOLEAN: {
      bool *buf = static_cast<bool *>(*buf_p);
      for (int i = 0; i < len; i++, buf++) {
        ss << fmt::format(fmt::runtime((i < end || !end) ? "{}, " : "{}"),
                          bool_as_py_string(*buf));
      }
      *buf_p = buf;
      break;
    }
    case PROP_INT: {
      int *buf = static_cast<int *>(*buf_p);
      for (int i = 0; i < len; i++, buf++) {
        ss << fmt::format(fmt::runtime((i < end || !end) ? "{}, " : "{}"), *buf);
      }
      *buf_p = buf;
      break;
    }
    case PROP_FLOAT: {
      float *buf = static_cast<float *>(*buf_p);
      for (int i = 0; i < len; i++, buf++) {
        ss << fmt::format(fmt::runtime((i < end || !end) ? "{:g}, " : "{:g}"), *buf);
      }
      *buf_p = buf;
      break;
    }
    default:
      BLI_assert_unreachable();
  }
}

static void rna_array_as_string_recursive(
    int type, void **buf_p, int totdim, const int *dim_size, std::stringstream &ss)
{
  ss << '(';
  if (totdim > 1) {
    totdim--;
    const int end = dim_size[totdim] - 1;
    for (int i = 0; i <= end; i++) {
      rna_array_as_string_recursive(type, buf_p, totdim, dim_size, ss);
      if (i < end || !end) {
        ss << ", ";
      }
    }
  }
  else {
    rna_array_as_string_elem(type, buf_p, dim_size[0], ss);
  }
  ss << ')';
}

static void rna_array_as_string(
    int type, int len, PointerRNA *ptr, PropertyRNA *prop, std::stringstream &ss)
{
  void *buf_end;
  void *buf = rna_array_as_string_alloc(type, len, ptr, prop, &buf_end);
  void *buf_step = buf;
  int totdim, dim_size[RNA_MAX_ARRAY_DIMENSION];

  totdim = RNA_property_array_dimension(ptr, prop, dim_size);

  rna_array_as_string_recursive(type, &buf_step, totdim, dim_size, ss);
  BLI_assert(buf_step == buf_end);
  MEM_freeN(buf);
}

std::string RNA_property_as_string(
    bContext *C, PointerRNA *ptr, PropertyRNA *prop, int index, int max_prop_length)
{
  int type = RNA_property_type(prop);
  int len = RNA_property_array_length(ptr, prop);

  std::stringstream ss;

  /* see if we can coerce into a python type - PropertyType */
  switch (type) {
    case PROP_BOOLEAN:
      if (len == 0) {
        ss << bool_as_py_string(RNA_property_boolean_get(ptr, prop));
      }
      else {
        if (index != -1) {
          ss << bool_as_py_string(RNA_property_boolean_get_index(ptr, prop, index));
        }
        else {
          rna_array_as_string(type, len, ptr, prop, ss);
        }
      }
      break;
    case PROP_INT:
      if (len == 0) {
        ss << RNA_property_int_get(ptr, prop);
      }
      else {
        if (index != -1) {
          ss << RNA_property_int_get_index(ptr, prop, index);
        }
        else {
          rna_array_as_string(type, len, ptr, prop, ss);
        }
      }
      break;
    case PROP_FLOAT:
      if (len == 0) {
        ss << fmt::format("{:g}", RNA_property_float_get(ptr, prop));
      }
      else {
        if (index != -1) {
          ss << fmt::format("{:g}", RNA_property_float_get_index(ptr, prop, index));
        }
        else {
          rna_array_as_string(type, len, ptr, prop, ss);
        }
      }
      break;
    case PROP_STRING: {
      char *buf_esc;
      char *buf;
      int length;

      length = RNA_property_string_length(ptr, prop);
      buf = MEM_malloc_arrayN<char>(size_t(length) + 1, "RNA_property_as_string");
      buf_esc = MEM_malloc_arrayN<char>(size_t(length) * 2 + 1, "RNA_property_as_string esc");
      RNA_property_string_get(ptr, prop, buf);
      BLI_str_escape(buf_esc, buf, length * 2 + 1);
      MEM_freeN(buf);
      ss << fmt::format("\"{}\"", buf_esc);
      MEM_freeN(buf_esc);
      break;
    }
    case PROP_ENUM: {
      /* string arrays don't exist */
      const char *identifier;
      int val = RNA_property_enum_get(ptr, prop);

      if (RNA_property_flag(prop) & PROP_ENUM_FLAG) {
        /* represent as a python set */
        if (val) {
          const EnumPropertyItem *item_array;
          bool free;

          ss << "{";

          RNA_property_enum_items(C, ptr, prop, &item_array, nullptr, &free);
          if (item_array) {
            const EnumPropertyItem *item = item_array;
            bool is_first = true;
            for (; item->identifier; item++) {
              if (item->identifier[0] && item->value & val) {
                ss << fmt::format(fmt::runtime(is_first ? "'{}'" : ", '{}'"), item->identifier);
                is_first = false;
              }
            }

            if (free) {
              MEM_freeN(item_array);
            }
          }

          ss << "}";
        }
        else {
          /* annoying exception, don't confuse with dictionary syntax above: {} */
          ss << "set()";
        }
      }
      else if (RNA_property_enum_identifier(C, ptr, prop, val, &identifier)) {
        ss << fmt::format("'{}'", identifier);
      }
      else {
        return "'<UNKNOWN ENUM>'";
      }
      break;
    }
    case PROP_POINTER: {
      PointerRNA tptr = RNA_property_pointer_get(ptr, prop);
      ss << RNA_pointer_as_string(C, ptr, prop, &tptr).value_or("");
      break;
    }
    case PROP_COLLECTION: {
      int i = 0;
      CollectionPropertyIterator collect_iter;
      ss << "[";

      for (RNA_property_collection_begin(ptr, prop, &collect_iter);
           (i < max_prop_length) && collect_iter.valid;
           RNA_property_collection_next(&collect_iter), i++)
      {
        PointerRNA itemptr = collect_iter.ptr;

        if (i != 0) {
          ss << ", ";
        }

        /* now get every prop of the collection */
        ss << RNA_pointer_as_string(C, ptr, prop, &itemptr).value_or("");
      }

      RNA_property_collection_end(&collect_iter);
      ss << "]";
      break;
    }
    default:
      return "'<UNKNOWN TYPE>'"; /* TODO */
  }

  return ss.str();
}

/* Function */

const char *RNA_function_identifier(FunctionRNA *func)
{
  return func->identifier;
}

const char *RNA_function_ui_description(FunctionRNA *func)
{
  return TIP_(func->description);
}

const char *RNA_function_ui_description_raw(FunctionRNA *func)
{
  return func->description;
}

int RNA_function_flag(FunctionRNA *func)
{
  return func->flag;
}

int RNA_function_defined(FunctionRNA *func)
{
  return func->call != nullptr;
}

PropertyRNA *RNA_function_get_parameter(PointerRNA * /*ptr*/, FunctionRNA *func, int index)
{
  return static_cast<PropertyRNA *>(BLI_findlink(&func->cont.properties, index));
}

PropertyRNA *RNA_function_find_parameter(PointerRNA * /*ptr*/,
                                         FunctionRNA *func,
                                         const char *identifier)
{
  PropertyRNA *parm;

  parm = static_cast<PropertyRNA *>(func->cont.properties.first);
  for (; parm; parm = parm->next) {
    if (STREQ(RNA_property_identifier(parm), identifier)) {
      break;
    }
  }

  return parm;
}

const ListBase *RNA_function_defined_parameters(FunctionRNA *func)
{
  return &func->cont.properties;
}

/* Utility */

int RNA_parameter_flag(PropertyRNA *prop)
{
  return int(rna_ensure_property(prop)->flag_parameter);
}

ParameterList *RNA_parameter_list_create(ParameterList *parms,
                                         PointerRNA * /*ptr*/,
                                         FunctionRNA *func)
{
  PointerRNA null_ptr = PointerRNA_NULL;
  void *data;
  int alloc_size = 0, size;

  parms->arg_count = 0;
  parms->ret_count = 0;

  /* allocate data */
  LISTBASE_FOREACH (PropertyRNA *, parm, &func->cont.properties) {
    alloc_size += rna_parameter_size_pad(rna_parameter_size(parm));

    if (parm->flag_parameter & PARM_OUTPUT) {
      parms->ret_count++;
    }
    else {
      parms->arg_count++;
    }
  }

  parms->data = MEM_callocN(alloc_size, "RNA_parameter_list_create");
  parms->func = func;
  parms->alloc_size = alloc_size;

  /* set default values */
  data = parms->data;

  LISTBASE_FOREACH (PropertyRNA *, parm, &func->cont.properties) {
    size = rna_parameter_size(parm);

    /* set length to 0, these need to be set later, see bpy_array.c's py_to_array */
    if (parm->flag & PROP_DYNAMIC) {
      ParameterDynAlloc *data_alloc = static_cast<ParameterDynAlloc *>(data);
      data_alloc->array_tot = 0;
      data_alloc->array = nullptr;
    }
    else if ((parm->flag_parameter & PARM_RNAPTR) && (parm->flag & PROP_THICK_WRAP)) {
      BLI_assert(parm->type == PROP_POINTER);
      new (static_cast<PointerRNA *>(data)) PointerRNA();
    }

    if (!(parm->flag_parameter & PARM_REQUIRED) && !(parm->flag & PROP_DYNAMIC)) {
      switch (parm->type) {
        case PROP_BOOLEAN:
          if (parm->arraydimension) {
            rna_property_boolean_get_default_array_values(
                &null_ptr, (BoolPropertyRNA *)parm, static_cast<bool *>(data));
          }
          else {
            memcpy(data, &((BoolPropertyRNA *)parm)->defaultvalue, size);
          }
          break;
        case PROP_INT:
          if (parm->arraydimension) {
            rna_property_int_get_default_array_values(
                &null_ptr, (IntPropertyRNA *)parm, static_cast<int *>(data));
          }
          else {
            memcpy(data, &((IntPropertyRNA *)parm)->defaultvalue, size);
          }
          break;
        case PROP_FLOAT:
          if (parm->arraydimension) {
            rna_property_float_get_default_array_values(
                &null_ptr, (FloatPropertyRNA *)parm, static_cast<float *>(data));
          }
          else {
            memcpy(data, &((FloatPropertyRNA *)parm)->defaultvalue, size);
          }
          break;
        case PROP_ENUM:
          memcpy(data, &((EnumPropertyRNA *)parm)->defaultvalue, size);
          break;
        case PROP_STRING: {
          const char *defvalue = ((StringPropertyRNA *)parm)->defaultvalue;
          if (defvalue && defvalue[0]) {
            /* Causes bug #29988, possibly this is only correct for thick wrapped
             * need to look further into it - campbell */
#if 0
            BLI_strncpy(data, defvalue, size);
#else
            memcpy(data, &defvalue, size);
#endif
          }
          break;
        }
        case PROP_POINTER:
        case PROP_COLLECTION:
          break;
      }
    }

    data = ((char *)data) + rna_parameter_size_pad(size);
  }

  return parms;
}

void RNA_parameter_list_free(ParameterList *parms)
{
  PropertyRNA *parm;

  parm = static_cast<PropertyRNA *>(parms->func->cont.properties.first);
  void *data = parms->data;
  for (; parm; parm = parm->next) {
    if (parm->type == PROP_COLLECTION) {
      CollectionVector *vector = static_cast<CollectionVector *>(data);
      vector->~CollectionVector();
    }
    else if ((parm->flag_parameter & PARM_RNAPTR) && (parm->flag & PROP_THICK_WRAP)) {
      BLI_assert(parm->type == PROP_POINTER);
      PointerRNA *ptr = static_cast<PointerRNA *>(data);
      /* #RNA_parameter_list_create ensures that 'thick wrap' PointerRNA parameters are
       * constructed. */
      ptr->~PointerRNA();
    }
    else if (parm->flag & PROP_DYNAMIC) {
      /* for dynamic arrays and strings, data is a pointer to an array */
      ParameterDynAlloc *data_alloc = static_cast<ParameterDynAlloc *>(data);
      if (data_alloc->array) {
        MEM_freeN(data_alloc->array);
      }
    }
    data = static_cast<char *>(data) + rna_parameter_size_pad(rna_parameter_size(parm));
  }

  MEM_freeN(parms->data);
  parms->data = nullptr;

  parms->func = nullptr;
}

int RNA_parameter_list_size(const ParameterList *parms)
{
  return parms->alloc_size;
}

int RNA_parameter_list_arg_count(const ParameterList *parms)
{
  return parms->arg_count;
}

int RNA_parameter_list_ret_count(const ParameterList *parms)
{
  return parms->ret_count;
}

void RNA_parameter_list_begin(ParameterList *parms, ParameterIterator *iter)
{
  /* may be useful but unused now */
  // RNA_pointer_create_discrete(nullptr, &RNA_Function, parms->func, &iter->funcptr); /* UNUSED */

  iter->parms = parms;
  iter->parm = static_cast<PropertyRNA *>(parms->func->cont.properties.first);
  iter->valid = iter->parm != nullptr;
  iter->offset = 0;

  if (iter->valid) {
    iter->size = rna_parameter_size(iter->parm);
    iter->data = ((char *)iter->parms->data); /* +iter->offset, always 0 */
  }
}

void RNA_parameter_list_next(ParameterIterator *iter)
{
  iter->offset += rna_parameter_size_pad(iter->size);
  iter->parm = iter->parm->next;
  iter->valid = iter->parm != nullptr;

  if (iter->valid) {
    iter->size = rna_parameter_size(iter->parm);
    iter->data = (((char *)iter->parms->data) + iter->offset);
  }
}

void RNA_parameter_list_end(ParameterIterator * /*iter*/)
{
  /* nothing to do */
}

void RNA_parameter_get(ParameterList *parms, PropertyRNA *parm, void **r_value)
{
  ParameterIterator iter;

  RNA_parameter_list_begin(parms, &iter);

  for (; iter.valid; RNA_parameter_list_next(&iter)) {
    if (iter.parm == parm) {
      break;
    }
  }

  if (iter.valid) {
    if (parm->flag & PROP_DYNAMIC) {
      /* for dynamic arrays and strings, data is a pointer to an array */
      ParameterDynAlloc *data_alloc = static_cast<ParameterDynAlloc *>(iter.data);
      *r_value = data_alloc->array;
    }
    else {
      *r_value = iter.data;
    }
  }
  else {
    *r_value = nullptr;
  }

  RNA_parameter_list_end(&iter);
}

void RNA_parameter_get_lookup(ParameterList *parms, const char *identifier, void **r_value)
{
  PropertyRNA *parm;

  parm = static_cast<PropertyRNA *>(parms->func->cont.properties.first);
  for (; parm; parm = parm->next) {
    if (STREQ(RNA_property_identifier(parm), identifier)) {
      break;
    }
  }

  if (parm) {
    RNA_parameter_get(parms, parm, r_value);
  }
}

void RNA_parameter_set(ParameterList *parms, PropertyRNA *parm, const void *value)
{
  ParameterIterator iter;

  RNA_parameter_list_begin(parms, &iter);

  for (; iter.valid; RNA_parameter_list_next(&iter)) {
    if (iter.parm == parm) {
      break;
    }
  }

  if (iter.valid) {
    if (parm->flag & PROP_DYNAMIC) {
      /* for dynamic arrays and strings, data is a pointer to an array */
      ParameterDynAlloc *data_alloc = static_cast<ParameterDynAlloc *>(iter.data);
      size_t size = 0;
      switch (parm->type) {
        case PROP_STRING:
          size = sizeof(char);
          break;
        case PROP_INT:
        case PROP_BOOLEAN:
          size = sizeof(int);
          break;
        case PROP_FLOAT:
          size = sizeof(float);
          break;
        default:
          break;
      }
      size *= data_alloc->array_tot;
      if (data_alloc->array) {
        MEM_freeN(data_alloc->array);
      }
      data_alloc->array = MEM_mallocN(size, __func__);
      memcpy(data_alloc->array, value, size);
    }
    else if ((parm->flag_parameter & PARM_RNAPTR) && (parm->flag & PROP_THICK_WRAP)) {
      BLI_assert(parm->type == PROP_POINTER);
      BLI_assert(iter.size == sizeof(PointerRNA));
      PointerRNA *ptr = static_cast<PointerRNA *>(iter.data);
      /* #RNA_parameter_list_create ensures that 'thick wrap' PointerRNA parameters are
       * constructed. */
      *ptr = PointerRNA(*static_cast<const PointerRNA *>(value));
    }
    else {
      memcpy(iter.data, value, iter.size);
    }
  }

  RNA_parameter_list_end(&iter);
}

void RNA_parameter_set_lookup(ParameterList *parms, const char *identifier, const void *value)
{
  PropertyRNA *parm;

  parm = static_cast<PropertyRNA *>(parms->func->cont.properties.first);
  for (; parm; parm = parm->next) {
    if (STREQ(RNA_property_identifier(parm), identifier)) {
      break;
    }
  }

  if (parm) {
    RNA_parameter_set(parms, parm, value);
  }
}

int RNA_parameter_dynamic_length_get(ParameterList *parms, PropertyRNA *parm)
{
  ParameterIterator iter;
  int len = 0;

  RNA_parameter_list_begin(parms, &iter);

  for (; iter.valid; RNA_parameter_list_next(&iter)) {
    if (iter.parm == parm) {
      break;
    }
  }

  if (iter.valid) {
    len = RNA_parameter_dynamic_length_get_data(parms, parm, iter.data);
  }

  RNA_parameter_list_end(&iter);

  return len;
}

void RNA_parameter_dynamic_length_set(ParameterList *parms, PropertyRNA *parm, int length)
{
  ParameterIterator iter;

  RNA_parameter_list_begin(parms, &iter);

  for (; iter.valid; RNA_parameter_list_next(&iter)) {
    if (iter.parm == parm) {
      break;
    }
  }

  if (iter.valid) {
    RNA_parameter_dynamic_length_set_data(parms, parm, iter.data, length);
  }

  RNA_parameter_list_end(&iter);
}

int RNA_parameter_dynamic_length_get_data(ParameterList * /*parms*/, PropertyRNA *parm, void *data)
{
  if (parm->flag & PROP_DYNAMIC) {
    return int(((ParameterDynAlloc *)data)->array_tot);
  }
  return 0;
}

void RNA_parameter_dynamic_length_set_data(ParameterList * /*parms*/,
                                           PropertyRNA *parm,
                                           void *data,
                                           int length)
{
  if (parm->flag & PROP_DYNAMIC) {
    ((ParameterDynAlloc *)data)->array_tot = intptr_t(length);
  }
}

int RNA_function_call(
    bContext *C, ReportList *reports, PointerRNA *ptr, FunctionRNA *func, ParameterList *parms)
{
  if (func->call) {
    func->call(C, reports, ptr, parms);

    return 0;
  }

  return -1;
}

std::optional<blender::StringRefNull> RNA_translate_ui_text(
    const char *text, const char *text_ctxt, StructRNA *type, PropertyRNA *prop, int translate)
{
  return rna_translate_ui_text(text, text_ctxt, type, prop, translate);
}

bool RNA_property_reset(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  int len;

  /* get the length of the array to work with */
  len = RNA_property_array_length(ptr, prop);

  /* get and set the default values as appropriate for the various types */
  switch (RNA_property_type(prop)) {
    case PROP_BOOLEAN:
      if (len) {
        if (index == -1) {
          bool *tmparray = MEM_calloc_arrayN<bool>(len, __func__);

          RNA_property_boolean_get_default_array(ptr, prop, tmparray);
          RNA_property_boolean_set_array(ptr, prop, tmparray);

          MEM_freeN(tmparray);
        }
        else {
          int value = RNA_property_boolean_get_default_index(ptr, prop, index);
          RNA_property_boolean_set_index(ptr, prop, index, value);
        }
      }
      else {
        int value = RNA_property_boolean_get_default(ptr, prop);
        RNA_property_boolean_set(ptr, prop, value);
      }
      return true;
    case PROP_INT:
      if (len) {
        if (index == -1) {
          int *tmparray = MEM_calloc_arrayN<int>(len, __func__);

          RNA_property_int_get_default_array(ptr, prop, tmparray);
          RNA_property_int_set_array(ptr, prop, tmparray);

          MEM_freeN(tmparray);
        }
        else {
          int value = RNA_property_int_get_default_index(ptr, prop, index);
          RNA_property_int_set_index(ptr, prop, index, value);
        }
      }
      else {
        int value = RNA_property_int_get_default(ptr, prop);
        RNA_property_int_set(ptr, prop, value);
      }
      return true;
    case PROP_FLOAT:
      if (len) {
        if (index == -1) {
          float *tmparray = MEM_calloc_arrayN<float>(len, __func__);

          RNA_property_float_get_default_array(ptr, prop, tmparray);
          RNA_property_float_set_array(ptr, prop, tmparray);

          MEM_freeN(tmparray);
        }
        else {
          float value = RNA_property_float_get_default_index(ptr, prop, index);
          RNA_property_float_set_index(ptr, prop, index, value);
        }
      }
      else {
        float value = RNA_property_float_get_default(ptr, prop);
        RNA_property_float_set(ptr, prop, value);
      }
      return true;
    case PROP_ENUM: {
      int value = RNA_property_enum_get_default(ptr, prop);
      RNA_property_enum_set(ptr, prop, value);
      return true;
    }

    case PROP_STRING: {
      char *value = RNA_property_string_get_default_alloc(ptr, prop, nullptr, 0, nullptr);
      RNA_property_string_set(ptr, prop, value);
      MEM_freeN(value);
      return true;
    }

    case PROP_POINTER: {
      PointerRNA value = RNA_property_pointer_get_default(ptr, prop);
      RNA_property_pointer_set(ptr, prop, value, nullptr);
      return true;
    }

    default:
      /* FIXME: are there still any cases that haven't been handled?
       * comment out "default" block to check :) */
      return false;
  }
}

bool RNA_property_assign_default(PointerRNA *ptr, PropertyRNA *prop)
{
  if (!RNA_property_is_idprop(prop) || RNA_property_array_check(prop)) {
    return false;
  }

  /* get and set the default values as appropriate for the various types */
  switch (RNA_property_type(prop)) {
    case PROP_INT: {
      int value = RNA_property_int_get(ptr, prop);
      return RNA_property_int_set_default(prop, value);
    }

    case PROP_FLOAT: {
      float value = RNA_property_float_get(ptr, prop);
      return RNA_property_float_set_default(prop, value);
    }

    default:
      return false;
  }
}

#ifdef WITH_PYTHON
extern void PyC_LineSpit();
#endif

void _RNA_warning(const char *format, ...)
{
  va_list args;

  va_start(args, format);
  vprintf(format, args);
  va_end(args);

  /* gcc macro adds '\n', but can't use for other compilers */
#ifndef __GNUC__
  fputc('\n', stdout);
#endif

#ifdef WITH_PYTHON
  {
    PyC_LineSpit();
  }
#endif
}

bool RNA_path_resolved_create(PointerRNA *ptr,
                              PropertyRNA *prop,
                              const int prop_index,
                              PathResolvedRNA *r_anim_rna)
{
  int array_len = RNA_property_array_length(ptr, prop);

  if ((array_len == 0) || (prop_index < array_len)) {
    r_anim_rna->ptr = *ptr;
    r_anim_rna->prop = prop;
    r_anim_rna->prop_index = array_len ? prop_index : -1;

    return true;
  }
  return false;
}

static char rna_struct_state_owner[128];
void RNA_struct_state_owner_set(const char *name)
{
  if (name) {
    STRNCPY(rna_struct_state_owner, name);
  }
  else {
    rna_struct_state_owner[0] = '\0';
  }
}

const char *RNA_struct_state_owner_get()
{
  if (rna_struct_state_owner[0]) {
    return rna_struct_state_owner;
  }
  return nullptr;
}
