/* SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup RNA
 */

#include <string.h>

#include <CLG_log.h>

#include "MEM_guardedalloc.h"

#include "DNA_ID.h"
#include "DNA_anim_types.h"
#include "DNA_camera_types.h"
#include "DNA_constraint_types.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_key_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"

#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"

//#define DEBUG_OVERRIDE_TIMEIT

#ifdef DEBUG_OVERRIDE_TIMEIT
#  include "PIL_time_utildefines.h"
#  include <stdio.h>
#endif

#include "BKE_armature.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
#include "BKE_lib_override.h"
#include "BKE_main.h"

#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "RNA_path.h"
#include "RNA_prototypes.h"

#include "rna_access_internal.h"
#include "rna_internal.h"

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

/**
 * Find the actual ID owner of the given \a ptr #PointerRNA, in override sense, and generate the
 * full rna path from it to given \a prop #PropertyRNA if \a rna_path is given.
 *
 * \note This is slightly different than 'generic' RNA 'id owner' as returned by
 * #RNA_find_real_ID_and_path, since in overrides we also consider shape keys as embedded data, not
 * only root node trees and master collections.
 */
static ID *rna_property_override_property_real_id_owner(Main *UNUSED(bmain),
                                                        PointerRNA *ptr,
                                                        PropertyRNA *prop,
                                                        char **r_rna_path)
{
  ID *id = ptr->owner_id;
  ID *owner_id = id;
  const char *rna_path_prefix = NULL;

  if (r_rna_path != NULL) {
    *r_rna_path = NULL;
  }

  if (id == NULL) {
    return NULL;
  }

  if (id->flag & (LIB_EMBEDDED_DATA | LIB_EMBEDDED_DATA_LIB_OVERRIDE)) {
    /* XXX this is very bad band-aid code, but for now it will do.
     * We should at least use a #define for those prop names.
     * Ideally RNA as a whole should be aware of those PITA of embedded IDs, and have a way to
     * retrieve their owner IDs and generate paths from those.
     */

    switch (GS(id->name)) {
      case ID_KE:
        owner_id = ((Key *)id)->from;
        rna_path_prefix = "shape_keys.";
        break;
      case ID_GR:
      case ID_NT:
        /* Master collections, Root node trees. */
        owner_id = RNA_find_real_ID_and_path(id, &rna_path_prefix);
        break;
      default:
        BLI_assert_unreachable();
    }
  }

  if (r_rna_path == NULL) {
    return owner_id;
  }

  char *rna_path = RNA_path_from_ID_to_property(ptr, prop);
  if (rna_path) {
    *r_rna_path = rna_path;
    if (rna_path_prefix != NULL) {
      *r_rna_path = BLI_sprintfN("%s%s", rna_path_prefix, rna_path);
      MEM_freeN(rna_path);
    }

    return owner_id;
  }
  return NULL;
}

int RNA_property_override_flag(PropertyRNA *prop)
{
  return rna_ensure_property(prop)->flag_override;
}

bool RNA_property_overridable_get(PointerRNA *ptr, PropertyRNA *prop)
{
  if (prop->magic == RNA_MAGIC) {
    /* Special handling for insertions of constraints or modifiers... */
    /* TODO: Note We may want to add a more generic system to RNA
     * (like a special property in struct of items)
     * if we get more overridable collections,
     * for now we can live with those special-cases handling I think. */
    if (RNA_struct_is_a(ptr->type, &RNA_Constraint)) {
      bConstraint *con = ptr->data;
      if (con->flag & CONSTRAINT_OVERRIDE_LIBRARY_LOCAL) {
        return true;
      }
    }
    else if (RNA_struct_is_a(ptr->type, &RNA_Modifier)) {
      ModifierData *mod = ptr->data;
      if (mod->flag & eModifierFlag_OverrideLibrary_Local) {
        return true;
      }
    }
    else if (RNA_struct_is_a(ptr->type, &RNA_GpencilModifier)) {
      GpencilModifierData *gp_mod = ptr->data;
      if (gp_mod->flag & eGpencilModifierFlag_OverrideLibrary_Local) {
        return true;
      }
    }
    else if (RNA_struct_is_a(ptr->type, &RNA_NlaTrack)) {
      NlaTrack *nla_track = ptr->data;
      if (nla_track->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) {
        return true;
      }
    }
    else if (RNA_struct_is_a(ptr->type, &RNA_CameraBackgroundImage)) {
      CameraBGImage *bgpic = ptr->data;
      if (bgpic->flag & CAM_BGIMG_FLAG_OVERRIDE_LIBRARY_LOCAL) {
        return true;
      }
    }
    /* If this is a RNA-defined property (real or 'virtual' IDProp),
     * we want to use RNA prop flag. */
    return !(prop->flag_override & PROPOVERRIDE_NO_COMPARISON) &&
           (prop->flag_override & PROPOVERRIDE_OVERRIDABLE_LIBRARY);
  }
  /* If this is a real 'pure' IDProp (aka custom property), we want to use the IDProp flag. */
  IDProperty *idprop = (IDProperty *)prop;
  return (idprop->flag & IDP_FLAG_OVERRIDABLE_LIBRARY) != 0;
}

bool RNA_property_overridable_library_set(PointerRNA *UNUSED(ptr),
                                          PropertyRNA *prop,
                                          const bool is_overridable)
{
  /* Only works for pure custom properties IDProps. */
  if (prop->magic != RNA_MAGIC) {
    IDProperty *idprop = (IDProperty *)prop;

    idprop->flag = is_overridable ? (idprop->flag | IDP_FLAG_OVERRIDABLE_LIBRARY) :
                                    (idprop->flag & ~IDP_FLAG_OVERRIDABLE_LIBRARY);
    return true;
  }

  return false;
}

bool RNA_property_overridden(PointerRNA *ptr, PropertyRNA *prop)
{
  char *rna_path = RNA_path_from_ID_to_property(ptr, prop);
  ID *id = ptr->owner_id;

  if (rna_path == NULL || id == NULL || !ID_IS_OVERRIDE_LIBRARY(id)) {
    return false;
  }

  return (BKE_lib_override_library_property_find(id->override_library, rna_path) != NULL);
}

bool RNA_property_comparable(PointerRNA *UNUSED(ptr), PropertyRNA *prop)
{
  prop = rna_ensure_property(prop);

  return !(prop->flag_override & PROPOVERRIDE_NO_COMPARISON);
}

static bool rna_property_override_operation_apply(Main *bmain,
                                                  PointerRNA *ptr_dst,
                                                  PointerRNA *ptr_src,
                                                  PointerRNA *ptr_storage,
                                                  PropertyRNA *prop_dst,
                                                  PropertyRNA *prop_src,
                                                  PropertyRNA *prop_storage,
                                                  PointerRNA *ptr_item_dst,
                                                  PointerRNA *ptr_item_src,
                                                  PointerRNA *ptr_item_storage,
                                                  IDOverrideLibraryPropertyOperation *opop);

