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

/** \file
 * \ingroup bke
 * Implementation of generic geometry attributes management. This is built
 * on top of CustomData, which manages individual domains.
 */

#include <cstring>
#include <optional>

#include "BKE_anonymous_attribute_id.hh"
#include "MEM_guardedalloc.h"

#include "DNA_ID.h"
#include "DNA_curves_types.h"
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"

#include "BLI_index_range.hh"
#include "BLI_string.h"
#include "BLI_string_utils.hh"

#include "BLT_translation.hh"

#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_report.hh"

#include <fmt/format.h>

namespace blender {

using bke::AttrDomain;

AttributeOwner AttributeOwner::from_id(ID *id)
{
  if (id == nullptr) {
    return {};
  }
  switch (GS(id->name)) {
    case ID_ME:
      return AttributeOwner(AttributeOwnerType::Mesh, id);
    case ID_PT:
      return AttributeOwner(AttributeOwnerType::PointCloud, id);
    case ID_CV:
      return AttributeOwner(AttributeOwnerType::Curves, id);
    case ID_GP:
      return AttributeOwner(AttributeOwnerType::GreasePencil, id);
    default:
      return {};
  }
}

AttributeOwnerType AttributeOwner::type() const
{
  return type_;
}

bool AttributeOwner::is_valid() const
{
  return ptr_ != nullptr;
}

Mesh *AttributeOwner::get_mesh() const
{
  BLI_assert(this->is_valid());
  BLI_assert(type_ == AttributeOwnerType::Mesh);
  return reinterpret_cast<Mesh *>(ptr_);
}

PointCloud *AttributeOwner::get_pointcloud() const
{
  BLI_assert(this->is_valid());
  BLI_assert(type_ == AttributeOwnerType::PointCloud);
  return reinterpret_cast<PointCloud *>(ptr_);
}

Curves *AttributeOwner::get_curves() const
{
  BLI_assert(this->is_valid());
  BLI_assert(type_ == AttributeOwnerType::Curves);
  return reinterpret_cast<Curves *>(ptr_);
}

GreasePencil *AttributeOwner::get_grease_pencil() const
{
  BLI_assert(this->is_valid());
  BLI_assert(type_ == AttributeOwnerType::GreasePencil);
  return reinterpret_cast<GreasePencil *>(ptr_);
}

GreasePencilDrawing *AttributeOwner::get_grease_pencil_drawing() const
{
  BLI_assert(this->is_valid());
  BLI_assert(type_ == AttributeOwnerType::GreasePencilDrawing);
  return reinterpret_cast<GreasePencilDrawing *>(ptr_);
}

bke::AttributeStorage *AttributeOwner::get_storage() const
{
  switch (type_) {
    case AttributeOwnerType::Mesh:
      return &this->get_mesh()->attribute_storage.wrap();
    case AttributeOwnerType::PointCloud:
      return &this->get_pointcloud()->attribute_storage.wrap();
    case AttributeOwnerType::Curves:
      return &this->get_curves()->geometry.attribute_storage.wrap();
    case AttributeOwnerType::GreasePencil:
      return &this->get_grease_pencil()->attribute_storage.wrap();
    case AttributeOwnerType::GreasePencilDrawing:
      return &this->get_grease_pencil_drawing()->geometry.attribute_storage.wrap();
  }
  BLI_assert(false);
  return nullptr;
}

std::optional<bke::MutableAttributeAccessor> AttributeOwner::get_accessor() const
{
  switch (type_) {
    case AttributeOwnerType::Mesh:
      /* The attribute API isn't implemented for BMesh, so edit mode meshes are not supported. */
      BLI_assert(this->get_mesh()->runtime->edit_mesh == nullptr);
      return this->get_mesh()->attributes_for_write();
    case AttributeOwnerType::PointCloud:
      return this->get_pointcloud()->attributes_for_write();
    case AttributeOwnerType::Curves:
      return this->get_curves()->geometry.wrap().attributes_for_write();
    case AttributeOwnerType::GreasePencil:
      return this->get_grease_pencil()->attributes_for_write();
    case AttributeOwnerType::GreasePencilDrawing:
      return this->get_grease_pencil_drawing()->geometry.wrap().attributes_for_write();
  }
  BLI_assert(false);
  return std::nullopt;
}

struct DomainInfo {
  CustomData *customdata = nullptr;
  int length = 0;
};

static std::array<DomainInfo, ATTR_DOMAIN_NUM> get_domains(BMesh *bm)
{
  std::array<DomainInfo, ATTR_DOMAIN_NUM> info;
  info[int(AttrDomain::Point)].customdata = &bm->vdata;
  info[int(AttrDomain::Point)].length = bm->totvert;
  info[int(AttrDomain::Edge)].customdata = &bm->edata;
  info[int(AttrDomain::Edge)].length = bm->totedge;
  info[int(AttrDomain::Corner)].customdata = &bm->ldata;
  info[int(AttrDomain::Corner)].length = bm->totloop;
  info[int(AttrDomain::Face)].customdata = &bm->pdata;
  info[int(AttrDomain::Face)].length = bm->totface;
  return info;
}

static bool bke_attribute_rename_if_exists(AttributeOwner &owner,
                                           const StringRef old_name,
                                           const StringRef new_name,
                                           ReportList *reports)
{
  const bke::AttributeStorage &storage = *owner.get_storage();
  if (!storage.lookup(old_name)) {
    return false;
  }
  return BKE_attribute_rename(owner, old_name, new_name, reports);
}

static bool bke_attribute_rename_if_exists(AttributeOwner &owner,
                                           BMesh &bm,
                                           const StringRef old_name,
                                           const StringRef new_name,
                                           ReportList *reports)
{
  BMDataLayerLookup attr = BM_data_layer_lookup(bm, old_name);
  if (!attr) {
    return false;
  }
  return BKE_attribute_rename(owner, old_name, new_name, reports);
}

static bool name_valid_for_builtin_domain_and_type(const bke::AttributeAccessor attributes,
                                                   const StringRef name,
                                                   const AttrDomain domain,
                                                   const bke::AttrType data_type,
                                                   ReportList *reports)
{
  if (const std::optional metadata = attributes.get_builtin_domain_and_type(name)) {
    if (domain != metadata->domain) {
      BKE_reportf(reports,
                  RPT_ERROR,
                  "Domain unsupported for \"%s\" attribute",
                  std::string(name).c_str());
      return false;
    }
    if (data_type != metadata->data_type) {
      BKE_reportf(
          reports, RPT_ERROR, "Type unsupported for \"%s\" attribute", std::string(name).c_str());
      return false;
    }
  }
  return true;
}

static bool mesh_attribute_valid(const Mesh &mesh,
                                 const StringRef name,
                                 const AttrDomain domain,
                                 const bke::AttrType data_type,
                                 ReportList *reports)
{
  if (mesh.runtime->edit_mesh) {
    if (BM_attribute_stored_in_bmesh_builtin(name)) {
      BKE_report(reports, RPT_ERROR, "Unable to create attribute in edit mode");
      return false;
    }
  }
  if (!name_valid_for_builtin_domain_and_type(mesh.attributes(), name, domain, data_type, reports))
  {
    return false;
  }
  return true;
}

bool BKE_attribute_rename(AttributeOwner &owner,
                          const StringRef old_name,
                          const StringRef new_name,
                          ReportList *reports)
{
  if (BKE_attribute_required(owner, old_name)) {
    BLI_assert_msg(0, "Required attribute name is not editable");
    return false;
  }
  if (new_name.is_empty()) {
    BKE_report(reports, RPT_ERROR, "Attribute name cannot be empty");
    return false;
  }

  if (owner.type() == AttributeOwnerType::Mesh) {
    Mesh *mesh = owner.get_mesh();
    if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
      /* NOTE: Checking if the new name matches the old name only makes sense when the name
       * is clamped to its maximum length, otherwise assigning an over-long name multiple times
       * will add `.001` suffix unnecessarily. */
      {
        const int new_name_maxncpy = CustomData_name_maxncpy_calc(new_name);
        /* NOTE: A function that performs a clamped comparison without copying would be handy. */
        char new_name_clamped[MAX_CUSTOMDATA_LAYER_NAME];
        new_name.copy_utf8_truncated(new_name_clamped, new_name_maxncpy);
        if (old_name == new_name_clamped) {
          return false;
        }
      }

      BMDataLayerLookup attr = BM_data_layer_lookup(*em->bm, old_name);
      if (!attr) {
        BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry");
        return false;
      }

      if (!mesh_attribute_valid(*mesh, new_name, attr.domain, attr.type, reports)) {
        return false;
      }

      std::string result_name = BKE_attribute_calc_unique_name(owner, new_name);

      if (attr.type == bke::AttrType::Float2) {
        /* Rename UV sub-attributes. */
        char buffer_src[MAX_CUSTOMDATA_LAYER_NAME];
        char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME];
        bke_attribute_rename_if_exists(owner,
                                       *em->bm,
                                       BKE_uv_map_pin_name_get(old_name, buffer_src),
                                       BKE_uv_map_pin_name_get(result_name, buffer_dst),
                                       reports);
      }

      if (old_name == BKE_id_attributes_active_color_name(&mesh->id)) {
        BKE_id_attributes_active_color_set(&mesh->id, result_name);
      }
      if (old_name == BKE_id_attributes_default_color_name(&mesh->id)) {
        BKE_id_attributes_default_color_set(&mesh->id, result_name);
      }

      StringRef(result_name).copy_utf8_truncated(const_cast<CustomDataLayer *>(attr.layer)->name);

      return true;
    }
  }

  bke::AttributeStorage &attributes = *owner.get_storage();
  bke::Attribute *attr = attributes.lookup(old_name);
  if (!attr) {
    BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry");
    return false;
  }

  if (owner.type() == AttributeOwnerType::Curves) {
    Curves *curves = owner.get_curves();
    if (!name_valid_for_builtin_domain_and_type(curves->geometry.wrap().attributes(),
                                                new_name,
                                                attr->domain(),
                                                attr->data_type(),
                                                reports))
    {
      return false;
    }
  }
  else if (owner.type() == AttributeOwnerType::Mesh) {
    Mesh *mesh = owner.get_mesh();
    if (!mesh_attribute_valid(*mesh, new_name, attr->domain(), attr->data_type(), reports)) {
      return false;
    }
    if (attr->data_type() == bke::AttrType::Float2) {
      /* Rename UV sub-attributes. */
      char buffer_src[MAX_CUSTOMDATA_LAYER_NAME];
      char buffer_dst[MAX_CUSTOMDATA_LAYER_NAME];
      bke_attribute_rename_if_exists(owner,
                                     BKE_uv_map_pin_name_get(attr->name(), buffer_src),
                                     BKE_uv_map_pin_name_get(new_name, buffer_dst),
                                     reports);
    }

    if (old_name == BKE_id_attributes_active_color_name(&mesh->id)) {
      BKE_id_attributes_active_color_set(&mesh->id, new_name);
    }
    if (old_name == BKE_id_attributes_default_color_name(&mesh->id)) {
      BKE_id_attributes_default_color_set(&mesh->id, new_name);
    }
    if (old_name == mesh->active_uv_map_name()) {
      mesh->uv_maps_active_set(new_name);
    }
    if (old_name == mesh->default_uv_map_name()) {
      mesh->uv_maps_default_set(new_name);
    }
  }

  attributes.rename(old_name, new_name);
  return true;
}

std::string BKE_attribute_calc_unique_name(const AttributeOwner &owner, const StringRef name)
{
  const StringRef name_final = name.is_empty() ? DATA_("Attribute") : name;
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh &mesh = *owner.get_mesh();
    /* While attribute names may collide with vertex group names,
     * it's important not to allow this when requesting a unique name
     * because #MutableAttributeAccessor::add will consider the layer as "existing",
     * and not add the new attribute as expected. See: #160029. */
    const auto is_used_vertex_group = [&](const StringRef check_name) {
      return BKE_defgroup_name_index(&mesh.vertex_group_names, check_name) != -1;
    };
    if (mesh.runtime->edit_mesh) {
      Set<StringRef, 8> names;
      const auto add_names = [&](const CustomData &data) {
        for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
          if (CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & CD_MASK_PROP_ALL) {
            names.add(layer.name);
          }
        }
      };
      const BMesh &bm = *mesh.runtime->edit_mesh->bm;
      add_names(bm.vdata);
      add_names(bm.edata);
      add_names(bm.pdata);
      add_names(bm.ldata);
      return BLI_uniquename_cb(
          [&](const StringRef new_name) {
            return names.contains(new_name) || BM_attribute_stored_in_bmesh_builtin(new_name) ||
                   is_used_vertex_group(new_name);
          },
          '.',
          name_final);
    }
    const bke::AttributeStorage &storage = *owner.get_storage();
    return BLI_uniquename_cb(
        [&](const StringRef check_name) {
          return storage.lookup(check_name) != nullptr || is_used_vertex_group(check_name);
        },
        '.',
        name_final);
  }

  bke::AttributeStorage &storage = *owner.get_storage();
  return storage.unique_name_calc(name_final);
}