bool RNA_property_copy(
    Main *bmain, PointerRNA *ptr, PointerRNA *fromptr, PropertyRNA *prop, int index)
{
  if (!RNA_property_editable(ptr, prop)) {
    return false;
  }

  PropertyRNA *prop_dst = prop;
  PropertyRNA *prop_src = prop;

  /* Ensure we get real property data,
   * be it an actual RNA property, or an #IDProperty in disguise. */
  prop_dst = rna_ensure_property_realdata(&prop_dst, ptr);
  prop_src = rna_ensure_property_realdata(&prop_src, fromptr);

  /* IDprops: destination may not exist, if source does and is set, try to create it. */
  /* NOTE: this is sort of quick hack/bandage to fix the issue,
   * we need to rethink how IDProps are handled in 'diff' RNA code completely, IMHO. */
  if (prop_src != NULL && prop_dst == NULL && RNA_property_is_set(fromptr, prop)) {
    BLI_assert(prop_src->magic != RNA_MAGIC);
    IDProperty *idp_dst = RNA_struct_idprops(ptr, true);
    IDProperty *prop_idp_dst = IDP_CopyProperty((IDProperty *)prop_src);
    IDP_AddToGroup(idp_dst, prop_idp_dst);
    rna_idproperty_touch(prop_idp_dst);
    /* Nothing else to do here... */
    return true;
  }

  if (ELEM(NULL, prop_dst, prop_src)) {
    return false;
  }

  IDOverrideLibraryPropertyOperation opop = {
      .operation = IDOVERRIDE_LIBRARY_OP_REPLACE,
      .subitem_reference_index = index,
      .subitem_local_index = index,
  };
  return rna_property_override_operation_apply(
      bmain, ptr, fromptr, NULL, prop_dst, prop_src, NULL, NULL, NULL, NULL, &opop);
}

static int rna_property_override_diff(Main *bmain,
                                      PropertyRNAOrID *prop_a,
                                      PropertyRNAOrID *prop_b,
                                      const char *rna_path,
                                      const size_t rna_path_len,
                                      eRNACompareMode mode,
                                      IDOverrideLibrary *override,
                                      const eRNAOverrideMatch flags,
                                      eRNAOverrideMatchResult *r_report_flags);

bool RNA_property_equals(
    Main *bmain, PointerRNA *ptr_a, PointerRNA *ptr_b, PropertyRNA *prop, eRNACompareMode mode)
{
  BLI_assert(ELEM(mode, RNA_EQ_STRICT, RNA_EQ_UNSET_MATCH_ANY, RNA_EQ_UNSET_MATCH_NONE));

  PropertyRNAOrID prop_a, prop_b;

  rna_property_rna_or_id_get(prop, ptr_a, &prop_a);
  rna_property_rna_or_id_get(prop, ptr_b, &prop_b);

  return (rna_property_override_diff(bmain, &prop_a, &prop_b, NULL, 0, mode, NULL, 0, NULL) == 0);
}

bool RNA_struct_equals(Main *bmain, PointerRNA *ptr_a, PointerRNA *ptr_b, eRNACompareMode mode)
{
  CollectionPropertyIterator iter;
  PropertyRNA *iterprop;
  bool equals = true;

  if (ptr_a == NULL && ptr_b == NULL) {
    return true;
  }
  if (ptr_a == NULL || ptr_b == NULL) {
    return false;
  }
  if (ptr_a->type != ptr_b->type) {
    return false;
  }

  iterprop = RNA_struct_iterator_property(ptr_a->type);

  RNA_property_collection_begin(ptr_a, iterprop, &iter);
  for (; iter.valid; RNA_property_collection_next(&iter)) {
    PropertyRNA *prop = iter.ptr.data;

    if (!RNA_property_equals(bmain, ptr_a, ptr_b, prop, mode)) {
      equals = false;
      break;
    }
  }
  RNA_property_collection_end(&iter);

  return equals;
}

/* Low-level functions, also used by non-override RNA API like copy or equality check. */

/**
 * Generic RNA property diff function.
 *
 * \note about \a prop and \a prop_a/prop_b parameters:
 * the former is expected to be an 'un-resolved' one,
 * while the two later are expected to be fully resolved ones
 * (i.e. to be the IDProps when they should be, etc.).
 * When \a prop is given, \a prop_a and \a prop_b should always be NULL, and vice-versa.
 * This is necessary, because we cannot perform 'set/unset' checks on resolved properties
 * (unset IDProps would merely be NULL then).
 *
 * \note When there is no equality,
 * but we cannot determine an order (greater than/lesser than), we return 1.
 */
static int rna_property_override_diff(Main *bmain,
                                      PropertyRNAOrID *prop_a,
                                      PropertyRNAOrID *prop_b,
                                      const char *rna_path,
                                      const size_t rna_path_len,
                                      eRNACompareMode mode,
                                      IDOverrideLibrary *override,
                                      const eRNAOverrideMatch flags,
                                      eRNAOverrideMatchResult *r_report_flags)
{
  BLI_assert(!ELEM(NULL, prop_a, prop_b));

  if (prop_a->rnaprop->flag_override & PROPOVERRIDE_NO_COMPARISON ||
      prop_b->rnaprop->flag_override & PROPOVERRIDE_NO_COMPARISON) {
    return 0;
  }

  if (mode == RNA_EQ_UNSET_MATCH_ANY) {
    /* Unset properties are assumed to match anything. */
    if (!prop_a->is_set || !prop_b->is_set) {
      return 0;
    }
  }
  else if (mode == RNA_EQ_UNSET_MATCH_NONE) {
    /* Unset properties never match set properties. */
    if (prop_a->is_set != prop_b->is_set) {
      return 1;
    }
  }

  if (prop_a->is_idprop && ELEM(NULL, prop_a->idprop, prop_b->idprop)) {
    return (prop_a->idprop == prop_b->idprop) ? 0 : 1;
  }

  /* Check if we are working with arrays. */
  const bool is_array_a = prop_a->is_array;
  const bool is_array_b = prop_b->is_array;

  if (is_array_a != is_array_b) {
    /* Should probably never happen actually... */
    BLI_assert_unreachable();
    return is_array_a ? 1 : -1;
  }

  /* Get the length of the array to work with. */
  const uint len_a = prop_a->array_len;
  const uint len_b = prop_b->array_len;

  if (len_a != len_b) {
    /* Do not handle override in that case,
     * we do not support insertion/deletion from arrays for now. */
    return len_a > len_b ? 1 : -1;
  }

  if (is_array_a && len_a == 0) {
    /* Empty arrays, will happen in some case with dynamic ones. */
    return 0;
  }

  RNAPropOverrideDiff override_diff = NULL;
  /* Special case for IDProps, we use default callback then. */
  if (prop_a->is_idprop) {
    override_diff = rna_property_override_diff_default;
    if (!prop_b->is_idprop && prop_b->rnaprop->override_diff != override_diff) {
      override_diff = NULL;
    }
  }
  else if (prop_b->is_idprop) {
    override_diff = rna_property_override_diff_default;
    if (prop_a->rnaprop->override_diff != override_diff) {
      override_diff = NULL;
    }
  }
  else if (prop_a->rnaprop->override_diff == prop_b->rnaprop->override_diff) {
    override_diff = prop_a->rnaprop->override_diff;
    if (override_diff == NULL) {
      override_diff = rna_property_override_diff_default;
    }
  }

  if (override_diff == NULL) {
    CLOG_ERROR(&LOG,
               "'%s' gives unmatching or NULL RNA diff callbacks, should not happen (%d vs. %d)",
               rna_path ? rna_path : prop_a->identifier,
               !prop_a->is_idprop,
               !prop_b->is_idprop);
    BLI_assert_unreachable();
    return 1;
  }

  bool override_changed = false;
  eRNAOverrideMatch diff_flags = flags;
  if (!RNA_property_overridable_get(&prop_a->ptr, prop_a->rawprop) ||
      (!ELEM(RNA_property_type(prop_a->rawprop), PROP_POINTER, PROP_COLLECTION) &&
       !RNA_property_editable_flag(&prop_a->ptr, prop_a->rawprop))) {
    diff_flags &= ~RNA_OVERRIDE_COMPARE_CREATE;
  }
  const int diff = override_diff(bmain,
                                 prop_a,
                                 prop_b,
                                 mode,
                                 override,
                                 rna_path,
                                 rna_path_len,
                                 diff_flags,
                                 &override_changed);
  if (override_changed && r_report_flags) {
    *r_report_flags |= RNA_OVERRIDE_MATCH_RESULT_CREATED;
  }

  return diff;
}