CustomDataLayer *BKE_attribute_new(Mesh &mesh,
                                   BMesh &bm,
                                   const StringRef name,
                                   const eCustomDataType type,
                                   const AttrDomain domain,
                                   ReportList *reports)
{
  using namespace blender::bke;
  BLI_assert(mesh.runtime->edit_mesh->bm == &bm);
  const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&bm);

  CustomData *customdata = info[int(domain)].customdata;
  if (customdata == nullptr) {
    BKE_report(reports, RPT_ERROR, "Attribute domain not supported by this geometry type");
    return nullptr;
  }

  std::string uniquename = BKE_attribute_calc_unique_name(AttributeOwner::from_id(&mesh.id), name);

  if (!mesh_attribute_valid(mesh, name, domain, *custom_data_type_to_attr_type(type), reports)) {
    return nullptr;
  }
  BM_data_layer_add_named(&bm, customdata, type, uniquename.c_str());
  const int index = CustomData_get_named_layer_index(customdata, type, uniquename);
  return (index == -1) ? nullptr : &(customdata->layers[index]);
}

static int color_name_to_index(AttributeOwner &owner, const StringRef name)
{
  return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
}

static int color_clamp_index(AttributeOwner &owner, int index)
{
  const int length = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
  return min_ii(index, length - 1);
}