/* Modify local data-block to make it ready for override application
 * (only needed for diff operations, where we use
 * the local data-block's data as second operand). */
static bool rna_property_override_operation_store(Main *bmain,
                                                  PointerRNA *ptr_local,
                                                  PointerRNA *ptr_reference,
                                                  PointerRNA *ptr_storage,
                                                  PropertyRNA *prop_local,
                                                  PropertyRNA *prop_reference,
                                                  PropertyRNA *prop_storage,
                                                  IDOverrideLibraryProperty *op)
{
  int len_local, len_reference, len_storage = 0;
  bool changed = false;

  if (ptr_storage == NULL) {
    return changed;
  }

  /* get the length of the array to work with */
  len_local = RNA_property_array_length(ptr_local, prop_local);
  len_reference = RNA_property_array_length(ptr_reference, prop_reference);
  if (prop_storage) {
    len_storage = RNA_property_array_length(ptr_storage, prop_storage);
  }

  if (len_local != len_reference || len_local != len_storage) {
    /* Do not handle override in that case,
     * we do not support insertion/deletion from arrays for now. */
    return changed;
  }

  RNAPropOverrideStore override_store = NULL;
  /* Special case for IDProps, we use default callback then. */
  if (prop_local->magic != RNA_MAGIC) {
    override_store = rna_property_override_store_default;
    if (prop_reference->magic == RNA_MAGIC && prop_reference->override_store != override_store) {
      override_store = NULL;
    }
  }
  else if (prop_reference->magic != RNA_MAGIC) {
    override_store = rna_property_override_store_default;
    if (prop_local->override_store != override_store) {
      override_store = NULL;
    }
  }
  else if (prop_local->override_store == prop_reference->override_store) {
    override_store = prop_local->override_store;
    if (override_store == NULL) {
      override_store = rna_property_override_store_default;
    }
  }

  if (ptr_storage != NULL && prop_storage->magic == RNA_MAGIC &&
      !ELEM(prop_storage->override_store, NULL, override_store)) {
    override_store = NULL;
  }

  if (override_store == NULL) {
    CLOG_ERROR(&LOG,
               "'%s' gives unmatching or NULL RNA store callbacks, should not happen (%d vs. %d)",
               op->rna_path,
               prop_local->magic == RNA_MAGIC,
               prop_reference->magic == RNA_MAGIC);
    BLI_assert_unreachable();
    return changed;
  }

  LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
    /* Only needed for diff operations. */
    if (!ELEM(opop->operation,
              IDOVERRIDE_LIBRARY_OP_ADD,
              IDOVERRIDE_LIBRARY_OP_SUBTRACT,
              IDOVERRIDE_LIBRARY_OP_MULTIPLY)) {
      continue;
    }

    if (override_store(bmain,
                       ptr_local,
                       ptr_reference,
                       ptr_storage,
                       prop_local,
                       prop_reference,
                       prop_storage,
                       len_local,
                       len_reference,
                       len_storage,
                       opop)) {
      changed = true;
    }
  }

  return changed;
}

static bool rna_property_override_operation_apply(Main *bmain,
                                                  PointerRNA *ptr_dst,
                                                  PointerRNA *ptr_src,
                                                  PointerRNA *ptr_storage,
                                                  PropertyRNA *prop_dst,
                                                  PropertyRNA *prop_src,
                                                  PropertyRNA *prop_storage,
                                                  PointerRNA *ptr_item_dst,
                                                  PointerRNA *ptr_item_src,
                                                  PointerRNA *ptr_item_storage,
                                                  IDOverrideLibraryPropertyOperation *opop)
{
  int len_dst, len_src, len_storage = 0;

  const short override_op = opop->operation;

  if (!BKE_lib_override_library_property_operation_operands_validate(
          opop, ptr_dst, ptr_src, ptr_storage, prop_dst, prop_src, prop_storage)) {
    return false;
  }

  if (override_op == IDOVERRIDE_LIBRARY_OP_NOOP) {
    return true;
  }

  RNAPropOverrideApply override_apply = NULL;
  /* Special case for IDProps, we use default callback then. */
  if (prop_dst->magic != RNA_MAGIC) {
    override_apply = rna_property_override_apply_default;
    if (prop_src->magic == RNA_MAGIC && !ELEM(prop_src->override_apply, NULL, override_apply)) {
      override_apply = NULL;
    }
  }
  else if (prop_src->magic != RNA_MAGIC) {
    override_apply = rna_property_override_apply_default;
    if (!ELEM(prop_dst->override_apply, NULL, override_apply)) {
      override_apply = NULL;
    }
  }
  else if (prop_dst->override_apply == prop_src->override_apply) {
    override_apply = prop_dst->override_apply;
    if (override_apply == NULL) {
      override_apply = rna_property_override_apply_default;
    }
  }

  if (ptr_storage && prop_storage->magic == RNA_MAGIC &&
      !ELEM(prop_storage->override_apply, NULL, override_apply)) {
    override_apply = NULL;
  }

  if (override_apply == NULL) {
    CLOG_ERROR(&LOG,
               "'%s' gives unmatching or NULL RNA apply callbacks, should not happen (%d vs. %d)",
               prop_dst->magic != RNA_MAGIC ? ((IDProperty *)prop_dst)->name :
                                              prop_dst->identifier,
               prop_dst->magic == RNA_MAGIC,
               prop_src->magic == RNA_MAGIC);
    BLI_assert_unreachable();
    return false;
  }

  /* get the length of the array to work with */
  len_dst = RNA_property_array_length(ptr_dst, prop_dst);
  len_src = RNA_property_array_length(ptr_src, prop_src);
  if (ptr_storage) {
    len_storage = RNA_property_array_length(ptr_storage, prop_storage);
  }

  if (len_dst != len_src || (ptr_storage && len_dst != len_storage)) {
    /* Do not handle override in that case,
     * we do not support insertion/deletion from arrays for now. */
    return false;
  }

  /* get and set the default values as appropriate for the various types */
  const bool success = override_apply(bmain,
                                      ptr_dst,
                                      ptr_src,
                                      ptr_storage,
                                      prop_dst,
                                      prop_src,
                                      prop_storage,
                                      len_dst,
                                      len_src,
                                      len_storage,
                                      ptr_item_dst,
                                      ptr_item_src,
                                      ptr_item_storage,
                                      opop);
  return success;
}

bool RNA_struct_override_matches(Main *bmain,
                                 PointerRNA *ptr_local,
                                 PointerRNA *ptr_reference,
                                 const char *root_path,
                                 const size_t root_path_len,
                                 IDOverrideLibrary *override,
                                 const eRNAOverrideMatch flags,
                                 eRNAOverrideMatchResult *r_report_flags)
{
  CollectionPropertyIterator iter;
  PropertyRNA *iterprop;
  bool matching = true;

  BLI_assert(ptr_local->type == ptr_reference->type);
  BLI_assert(ptr_local->owner_id && ptr_reference->owner_id);

  const bool ignore_non_overridable = (flags & RNA_OVERRIDE_COMPARE_IGNORE_NON_OVERRIDABLE) != 0;
  const bool ignore_overridden = (flags & RNA_OVERRIDE_COMPARE_IGNORE_OVERRIDDEN) != 0;
  const bool do_create = (flags & RNA_OVERRIDE_COMPARE_CREATE) != 0;
  const bool do_restore = (flags & RNA_OVERRIDE_COMPARE_RESTORE) != 0;

#ifdef DEBUG_OVERRIDE_TIMEIT
  static float _sum_time_global = 0.0f;
  static float _num_time_global = 0.0f;
  double _timeit_time_global;
  static float _sum_time_diffing = 0.0f;
  static float _delta_time_diffing = 0.0f;
  static int _num_delta_time_diffing = 0.0f;
  static float _num_time_diffing = 0.0f;
  double _timeit_time_diffing;

  if (!root_path) {
    _delta_time_diffing = 0.0f;
    _num_delta_time_diffing = 0;
    _timeit_time_global = PIL_check_seconds_timer();
  }
#endif

  if (ptr_local->owner_id == ptr_local->data && GS(ptr_local->owner_id->name) == ID_OB) {
    /* Our beloved pose's bone cross-data pointers. Usually, depsgraph evaluation would
     * ensure this is valid, but in some situations (like hidden collections etc.) this won't
     * be the case, so we need to take care of this ourselves.
     *
     * NOTE: Typically callers of this function (from BKE_lib_override area) will already have
     * ensured this. However, studio is still reporting sporadic, unreproducible crashes due to
     * invalid pose data, so think there are still some cases where some armatures are somehow
     * missing updates (possibly due to dependencies?). Since calling this function on same ID
     * several time is almost free, and safe even in a threaded context as long as it has been done
     * at least once first outside of threaded processing, we do it another time here. */
    Object *ob_local = (Object *)ptr_local->owner_id;
    if (ob_local->type == OB_ARMATURE) {
      Object *ob_reference = (Object *)ptr_local->owner_id->override_library->reference;
      BLI_assert(ob_local->data != NULL);
      BLI_assert(ob_reference->data != NULL);
      BKE_pose_ensure(bmain, ob_local, ob_local->data, true);
      BKE_pose_ensure(bmain, ob_reference, ob_reference->data, true);
    }
  }

  iterprop = RNA_struct_iterator_property(ptr_local->type);

  for (RNA_property_collection_begin(ptr_local, iterprop, &iter); iter.valid;
       RNA_property_collection_next(&iter)) {
    PropertyRNA *rawprop = iter.ptr.data;

    PropertyRNAOrID prop_local;
    PropertyRNAOrID prop_reference;

    rna_property_rna_or_id_get(rawprop, ptr_local, &prop_local);
    rna_property_rna_or_id_get(rawprop, ptr_reference, &prop_reference);

    BLI_assert(prop_local.rnaprop != NULL);
    BLI_assert(prop_local.rnaprop == prop_reference.rnaprop);
    BLI_assert(prop_local.is_idprop == prop_reference.is_idprop);

    if ((prop_local.is_idprop && prop_local.idprop == NULL) ||
        (prop_reference.is_idprop && prop_reference.idprop == NULL)) {
      continue;
    }

    if (ignore_non_overridable && !RNA_property_overridable_get(&prop_local.ptr, rawprop)) {
      continue;
    }

    if (!prop_local.is_idprop &&
        RNA_property_override_flag(prop_local.rnaprop) & PROPOVERRIDE_IGNORE) {
      continue;
    }

#if 0 /* This actually makes things slower, since it has to check for animation paths etc! */
    if (RNA_property_animated(ptr_local, prop_local)) {
      /* We cannot do anything here really, animation is some kind of dynamic overrides that has
       * precedence over static one... */
      continue;
    }
#endif

#define RNA_PATH_BUFFSIZE 8192

    char rna_path_buffer[RNA_PATH_BUFFSIZE];
    char *rna_path = rna_path_buffer;
    size_t rna_path_len = 0;

    /* XXX TODO: this will have to be refined to handle collections insertions, and array items. */
    if (root_path) {
      BLI_assert(strlen(root_path) == root_path_len);

      const char *prop_name = prop_local.identifier;
      const size_t prop_name_len = strlen(prop_name);

      /* Inlined building (significantly more efficient). */
      if (!prop_local.is_idprop) {
        rna_path_len = root_path_len + 1 + prop_name_len;
        if (rna_path_len >= RNA_PATH_BUFFSIZE) {
          rna_path = MEM_mallocN(rna_path_len + 1, __func__);
        }

        memcpy(rna_path, root_path, root_path_len);
        rna_path[root_path_len] = '.';
        memcpy(rna_path + root_path_len + 1, prop_name, prop_name_len);
        rna_path[rna_path_len] = '\0';
      }
      else {
        rna_path_len = root_path_len + 2 + prop_name_len + 2;
        if (rna_path_len >= RNA_PATH_BUFFSIZE) {
          rna_path = MEM_mallocN(rna_path_len + 1, __func__);
        }

        memcpy(rna_path, root_path, root_path_len);
        rna_path[root_path_len] = '[';
        rna_path[root_path_len + 1] = '"';
        memcpy(rna_path + root_path_len + 2, prop_name, prop_name_len);
        rna_path[root_path_len + 2 + prop_name_len] = '"';
        rna_path[root_path_len + 2 + prop_name_len + 1] = ']';
        rna_path[rna_path_len] = '\0';
      }
    }
    else {
      /* This is rather slow, but is not much called, so not really worth optimizing. */
      rna_path = RNA_path_from_ID_to_property(ptr_local, rawprop);
      if (rna_path != NULL) {
        rna_path_len = strlen(rna_path);
      }
    }
    if (rna_path == NULL) {
      continue;
    }

    CLOG_INFO(&LOG, 5, "Override Checking %s", rna_path);

    IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(override, rna_path);
    if (ignore_overridden && op != NULL) {
      BKE_lib_override_library_operations_tag(op, IDOVERRIDE_LIBRARY_TAG_UNUSED, false);

      if (rna_path != rna_path_buffer) {
        MEM_freeN(rna_path);
      }
      continue;
    }

#ifdef DEBUG_OVERRIDE_TIMEIT
    if (!root_path) {
      _timeit_time_diffing = PIL_check_seconds_timer();
    }
#endif

    eRNAOverrideMatchResult report_flags = 0;
    const int diff = rna_property_override_diff(bmain,
                                                &prop_local,
                                                &prop_reference,
                                                rna_path,
                                                rna_path_len,
                                                RNA_EQ_STRICT,
                                                override,
                                                flags,
                                                &report_flags);

#ifdef DEBUG_OVERRIDE_TIMEIT
    if (!root_path) {
      const float _delta_time = (float)(PIL_check_seconds_timer() - _timeit_time_diffing);
      _delta_time_diffing += _delta_time;
      _num_delta_time_diffing++;
    }
#endif

    matching = matching && diff == 0;
    if (r_report_flags) {
      *r_report_flags |= report_flags;
    }

    if (diff != 0) {
      /* XXX TODO: refine this for per-item overriding of arrays... */
      op = BKE_lib_override_library_property_find(override, rna_path);
      IDOverrideLibraryPropertyOperation *opop = op ? op->operations.first : NULL;

      if (op != NULL) {
        BKE_lib_override_library_operations_tag(op, IDOVERRIDE_LIBRARY_TAG_UNUSED, false);
      }

      if (do_restore && (report_flags & RNA_OVERRIDE_MATCH_RESULT_CREATED) == 0) {
        /* We are allowed to restore to reference's values. */
        if (ELEM(NULL, op, opop) || opop->operation == IDOVERRIDE_LIBRARY_OP_NOOP) {
          /* We should restore that property to its reference value */
          if (RNA_property_editable(ptr_local, rawprop)) {
            IDOverrideLibraryPropertyOperation opop_tmp = {
                .operation = IDOVERRIDE_LIBRARY_OP_REPLACE,
                .subitem_reference_index = -1,
                .subitem_local_index = -1,
            };
            rna_property_override_operation_apply(bmain,
                                                  ptr_local,
                                                  ptr_reference,
                                                  NULL,
                                                  rawprop,
                                                  rawprop,
                                                  NULL,
                                                  NULL,
                                                  NULL,
                                                  NULL,
                                                  &opop_tmp);
            if (r_report_flags) {
              *r_report_flags |= RNA_OVERRIDE_MATCH_RESULT_RESTORED;
            }
          }
          else {
            /* Too noisy for now, this triggers on runtime props like transform matrices etc. */
#if 0
            BLI_assert_msg(0, "We have differences between reference and "
                       "overriding data on non-editable property.");
#endif
            matching = false;
          }
        }
      }
      else if ((report_flags & RNA_OVERRIDE_MATCH_RESULT_CREATED) == 0 && ELEM(NULL, op, opop)) {
        /* This property is not overridden, and differs from reference, so we have no match. */
        matching = false;
        if (!(do_create || do_restore)) {
          /* Since we have no 'changing' action allowed, we can break here. */
          if (rna_path != rna_path_buffer) {
            MEM_freeN(rna_path);
          }
          break;
        }
      }
    }

    if (rna_path != rna_path_buffer) {
      MEM_freeN(rna_path);
    }
#undef RNA_PATH_BUFFSIZE
  }
  RNA_property_collection_end(&iter);

#ifdef DEBUG_OVERRIDE_TIMEIT
  if (!root_path) {
    const float _delta_time = (float)(PIL_check_seconds_timer() - _timeit_time_global);
    _sum_time_global += _delta_time;
    _num_time_global++;
    _sum_time_diffing += _delta_time_diffing;
    _num_time_diffing++;
    printf("ID: %s\n", ((ID *)ptr_local->owner_id)->name);
    printf("time end      (%s): %.6f\n", __func__, _delta_time);
    printf("time averaged (%s): %.6f (total: %.6f, in %d runs)\n",
           __func__,
           (_sum_time_global / _num_time_global),
           _sum_time_global,
           (int)_num_time_global);
    printf("diffing time end      (%s): %.6f (in %d runs)\n",
           __func__,
           _delta_time_diffing,
           _num_delta_time_diffing);
    printf("diffing time averaged (%s): %.6f (total: %.6f, in %d runs)\n",
           __func__,
           (_sum_time_diffing / _num_time_diffing),
           _sum_time_diffing,
           (int)_num_time_diffing);
  }
#endif

  return matching;
}