static StringRef color_name_from_index(AttributeOwner &owner, int index)
{
  return BKE_attribute_from_index(owner, index, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL)
      .value_or("");
}

static int uv_name_to_index(AttributeOwner &owner, const StringRef name)
{
  return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2);
}

static int uv_clamp_index(AttributeOwner &owner, int index)
{
  const int length = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2);
  return min_ii(index, length - 1);
}

static StringRef uv_name_from_index(AttributeOwner &owner, int index)
{
  return BKE_attribute_from_index(owner, index, ATTR_DOMAIN_MASK_CORNER, CD_MASK_PROP_FLOAT2)
      .value_or("");
}

bool BKE_attribute_remove(AttributeOwner &owner, const StringRef name, ReportList *reports)
{
  using namespace blender::bke;
  if (name.is_empty()) {
    BKE_report(reports, RPT_ERROR, "The attribute name must not be empty");
    return false;
  }
  if (BKE_attribute_required(owner, name)) {
    BKE_reportf(reports,
                RPT_ERROR,
                "Attribute '%s' is required and cannot be removed",
                std::string(name).c_str());
    return false;
  }

  if (owner.type() == AttributeOwnerType::Mesh) {
    Mesh *mesh = owner.get_mesh();
    if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
      for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
        if (CustomData *data = info[domain].customdata) {
          const std::string name_copy = name;
          const int layer_index = CustomData_get_named_layer_index_notype(data, name_copy);
          if (layer_index == -1) {
            continue;
          }

          const eCustomDataType type = eCustomDataType(data->layers[layer_index].type);
          const bool is_active_uv_attribute = name_copy == mesh->active_uv_map_name();
          const bool is_default_uv_attribute = name_copy == mesh->default_uv_map_name();
          const bool is_active_color_attribute = name_copy.c_str() ==
                                                 StringRef(mesh->active_color_attribute);
          const bool is_default_color_attribute = name_copy.c_str() ==
                                                  StringRef(mesh->default_color_attribute);
          const int active_color_index = color_name_to_index(owner, mesh->active_color_attribute);
          const int default_color_index = color_name_to_index(owner,
                                                              mesh->default_color_attribute);
          const int active_uv_index = uv_name_to_index(owner, mesh->active_uv_map_name());
          const int default_uv_index = uv_name_to_index(owner, mesh->default_uv_map_name());

          if (!BM_data_layer_free_named(em->bm, data, name_copy.c_str())) {
            BLI_assert_unreachable();
          }

          if (is_active_color_attribute) {
            BKE_id_attributes_active_color_set(
                &mesh->id,
                color_name_from_index(owner, color_clamp_index(owner, active_color_index)));
          }
          if (is_default_color_attribute) {
            BKE_id_attributes_default_color_set(
                &mesh->id,
                color_name_from_index(owner, color_clamp_index(owner, default_color_index)));
          }
          if (is_active_uv_attribute) {
            mesh->uv_maps_active_set(
                uv_name_from_index(owner, uv_clamp_index(owner, active_uv_index)));
          }
          if (is_default_uv_attribute) {
            mesh->uv_maps_default_set(
                uv_name_from_index(owner, uv_clamp_index(owner, default_uv_index)));
          }

          if (type == CD_PROP_FLOAT2 && domain == int(AttrDomain::Corner)) {
            char buffer[MAX_CUSTOMDATA_LAYER_NAME];
            BM_data_layer_free_named(em->bm, data, BKE_uv_map_pin_name_get(name_copy, buffer));
          }
          return true;
        }
      }
      return false;
    }
  }

  std::optional<MutableAttributeAccessor> attributes = owner.get_accessor();
  if (!attributes) {
    return false;
  }

  if (owner.type() == AttributeOwnerType::Mesh) {
    const std::string name_copy = name;
    std::optional<bke::AttributeMetaData> metadata = attributes->lookup_meta_data(name_copy);
    if (!metadata) {
      return false;
    }
    /* Update active and default color attributes. */
    Mesh *mesh = owner.get_mesh();
    const bool is_active_color_attribute = name_copy == StringRef(mesh->active_color_attribute);
    const bool is_default_color_attribute = name_copy == StringRef(mesh->default_color_attribute);
    const bool is_active_uv_attribute = name_copy == mesh->active_uv_map_name();
    const bool is_default_uv_attribute = name_copy == mesh->default_uv_map_name();
    const int active_color_index = color_name_to_index(owner, mesh->active_color_attribute);
    const int default_color_index = color_name_to_index(owner, mesh->default_color_attribute);
    const int active_uv_index = uv_name_to_index(owner, mesh->active_uv_map_name());
    const int default_uv_index = uv_name_to_index(owner, mesh->default_uv_map_name());

    if (!attributes->remove(name_copy)) {
      BLI_assert_unreachable();
    }

    if (is_active_color_attribute) {
      BKE_id_attributes_active_color_set(
          &mesh->id, color_name_from_index(owner, color_clamp_index(owner, active_color_index)));
    }
    if (is_default_color_attribute) {
      BKE_id_attributes_default_color_set(
          &mesh->id, color_name_from_index(owner, color_clamp_index(owner, default_color_index)));
    }
    if (is_active_uv_attribute) {
      mesh->uv_maps_active_set(uv_name_from_index(owner, uv_clamp_index(owner, active_uv_index)));
    }
    if (is_default_uv_attribute) {
      mesh->uv_maps_default_set(
          uv_name_from_index(owner, uv_clamp_index(owner, default_uv_index)));
    }

    if (bke::mesh::is_uv_map(metadata)) {
      char buffer[MAX_CUSTOMDATA_LAYER_NAME];
      attributes->remove(BKE_uv_map_pin_name_get(name_copy, buffer));
    }
    return true;
  }

  return attributes->remove(name);
}