bool RNA_struct_override_store(Main *bmain,
                               PointerRNA *ptr_local,
                               PointerRNA *ptr_reference,
                               PointerRNA *ptr_storage,
                               IDOverrideLibrary *override)
{
  bool changed = false;

#ifdef DEBUG_OVERRIDE_TIMEIT
  TIMEIT_START_AVERAGED(RNA_struct_override_store);
#endif
  LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &override->properties) {
    /* Simplified for now! */
    PointerRNA data_reference, data_local;
    PropertyRNA *prop_reference, *prop_local;

    if (RNA_path_resolve_property(ptr_local, op->rna_path, &data_local, &prop_local) &&
        RNA_path_resolve_property(ptr_reference, op->rna_path, &data_reference, &prop_reference)) {
      PointerRNA data_storage;
      PropertyRNA *prop_storage = NULL;

      /* It is totally OK if this does not success,
       * only a subset of override operations actually need storage. */
      if (ptr_storage && (ptr_storage->owner_id != NULL)) {
        RNA_path_resolve_property(ptr_storage, op->rna_path, &data_storage, &prop_storage);
      }

      if (rna_property_override_operation_store(bmain,
                                                &data_local,
                                                &data_reference,
                                                &data_storage,
                                                prop_reference,
                                                prop_local,
                                                prop_storage,
                                                op)) {
        changed = true;
      }
    }
  }
#ifdef DEBUG_OVERRIDE_TIMEIT
  TIMEIT_END_AVERAGED(RNA_struct_override_store);
#endif

  return changed;
}

static void rna_porperty_override_collection_subitem_lookup(
    PointerRNA *ptr_dst,
    PointerRNA *ptr_src,
    PointerRNA *ptr_storage,
    PropertyRNA *prop_dst,
    PropertyRNA *prop_src,
    PropertyRNA *prop_storage,
    PointerRNA **r_ptr_item_dst,
    PointerRNA **r_ptr_item_src,
    PointerRNA **r_ptr_item_storage,
    PointerRNA *private_ptr_item_dst,
    PointerRNA *private_ptr_item_src,
    PointerRNA *private_ptr_item_storage,
    IDOverrideLibraryProperty *op,
    IDOverrideLibraryPropertyOperation *opop)
{
  if ((RNA_property_type(prop_dst) != PROP_COLLECTION ||
       RNA_property_type(prop_src) != PROP_COLLECTION ||
       (prop_storage != NULL && RNA_property_type(prop_storage) != PROP_COLLECTION)) ||
      (opop->subitem_local_name == NULL && opop->subitem_reference_name == NULL &&
       opop->subitem_local_index == -1 && opop->subitem_reference_index == -1)) {
    return;
  }

  RNA_POINTER_INVALIDATE(private_ptr_item_dst);
  RNA_POINTER_INVALIDATE(private_ptr_item_src);
  if (prop_storage != NULL) {
    RNA_POINTER_INVALIDATE(private_ptr_item_storage);
  }
  if (opop->subitem_local_name != NULL) {
    RNA_property_collection_lookup_string(
        ptr_src, prop_src, opop->subitem_local_name, private_ptr_item_src);
    if (opop->subitem_reference_name != NULL &&
        RNA_property_collection_lookup_string(
            ptr_dst, prop_dst, opop->subitem_reference_name, private_ptr_item_dst)) {
      /* This is rather fragile, but the fact that local override IDs may have a different name
       * than their linked reference makes it necessary.
       * Basically, here we are considering that if we cannot find the original linked ID in
       * the local override we are (re-)applying the operations, then it may be because some of
       * those operations have already been applied, and we may already have the local ID
       * pointer we want to set.
       * This happens e.g. during re-sync of an override, since we have already remapped all ID
       * pointers to their expected values.
       * In that case we simply try to get the property from the local expected name. */
    }
    else {
      RNA_property_collection_lookup_string(
          ptr_dst, prop_dst, opop->subitem_local_name, private_ptr_item_dst);
    }
  }
  else if (opop->subitem_reference_name != NULL) {
    RNA_property_collection_lookup_string(
        ptr_src, prop_src, opop->subitem_reference_name, private_ptr_item_src);
    RNA_property_collection_lookup_string(
        ptr_dst, prop_dst, opop->subitem_reference_name, private_ptr_item_dst);
  }
  else if (opop->subitem_local_index != -1) {
    RNA_property_collection_lookup_int(
        ptr_src, prop_src, opop->subitem_local_index, private_ptr_item_src);
    if (opop->subitem_reference_index != -1) {
      RNA_property_collection_lookup_int(
          ptr_dst, prop_dst, opop->subitem_reference_index, private_ptr_item_dst);
    }
    else {
      RNA_property_collection_lookup_int(
          ptr_dst, prop_dst, opop->subitem_local_index, private_ptr_item_dst);
    }
  }
  else if (opop->subitem_reference_index != -1) {
    RNA_property_collection_lookup_int(
        ptr_src, prop_src, opop->subitem_reference_index, private_ptr_item_src);
    RNA_property_collection_lookup_int(
        ptr_dst, prop_dst, opop->subitem_reference_index, private_ptr_item_dst);
  }
  if (prop_storage != NULL) {
    if (opop->subitem_local_name != NULL) {
      RNA_property_collection_lookup_string(
          ptr_storage, prop_storage, opop->subitem_local_name, private_ptr_item_storage);
    }
    else if (opop->subitem_reference_name != NULL) {
      RNA_property_collection_lookup_string(
          ptr_storage, prop_storage, opop->subitem_reference_name, private_ptr_item_storage);
    }
    else if (opop->subitem_local_index != -1) {
      RNA_property_collection_lookup_int(
          ptr_storage, prop_storage, opop->subitem_local_index, private_ptr_item_storage);
    }
    else if (opop->subitem_reference_index != -1) {
      RNA_property_collection_lookup_int(
          ptr_storage, prop_storage, opop->subitem_reference_index, private_ptr_item_storage);
    }
  }
  *r_ptr_item_dst = private_ptr_item_dst;
  *r_ptr_item_src = private_ptr_item_src;
  if (prop_storage != NULL) {
    *r_ptr_item_storage = private_ptr_item_storage;
  }

  /* Note that there is no reason to report in case no item is expected, i.e. in case subitem name
   * and index are invalid. This can often happen when inserting new items (constraint,
   * modifier...) in a collection that supports it. */
  if ((*r_ptr_item_dst)->type == NULL &&
      ((opop->subitem_reference_name != NULL && opop->subitem_reference_name[0] != '\0') ||
       opop->subitem_reference_index != -1)) {
    CLOG_INFO(&LOG,
              2,
              "Failed to find destination sub-item '%s' (%d) of '%s' in new override data '%s'",
              opop->subitem_reference_name,
              opop->subitem_reference_index,
              op->rna_path,
              ptr_dst->owner_id->name);
  }
  if ((*r_ptr_item_src)->type == NULL &&
      ((opop->subitem_local_name != NULL && opop->subitem_local_name[0] != '\0') ||
       opop->subitem_local_index != -1)) {
    CLOG_INFO(&LOG,
              2,
              "Failed to find source sub-item '%s' (%d) of '%s' in old override data '%s'",
              opop->subitem_local_name,
              opop->subitem_local_index,
              op->rna_path,
              ptr_src->owner_id->name);
  }
}