int BKE_attributes_length(const AttributeOwner &owner,
                          const AttrDomainMask domain_mask,
                          const eCustomDataMask mask,
                          const bool include_anonymous)
{
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh &mesh = *owner.get_mesh();
    if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
      int length = 0;
      for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
        const CustomData *customdata = info[domain].customdata;
        if (customdata == nullptr) {
          continue;
        }
        if (!customdata || !((1 << int(domain)) & domain_mask)) {
          continue;
        }
        for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
          if (!(mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
            continue;
          }
          if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
            continue;
          }
          length++;
        }
      }
      return length;
    }
  }
  const bke::AttributeStorage &storage = *owner.get_storage();
  if (include_anonymous && domain_mask == ATTR_DOMAIN_MASK_ALL && mask == CD_MASK_PROP_ALL) {
    return storage.count();
  }
  return std::count_if(storage.begin(), storage.end(), [&](const bke::Attribute &attr) {
    if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
      return false;
    }
    if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & mask)) {
      return false;
    }
    if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
      return false;
    }
    return true;
  });
}

AttrDomain BKE_attribute_domain(const Mesh &mesh, const BMesh &bm, const CustomDataLayer *layer)
{
  BLI_assert(mesh.runtime->edit_mesh->bm == &bm);
  UNUSED_VARS_NDEBUG(mesh);
  const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));

  for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
    const CustomData *customdata = info[domain].customdata;
    if (customdata == nullptr) {
      continue;
    }
    if (Span(customdata->layers, customdata->totlayer).contains_ptr(layer)) {
      return AttrDomain(domain);
    }
  }

  BLI_assert_msg(0, "Custom data layer not found in geometry");
  return AttrDomain(AttrDomain::Point);
}

int BKE_attribute_domain_size(const AttributeOwner &owner, const int domain)
{
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh &mesh = *owner.get_mesh();
    if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
      const BMesh &bm = *em->bm;
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));
      return info[domain].length;
    }
  }
  const bke::AttributeAccessor attributes = *owner.get_accessor();
  return attributes.domain_size(bke::AttrDomain(domain));
}

bool BKE_attribute_required(const AttributeOwner &owner, const StringRef name)
{
  switch (owner.type()) {
    case AttributeOwnerType::PointCloud:
      return BKE_pointcloud_attribute_required(owner.get_pointcloud(), name);
    case AttributeOwnerType::Curves:
      return BKE_curves_attribute_required(owner.get_curves(), name);
    case AttributeOwnerType::Mesh:
      return BKE_mesh_attribute_required(name);
    case AttributeOwnerType::GreasePencil:
      return false;
    case AttributeOwnerType::GreasePencilDrawing:
      return BKE_grease_pencil_drawing_attribute_required(owner.get_grease_pencil_drawing(), name);
  }
  return false;
}

std::optional<StringRefNull> BKE_attributes_active_name_get(AttributeOwner &owner)
{
  using namespace blender::bke;
  int active_index = *BKE_attributes_active_index_p(owner);
  if (active_index == -1) {
    return std::nullopt;
  }
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh *mesh = owner.get_mesh();
    if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
      if (active_index > BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL)) {
        active_index = 0;
      }
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
      int index = 0;
      for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
        CustomData *customdata = info[domain].customdata;
        if (customdata == nullptr) {
          continue;
        }
        for (int i = 0; i < customdata->totlayer; i++) {
          CustomDataLayer *layer = &customdata->layers[i];
          if (CD_MASK_PROP_ALL & CD_TYPE_AS_MASK(eCustomDataType(layer->type))) {
            if (index == active_index) {
              if (bke::allow_procedural_attribute_access(layer->name)) {
                return layer->name;
              }
              return std::nullopt;
            }
            index++;
          }
        }
      }
      return std::nullopt;
    }
  }

  bke::AttributeStorage &storage = *owner.get_storage();
  if (!IndexRange(storage.count()).contains(active_index)) {
    return std::nullopt;
  }
  return storage.at_index(active_index).name();
}

void BKE_attributes_active_set(AttributeOwner &owner, const StringRef name)
{
  BLI_assert(bke::allow_procedural_attribute_access(name));
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh *mesh = owner.get_mesh();
    if (mesh->runtime->edit_mesh) {
      const int index = BKE_attribute_to_index(
          owner, name, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
      *BKE_attributes_active_index_p(owner) = index;
      return;
    }
  }

  bke::AttributeStorage &attributes = *owner.get_storage();
  *BKE_attributes_active_index_p(owner) = attributes.index_of(name);
}