static void rna_property_override_check_resync(Main *bmain,
                                               PointerRNA *ptr_dst,
                                               PointerRNA *ptr_src,
                                               PointerRNA *ptr_item_dst,
                                               PointerRNA *ptr_item_src)
{
  ID *id_owner_src = rna_property_override_property_real_id_owner(bmain, ptr_src, NULL, NULL);
  ID *id_owner_dst = rna_property_override_property_real_id_owner(bmain, ptr_dst, NULL, NULL);
  ID *id_src = rna_property_override_property_real_id_owner(bmain, ptr_item_src, NULL, NULL);
  ID *id_dst = rna_property_override_property_real_id_owner(bmain, ptr_item_dst, NULL, NULL);

  BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_owner_src));

  /* If the owner ID is not part of an override hierarchy, there is no possible resync. */
  if (id_owner_src->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY) {
    return;
  }

  /* If `id_src` is not a liboverride, we cannot perform any further 'need resync' checks from
   * here. */
  if (id_src != NULL && !ID_IS_OVERRIDE_LIBRARY_REAL(id_src)) {
    return;
  }

  if (/* We might be in a case where id_dst has already been processed and its usages
       * remapped to its new local override. In that case overrides and linked data
       * are always properly matching. */
      id_src != id_dst &&
      /* If one of the pointers is NULL and not the other, we are in a non-matching case. */
      (ELEM(NULL, id_src, id_dst) ||
       /* If `id_dst` is not from same lib as id_src, and linked reference ID of `id_src` is not
        * `id_dst`, we are in a non-matching case. */
       (id_dst->lib != id_src->lib && id_src->override_library->reference != id_dst) ||
       /* If `id_dst` is from same lib as id_src, and is not same as `id_owner`, we are in a
        * non-matching case.
        *
        * NOTE: Here we are testing if `id_owner` is referencing itself, in that case the new
        * override copy generated by `BKE_lib_override_library_update` will already have its
        * self-references updated to itself, instead of still pointing to its linked source. */
       (id_dst->lib == id_src->lib && id_dst != id_owner_dst))) {
    id_owner_dst->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
    if (ID_IS_LINKED(id_owner_src)) {
      id_owner_src->lib->tag |= LIBRARY_TAG_RESYNC_REQUIRED;
    }
    CLOG_INFO(&LOG, 3, "Local override %s detected as needing resync", id_owner_dst->name);
  }
}

static void rna_property_override_apply_ex(Main *bmain,
                                           PointerRNA *ptr_dst,
                                           PointerRNA *ptr_src,
                                           PointerRNA *ptr_storage,
                                           PropertyRNA *prop_dst,
                                           PropertyRNA *prop_src,
                                           PropertyRNA *prop_storage,
                                           PointerRNA *ptr_item_dst,
                                           PointerRNA *ptr_item_src,
                                           PointerRNA *ptr_item_storage,
                                           IDOverrideLibraryProperty *op,
                                           const bool do_insert)
{
  LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
    if (!do_insert != !ELEM(opop->operation,
                            IDOVERRIDE_LIBRARY_OP_INSERT_AFTER,
                            IDOVERRIDE_LIBRARY_OP_INSERT_BEFORE)) {
      if (!do_insert) {
        CLOG_INFO(&LOG, 5, "Skipping insert override operations in first pass (%s)", op->rna_path);
      }
      continue;
    }

    /* NOTE: will have to think about putting that logic into its own function maybe?
     * Would be nice to have it in a single place...
     * Note that here, src is the local saved ID, and dst is a copy of the linked ID (since we use
     * local ID as storage to apply local changes on top of a clean copy of the linked data). */
    PointerRNA private_ptr_item_dst, private_ptr_item_src, private_ptr_item_storage;
    rna_porperty_override_collection_subitem_lookup(ptr_dst,
                                                    ptr_src,
                                                    ptr_storage,
                                                    prop_dst,
                                                    prop_src,
                                                    prop_storage,
                                                    &ptr_item_dst,
                                                    &ptr_item_src,
                                                    &ptr_item_storage,
                                                    &private_ptr_item_dst,
                                                    &private_ptr_item_src,
                                                    &private_ptr_item_storage,
                                                    op,
                                                    opop);

    if (!rna_property_override_operation_apply(bmain,
                                               ptr_dst,
                                               ptr_src,
                                               ptr_storage,
                                               prop_dst,
                                               prop_src,
                                               prop_storage,
                                               ptr_item_dst,
                                               ptr_item_src,
                                               ptr_item_storage,
                                               opop)) {
      CLOG_INFO(&LOG,
                4,
                "Failed to apply '%s' override operation on %s\n",
                op->rna_path,
                ptr_src->owner_id->name);
    }
  }
}

void RNA_struct_override_apply(Main *bmain,
                               PointerRNA *ptr_dst,
                               PointerRNA *ptr_src,
                               PointerRNA *ptr_storage,
                               IDOverrideLibrary *override,
                               const eRNAOverrideApplyFlag flag)
{
#ifdef DEBUG_OVERRIDE_TIMEIT
  TIMEIT_START_AVERAGED(RNA_struct_override_apply);
#endif
  /* NOTE: Applying insert operations in a separate pass is mandatory.
   * We could optimize this later, but for now, as inefficient as it is,
   * don't think this is a critical point.
   */
  bool do_insert = false;
  for (int i = 0; i < 2; i++, do_insert = true) {
    LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &override->properties) {
      /* Simplified for now! */
      PointerRNA data_src, data_dst;
      PointerRNA data_item_src, data_item_dst;
      PropertyRNA *prop_src, *prop_dst;

      if (RNA_path_resolve_property_and_item_pointer(
              ptr_dst, op->rna_path, &data_dst, &prop_dst, &data_item_dst) &&
          RNA_path_resolve_property_and_item_pointer(
              ptr_src, op->rna_path, &data_src, &prop_src, &data_item_src)) {
        PointerRNA data_storage, data_item_storage;
        PropertyRNA *prop_storage = NULL;

        /* It is totally OK if this does not success,
         * only a subset of override operations actually need storage. */
        if (ptr_storage && (ptr_storage->owner_id != NULL)) {
          RNA_path_resolve_property_and_item_pointer(
              ptr_storage, op->rna_path, &data_storage, &prop_storage, &data_item_storage);
        }

        /* Check if an overridden ID pointer supposed to be in sync with linked data gets out of
         * sync. */
        if ((ptr_dst->owner_id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0) {
          if (op->rna_prop_type == PROP_POINTER &&
              (((IDOverrideLibraryPropertyOperation *)op->operations.first)->flag &
               IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) != 0) {
            BLI_assert(RNA_struct_is_ID(RNA_property_pointer_type(&data_src, prop_src)));
            BLI_assert(ptr_src->owner_id ==
                       rna_property_override_property_real_id_owner(bmain, &data_src, NULL, NULL));
            BLI_assert(ptr_dst->owner_id ==
                       rna_property_override_property_real_id_owner(bmain, &data_dst, NULL, NULL));

            PointerRNA prop_ptr_src = RNA_property_pointer_get(&data_src, prop_src);
            PointerRNA prop_ptr_dst = RNA_property_pointer_get(&data_dst, prop_dst);
            rna_property_override_check_resync(
                bmain, ptr_dst, ptr_src, &prop_ptr_dst, &prop_ptr_src);
          }
          else if (op->rna_prop_type == PROP_COLLECTION) {
            if (RNA_struct_is_ID(RNA_property_pointer_type(&data_src, prop_src))) {
              BLI_assert(ptr_src->owner_id == rna_property_override_property_real_id_owner(
                                                  bmain, &data_src, NULL, NULL));
              BLI_assert(ptr_dst->owner_id == rna_property_override_property_real_id_owner(
                                                  bmain, &data_dst, NULL, NULL));

              LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
                if ((opop->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) {
                  continue;
                }

                PointerRNA *ptr_item_dst, *ptr_item_src;
                PointerRNA private_ptr_item_dst, private_ptr_item_src;
                rna_porperty_override_collection_subitem_lookup(ptr_dst,
                                                                ptr_src,
                                                                NULL,
                                                                prop_dst,
                                                                prop_src,
                                                                NULL,
                                                                &ptr_item_dst,
                                                                &ptr_item_src,
                                                                NULL,
                                                                &private_ptr_item_dst,
                                                                &private_ptr_item_src,
                                                                NULL,
                                                                op,
                                                                opop);

                rna_property_override_check_resync(
                    bmain, ptr_dst, ptr_src, ptr_item_dst, ptr_item_src);
              }
            }
          }
        }

        /* Workaround for older broken overrides, we then assume that non-matching ID pointers
         * override operations that replace a non-NULL value are 'mistakes', and ignore (do not
         * apply) them. */
        if ((flag & RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS) != 0 &&
            op->rna_prop_type == PROP_POINTER &&
            (((IDOverrideLibraryPropertyOperation *)op->operations.first)->flag &
             IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) {
          BLI_assert(ptr_src->owner_id ==
                     rna_property_override_property_real_id_owner(bmain, &data_src, NULL, NULL));
          BLI_assert(ptr_dst->owner_id ==
                     rna_property_override_property_real_id_owner(bmain, &data_dst, NULL, NULL));

          PointerRNA prop_ptr_dst = RNA_property_pointer_get(&data_dst, prop_dst);
          if (prop_ptr_dst.type != NULL && RNA_struct_is_ID(prop_ptr_dst.type)) {
#ifndef NDEBUG
            PointerRNA prop_ptr_src = RNA_property_pointer_get(&data_src, prop_src);
            BLI_assert(prop_ptr_src.type == NULL || RNA_struct_is_ID(prop_ptr_src.type));
#endif
            ID *id_dst = rna_property_override_property_real_id_owner(
                bmain, &prop_ptr_dst, NULL, NULL);

            if (id_dst != NULL) {
              CLOG_INFO(&LOG,
                        4,
                        "%s: Ignoring local override on ID pointer property '%s', as requested by "
                        "RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS flag",
                        ptr_dst->owner_id->name,
                        op->rna_path);
              continue;
            }
          }
        }

        rna_property_override_apply_ex(bmain,
                                       &data_dst,
                                       &data_src,
                                       prop_storage ? &data_storage : NULL,
                                       prop_dst,
                                       prop_src,
                                       prop_storage,
                                       &data_item_dst,
                                       &data_item_src,
                                       prop_storage ? &data_item_storage : NULL,
                                       op,
                                       do_insert);
      }
      else {
        CLOG_INFO(&LOG,
                  4,
                  "Failed to apply library override operation to '%s.%s' "
                  "(could not resolve some properties, local:  %d, override: %d)",
                  ((ID *)ptr_src->owner_id)->name,
                  op->rna_path,
                  RNA_path_resolve_property(ptr_dst, op->rna_path, &data_dst, &prop_dst),
                  RNA_path_resolve_property(ptr_src, op->rna_path, &data_src, &prop_src));
      }
    }
  }

  /* Some cases (like point caches) may require additional post-processing. */
  if (RNA_struct_is_a(ptr_dst->type, &RNA_ID)) {
    ID *id_dst = ptr_dst->data;
    ID *id_src = ptr_src->data;
    const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id_dst);
    if (id_type->lib_override_apply_post != NULL) {
      id_type->lib_override_apply_post(id_dst, id_src);
    }
  }

#ifdef DEBUG_OVERRIDE_TIMEIT
  TIMEIT_END_AVERAGED(RNA_struct_override_apply);
#endif
}

IDOverrideLibraryProperty *RNA_property_override_property_find(Main *bmain,
                                                               PointerRNA *ptr,
                                                               PropertyRNA *prop,
                                                               ID **r_owner_id)
{
  char *rna_path;

  *r_owner_id = rna_property_override_property_real_id_owner(bmain, ptr, prop, &rna_path);
  if (rna_path != NULL) {
    IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(
        (*r_owner_id)->override_library, rna_path);
    MEM_freeN(rna_path);
    return op;
  }
  return NULL;
}

IDOverrideLibraryProperty *RNA_property_override_property_get(Main *bmain,
                                                              PointerRNA *ptr,
                                                              PropertyRNA *prop,
                                                              bool *r_created)
{
  char *rna_path;

  if (r_created != NULL) {
    *r_created = false;
  }

  ID *id = rna_property_override_property_real_id_owner(bmain, ptr, prop, &rna_path);
  if (rna_path != NULL) {
    IDOverrideLibraryProperty *op = BKE_lib_override_library_property_get(
        id->override_library, rna_path, r_created);
    MEM_freeN(rna_path);
    return op;
  }
  return NULL;
}

IDOverrideLibraryPropertyOperation *RNA_property_override_property_operation_find(
    Main *bmain,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const int index,
    const bool strict,
    bool *r_strict)
{
  ID *owner_id;
  IDOverrideLibraryProperty *op = RNA_property_override_property_find(bmain, ptr, prop, &owner_id);

  if (!op) {
    return NULL;
  }

  return BKE_lib_override_library_property_operation_find(
      op, NULL, NULL, index, index, strict, r_strict);
}

IDOverrideLibraryPropertyOperation *RNA_property_override_property_operation_get(
    Main *bmain,
    PointerRNA *ptr,
    PropertyRNA *prop,
    const short operation,
    const int index,
    const bool strict,
    bool *r_strict,
    bool *r_created)
{
  if (r_created != NULL) {
    *r_created = false;
  }

  IDOverrideLibraryProperty *op = RNA_property_override_property_get(bmain, ptr, prop, NULL);

  if (!op) {
    return NULL;
  }

  return BKE_lib_override_library_property_operation_get(
      op, operation, NULL, NULL, index, index, strict, r_strict, r_created);
}

eRNAOverrideStatus RNA_property_override_library_status(Main *bmain,
                                                        PointerRNA *ptr,
                                                        PropertyRNA *prop,
                                                        const int index)
{
  uint override_status = 0;

  if (!ptr || !prop || !ptr->owner_id || !ID_IS_OVERRIDE_LIBRARY(ptr->owner_id)) {
    return override_status;
  }

  if (RNA_property_overridable_get(ptr, prop) && RNA_property_editable_flag(ptr, prop)) {
    override_status |= RNA_OVERRIDE_STATUS_OVERRIDABLE;
  }

  IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_find(
      bmain, ptr, prop, index, false, NULL);
  if (opop != NULL) {
    override_status |= RNA_OVERRIDE_STATUS_OVERRIDDEN;
    if (opop->flag & IDOVERRIDE_LIBRARY_FLAG_MANDATORY) {
      override_status |= RNA_OVERRIDE_STATUS_MANDATORY;
    }
    if (opop->flag & IDOVERRIDE_LIBRARY_FLAG_LOCKED) {
      override_status |= RNA_OVERRIDE_STATUS_LOCKED;
    }
  }

  return override_status;
}