void BKE_attributes_active_clear(AttributeOwner &owner)
{
  *BKE_attributes_active_index_p(owner) = -1;
}

int *BKE_attributes_active_index_p(AttributeOwner &owner)
{
  switch (owner.type()) {
    case AttributeOwnerType::PointCloud: {
      return &owner.get_pointcloud()->attributes_active_index;
    }
    case AttributeOwnerType::Mesh: {
      return &owner.get_mesh()->attributes_active_index;
    }
    case AttributeOwnerType::Curves: {
      return &owner.get_curves()->geometry.attributes_active_index;
    }
    case AttributeOwnerType::GreasePencil: {
      return &owner.get_grease_pencil()->attributes_active_index;
    }
    case AttributeOwnerType::GreasePencilDrawing: {
      return &owner.get_grease_pencil_drawing()->geometry.attributes_active_index;
    }
  }
  return nullptr;
}

void BKE_attributes_active_index_validate(AttributeOwner &owner)
{
  int *active_index = BKE_attributes_active_index_p(owner);
  if (*active_index < 0) {
    /* Mark none as active. */
    *active_index = -1;
    return;
  }

  const int attributes_num = BKE_attributes_length(owner, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
  if (attributes_num == 0) {
    /* Mark none as active. */
    *active_index = -1;
    return;
  }

  /* First try downwards. */
  int index_check = *active_index;
  while (index_check >= 0) {
    if (index_check < attributes_num) {
      std::optional<StringRef> attribute_name_check = BKE_attribute_from_index(
          owner, index_check, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
      if (attribute_name_check.has_value() &&
          bke::allow_procedural_attribute_access(attribute_name_check.value()))
      {
        *active_index = index_check;
        return;
      }
    }
    index_check--;
  }

  /* Still not found? Try upwards. */
  index_check = *active_index + 1;
  while (index_check < attributes_num) {
    std::optional<StringRef> attribute_name_check = BKE_attribute_from_index(
        owner, index_check, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL);
    if (attribute_name_check.has_value() &&
        bke::allow_procedural_attribute_access(attribute_name_check.value()))
    {
      *active_index = index_check;
      return;
    }
    index_check++;
  }

  /* Still not found? Mark none as active. */
  *active_index = -1;
}

std::optional<StringRef> BKE_attribute_from_index(AttributeOwner &owner,
                                                  const int lookup_index,
                                                  const AttrDomainMask domain_mask,
                                                  const eCustomDataMask layer_mask,
                                                  const bool include_anonymous)
{
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh &mesh = *owner.get_mesh();
    if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
      const BMesh &bm = *em->bm;
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(&const_cast<BMesh &>(bm));

      int index = 0;
      for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
        CustomData *customdata = info[domain].customdata;
        if (!customdata || !((1 << int(domain)) & domain_mask)) {
          continue;
        }
        for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
          if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
            continue;
          }
          if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
            continue;
          }
          if (index == lookup_index) {
            return layer.name;
          }
          index++;
        }
      }
      return std::nullopt;
    }
  }

  bke::AttributeStorage &storage = *owner.get_storage();
  if (domain_mask == ATTR_DOMAIN_MASK_ALL && layer_mask == CD_MASK_PROP_ALL) {
    return storage.at_index(lookup_index).name();
  }
  int index = 0;
  for (const bke::Attribute &attr : storage) {
    if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
      continue;
    }
    if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & layer_mask)) {
      continue;
    }
    if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
      continue;
    }
    if (index == lookup_index) {
      return attr.name();
    }
    index++;
  }
  return std::nullopt;
}

int BKE_attribute_to_index(const AttributeOwner &owner,
                           const StringRef name,
                           AttrDomainMask domain_mask,
                           eCustomDataMask layer_mask,
                           const bool include_anonymous)
{
  if (owner.type() == AttributeOwnerType::Mesh) {
    const Mesh &mesh = *owner.get_mesh();
    if (BMEditMesh *em = mesh.runtime->edit_mesh.get()) {
      const std::array<DomainInfo, ATTR_DOMAIN_NUM> info = get_domains(em->bm);
      int index = 0;
      for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) {
        const CustomData *customdata = info[domain].customdata;
        if (!customdata || !((1 << int(domain)) & domain_mask)) {
          continue;
        }
        for (const CustomDataLayer &layer : Span(customdata->layers, customdata->totlayer)) {
          if (!(layer_mask & CD_TYPE_AS_MASK(eCustomDataType(layer.type)))) {
            continue;
          }
          if (!include_anonymous && bke::attribute_name_is_anonymous(layer.name)) {
            continue;
          }
          if (layer.name == name) {
            return index;
          }
          index++;
        }
      }
      return -1;
    }
  }

  const bke::AttributeStorage &storage = *owner.get_storage();
  if (domain_mask == ATTR_DOMAIN_MASK_ALL && layer_mask == CD_MASK_PROP_ALL) {
    return storage.index_of(name);
  }
  int index = 0;
  for (const bke::Attribute &attr : storage) {
    if (!(ATTR_DOMAIN_AS_MASK(attr.domain()) & domain_mask)) {
      continue;
    }
    if (!(CD_TYPE_AS_MASK(*bke::attr_type_to_custom_data_type(attr.data_type())) & layer_mask)) {
      continue;
    }
    if (!include_anonymous && bke::attribute_name_is_anonymous(attr.name())) {
      continue;
    }
    if (attr.name() == name) {
      return index;
    }
    index++;
  }
  return -1;
}

std::optional<StringRef> BKE_id_attributes_active_color_name(const ID *id)
{
  if (GS(id->name) == ID_ME) {
    return reinterpret_cast<const Mesh *>(id)->active_color_attribute;
  }
  return std::nullopt;
}

std::optional<StringRef> BKE_id_attributes_default_color_name(const ID *id)
{
  if (GS(id->name) == ID_ME) {
    return reinterpret_cast<const Mesh *>(id)->default_color_attribute;
  }
  return std::nullopt;
}

void BKE_id_attributes_active_color_set(ID *id, const std::optional<StringRef> name)
{
  switch (GS(id->name)) {
    case ID_ME: {
      Mesh *mesh = reinterpret_cast<Mesh *>(id);
      MEM_SAFE_DELETE(mesh->active_color_attribute);
      if (name) {
        mesh->active_color_attribute = BLI_strdupn(name->data(), name->size());
      }
      break;
    }
    default:
      break;
  }
}

void BKE_id_attributes_active_color_clear(ID *id)
{
  switch (GS(id->name)) {
    case ID_ME: {
      Mesh *mesh = reinterpret_cast<Mesh *>(id);
      MEM_SAFE_DELETE(mesh->active_color_attribute);
      break;
    }
    default:
      break;
  }
}

void BKE_id_attributes_default_color_set(ID *id, const std::optional<StringRef> name)
{
  switch (GS(id->name)) {
    case ID_ME: {
      Mesh *mesh = reinterpret_cast<Mesh *>(id);
      MEM_SAFE_DELETE(mesh->default_color_attribute);
      if (name) {
        mesh->default_color_attribute = BLI_strdupn(name->data(), name->size());
      }
      break;
    }
    default:
      break;
  }
}

bool BKE_id_attributes_color_find(const ID *id, const StringRef name)
{
  AttributeOwner owner = AttributeOwner::from_id(const_cast<ID *>(id));
  return BKE_attribute_to_index(owner, name, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL) != -1;
}

StringRef BKE_uv_map_pin_name_get(const StringRef uv_map_name, char *buffer)
{
  BLI_assert(strlen(UV_PINNED_NAME) == 2);
  BLI_assert(uv_map_name.size() < MAX_CUSTOMDATA_LAYER_NAME - 4);
  const auto result = fmt::format_to_n(
      buffer, MAX_CUSTOMDATA_LAYER_NAME, ".{}.{}", UV_PINNED_NAME, uv_map_name);
  return StringRef(buffer, result.size);
}

}  // namespace blender
