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

/** \file
 * \ingroup bke
 */

#include <algorithm>
#include <fmt/format.h>

#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_math.hh"
#include "CLG_log.h"

#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"

#include "BLI_array_utils.hh"
#include "BLI_index_ranges_builder.hh"
#include "BLI_listbase.h"
#include "BLI_ordered_edge.hh"

#include "BKE_attribute.hh"
#include "BKE_customdata.hh"
#include "BKE_mesh.hh"

#include "DEG_depsgraph.hh"

namespace blender {

static CLG_LogRef LOG = {"geom.mesh"};

namespace bke {

/* Helper class to collect error messages in parallel. */
class ErrorMessages {
  Mutex mutex_;
  bool verbose_;

 public:
  Vector<std::string> messages;

  ErrorMessages(const bool verbose) : verbose_(verbose) {}
  ~ErrorMessages()
  {
    std::ranges::sort(messages);
    for (const std::string &message : messages) {
      CLOG_ERROR(&LOG, "%s", message.c_str());
    }
  }

  void add(std::string message)
  {
    if (!verbose_) {
      return;
    }
    std::lock_guard lock(mutex_);
    this->messages.append(std::move(message));
  }

  template<typename... T> void add(fmt::format_string<T...> fmt, T &&...args)
  {
    if (!verbose_) {
      return;
    }
    this->add(fmt::format(fmt::runtime(fmt), args...));
  }
};

template<typename... T>
static void print_error_with_indices(const IndexMask &mask,
                                     fmt::format_string<T...> fmt,
                                     T &&...args)
{
  fmt::memory_buffer buffer;
  fmt::appender dst(buffer);
  fmt::format_to(dst, fmt, std::forward<T>(args)...);
  fmt::format_to(dst, " at indices: ");
  mask.foreach_index([&](const int index, const int pos) {
    if (pos != 0) {
      fmt::format_to(dst, ", ");
    }
    fmt::format_to(dst, "{}", index);
  });
  CLOG_ERROR(&LOG, "%s", fmt::to_string(buffer).c_str());
}

static IndexMask find_edges_bad_verts(const Mesh &mesh,
                                      IndexMaskMemory &memory,
                                      const bool verbose)
{
  const IndexRange verts_range(mesh.verts_num);
  const Span<int2> edges = mesh.edges();

  ErrorMessages errors(verbose);
  return IndexMask::from_predicate(edges.index_range(), memory, [&](const int edge_i) {
    const int2 edge = edges[edge_i];
    if (edge[0] == edge[1]) {
      errors.add("Edge {} has equal vertex indices {}", edge_i, edge[0]);
      return true;
    }
    if (!verts_range.contains(edge[0]) || !verts_range.contains(edge[1])) {
      errors.add("Edge {} has out of range vertex ({}, {})", edge_i, edge[0], edge[1]);
      return true;
    }
    return false;
  });
}

using EdgeMap = VectorSet<OrderedEdge,
                          32,
                          DefaultProbingStrategy,
                          DefaultHash<OrderedEdge>,
                          DefaultEquality<OrderedEdge>,
                          SimpleVectorSetSlot<OrderedEdge, int>,
                          GuardedAllocator>;

static IndexMask find_edges_duplicates(const Mesh &mesh,
                                       const IndexMask &mask,
                                       IndexMaskMemory &memory,
                                       const bool verbose,
                                       EdgeMap &unique_edges)
{
  const Span<int2> edges = mesh.edges();
  BitVector<> duplicate_edges(edges.size());
  ErrorMessages errors(verbose);
  mask.foreach_index([&](const int edge_i) {
    const int2 edge = edges[edge_i];
    if (!unique_edges.add(edge)) {
      errors.add(fmt::runtime("Edge {} is a duplicate of {}"),
                 edge_i,
                 int(mask[unique_edges.index_of(edge)]));
      duplicate_edges[edge_i].set();
    }
  });
  return IndexMask::from_bits(mask, duplicate_edges, memory);
}

static IndexMask find_faces_bad_offsets(const Mesh &mesh,
                                        IndexMaskMemory &memory,
                                        const bool verbose)
{
  if (mesh.faces_num == 0) {
    return {};
  }
  const Span<int> face_offsets = mesh.face_offsets();
  ErrorMessages errors(verbose);
  if (face_offsets.last() != mesh.corners_num) {
    errors.add("Face offsets last value is {}, expected {}. Considering all faces invalid",
               face_offsets.last(),
               mesh.corners_num);
    return IndexMask(mesh.faces_num);
  }
  if (face_offsets.first() != 0) {
    errors.add("Faces offsets do not start at 0. Considering all faces invalid");
    return IndexMask(mesh.faces_num);
  }
  std::atomic<bool> non_monotonic = false;
  const IndexMask mask = IndexMask::from_batch_predicate(
      IndexRange(mesh.faces_num),
      memory,
      [&](const IndexMaskSegment universe_segment, IndexRangesBuilder<int16_t> &builder) {
        for (const int16_t i : universe_segment.base_span()) {
          const int face_i = int(universe_segment.offset() + i);
          const int face_start = face_offsets[face_i];
          const int face_end = face_offsets[face_i + 1];
          if (face_end < face_start) {
            non_monotonic.store(true, std::memory_order_relaxed);
            continue;
          }
          if (face_end - face_start < 3) {
            errors.add("Face {} has invalid size {}", face_i, face_end - face_start);
            builder.add(i);
          }
        }
        return universe_segment.offset();
      });
  if (non_monotonic.load(std::memory_order_relaxed)) {
    errors.add("Face offsets must be monotonically increasing. Considering all faces invalid");
    return IndexMask(mesh.faces_num);
  }
  return mask;
}

static IndexMask find_faces_bad_verts(const Mesh &mesh,
                                      const IndexMask &mask,
                                      IndexMaskMemory &memory,
                                      const bool verbose)
{
  const IndexRange verts_range(mesh.verts_num);
  const OffsetIndices<int> faces(mesh.face_offsets(), offset_indices::NoSortCheck());
  const Span<int> corner_verts = mesh.corner_verts();
  ErrorMessages errors(verbose);
  return IndexMask::from_predicate(
      mask,
      memory,
      [&](const int face_i) {
        const IndexRange face = faces[face_i];
        for (const int vert : corner_verts.slice(face)) {
          if (!verts_range.contains(vert)) {
            errors.add("Face {} has invalid vertex {}", face_i, vert);
            return true;
          }
        }
        return false;
      },
      exec_mode::grain_size(512));
}

static IndexMask find_faces_duplicate_verts(const Mesh &mesh,
                                            const IndexMask &mask,
                                            IndexMaskMemory &memory,
                                            const bool verbose)
{
  const IndexRange verts_range(mesh.verts_num);
  const OffsetIndices<int> faces(mesh.face_offsets(), offset_indices::NoSortCheck());
  const Span<int> corner_verts = mesh.corner_verts();
  ErrorMessages errors(verbose);
  return IndexMask::from_predicate(
      mask,
      memory,
      [&](const int face_i) {
        Set<int, 16> set;
        const IndexRange face = faces[face_i];
        for (const int vert : corner_verts.slice(face)) {
          if (!set.add(vert)) {
            errors.add("Face {} has duplicate vertex {}", face_i, vert);
            return true;
          }
        }
        return false;
      },
      exec_mode::grain_size(512));
}

static IndexMask find_faces_missing_edges(const Mesh &mesh,
                                          const IndexMask &mask,
                                          const EdgeMap &unique_edges,
                                          IndexMaskMemory &memory,
                                          const bool verbose)
{
  const IndexRange edges_range(mesh.edges_num);
  const OffsetIndices<int> faces(mesh.face_offsets(), offset_indices::NoSortCheck());
  const Span<int> corner_verts = mesh.corner_verts();

  ErrorMessages errors(verbose);
  return IndexMask::from_predicate(
      mask,
      memory,
      [&](const int face_i) {
        const IndexRange face = faces[face_i];
        for (const int corner : face) {
          const int corner_next = mesh::face_corner_next(face, corner);
          const OrderedEdge actual_edge(corner_verts[corner], corner_verts[corner_next]);
          if (!unique_edges.contains(actual_edge)) {
            errors.add("Face {} has missing edge ({}, {})",
                       face_i,
                       actual_edge.v_low,
                       actual_edge.v_high);
            return true;
          }
        }
        return false;
      },
      exec_mode::grain_size(1024));
}

static bool find_faces_bad_edges(const Mesh &mesh,
                                 const IndexMask &mask,
                                 IndexMaskMemory &memory,
                                 const bool verbose)
{
  const IndexRange edges_range(mesh.edges_num);
  const Span<int2> edges = mesh.edges();
  const OffsetIndices<int> faces(mesh.face_offsets(), offset_indices::NoSortCheck());
  const Span<int> corner_verts = mesh.corner_verts();
  const Span<int> corner_edges = mesh.corner_edges();

  ErrorMessages errors(verbose);
  const IndexMask bad_faces = IndexMask::from_predicate(
      mask,
      memory,
      [&](const int face_i) {
        const IndexRange face = faces[face_i];
        for (const int corner : face) {
          const int edge_index = corner_edges[corner];
          if (!edges_range.contains(edge_index)) {
            errors.add("Corner {} has out of range edge index {}", corner, edge_index);
            return true;
          }
          const int corner_next = mesh::face_corner_next(face, corner);
          const OrderedEdge actual_edge(corner_verts[corner], corner_verts[corner_next]);
          if (OrderedEdge(edges[edge_index]) != actual_edge) {
            errors.add("Corner {} has incorrect edge index {}", corner, edge_index);
            return true;
          }
        }
        return false;
      },
      exec_mode::grain_size(1024));
  return !bad_faces.is_empty();
}

static IndexMask find_duplicate_faces(const Mesh &mesh,
                                      const IndexMask &mask,
                                      IndexMaskMemory &memory,
                                      const bool verbose)
{
  const OffsetIndices<int> faces(mesh.face_offsets(), offset_indices::NoSortCheck());
  const Span<int> corner_verts = mesh.corner_verts();

  Array<int> ordered_corner_verts(mesh.corners_num);
  mask.foreach_index(
      [&](const int face_i) {
        const IndexRange face = faces[face_i];
        MutableSpan<int> ordered_face_verts = ordered_corner_verts.as_mutable_span().slice(face);
        /* Order by the smallest index, then the smallest adjacent index this ensures:
         * - Faces are only considered duplicates when they share vertices & edges.
         * - Faces winding in opposite directions are considered duplicates.
         * See: #150842. */
        Span<int> face_verts = corner_verts.slice(face);
        const int face_verts_num = face_verts.size();
        int i_min = 0;
        for (int i = 1; i < face_verts_num; i += 1) {
          if (face_verts[i] < face_verts[i_min]) {
            i_min = i;
          }
        }
        /* Allow modulo after subtraction within the `face_verts_num` range. */
        const int i_min_for_modulo = i_min + face_verts_num;
        const int i_min_prev = (i_min_for_modulo - 1) % face_verts_num;
        const int i_min_next = (i_min_for_modulo + 1) % face_verts_num;
        const int sign = face_verts[i_min_next] < face_verts[i_min_prev] ? 1 : -1;
        for (int i = 0; i < face_verts_num; i += 1) {
          ordered_face_verts[i] = face_verts[(i_min_for_modulo + (i * sign)) % face_verts_num];
        }
      },
      exec_mode::grain_size(1024));

  using FaceMap = VectorSet<Span<int>,
                            32,
                            DefaultProbingStrategy,
                            DefaultHash<Span<int>>,
                            DefaultEquality<Span<int>>,
                            SimpleVectorSetSlot<Span<int>, int>>;

  FaceMap face_hash;
  face_hash.reserve(mesh.faces_num);

  BitVector<> duplicate_faces(mesh.faces_num, false);
  ErrorMessages errors(verbose);
  mask.foreach_index([&](int face_i) {
    const IndexRange face = faces[face_i];
    const Span<int> face_verts = ordered_corner_verts.as_span().slice(face);
    if (!face_hash.add(face_verts)) {
      errors.add("Face {} is a duplicate of {}", face_i, face_hash.index_of(face_verts));
      duplicate_faces[face_i].set();
    }
  });

  return IndexMask::from_bits(duplicate_faces, memory);
}

static void remove_invalid_faces(Mesh &mesh, const IndexMask &valid_faces)
{
  const int valid_faces_num = valid_faces.size();
  const OffsetIndices<int> old_faces(mesh.face_offsets(), offset_indices::NoSortCheck());

  Vector<int> new_face_offsets(valid_faces_num + 1);
  const OffsetIndices new_faces = offset_indices::gather_selected_offsets(
      old_faces, valid_faces, new_face_offsets);

  for (bke::Attribute &attr : mesh.attribute_storage.wrap()) {
    const CPPType &type = attribute_type_to_cpp_type(attr.data_type());
    switch (attr.domain()) {
      case AttrDomain::Face: {
        switch (attr.storage_type()) {
          case AttrStorageType::Array: {
            const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
            auto dst_data = Attribute::ArrayData::from_uninitialized(type, valid_faces_num);
            array_utils::gather(GSpan(type, src_data.data, mesh.faces_num),
                                valid_faces,
                                GMutableSpan(type, dst_data.data, valid_faces_num));
            attr.assign_data(std::move(dst_data));
            break;
          }
          case AttrStorageType::Single:
            break;
        }
        break;
      }
      case AttrDomain::Corner: {
        switch (attr.storage_type()) {
          case AttrStorageType::Array: {
            const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
            auto dst_data = Attribute::ArrayData::from_uninitialized(type, new_faces.total_size());
            bke::attribute_math::gather_group_to_group(
                old_faces,
                new_faces,
                valid_faces,
                GSpan(type, src_data.data, mesh.corners_num),
                GMutableSpan(type, dst_data.data, new_faces.total_size()));
            attr.assign_data(std::move(dst_data));
            break;
          }
          case AttrStorageType::Single:
            break;
        }
        break;
      }
      default:
        break;
    }
  }

  for (CustomDataLayer &layer : MutableSpan(mesh.face_data.layers, mesh.face_data.totlayer)) {
    if (layer.type == CD_ORIGINDEX) {
      const Span src(static_cast<const int *>(layer.data), mesh.faces_num);

      int *dst_data = MEM_new_array_uninitialized<int>(valid_faces_num, __func__);
      MutableSpan dst(dst_data, valid_faces_num);

      array_utils::gather(src, valid_faces, dst);

      layer.sharing_info->remove_user_and_delete_if_last();
      layer.data = dst_data;
      layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
    }
  }

  for (CustomDataLayer &layer : MutableSpan(mesh.corner_data.layers, mesh.corner_data.totlayer)) {
    const eCustomDataType cd_type = eCustomDataType(layer.type);
    if (ELEM(layer.type,
             CD_NORMAL,
             CD_ORIGINDEX,
             CD_MDISPS,
             CD_GRID_PAINT_MASK,
             CD_ORIGSPACE_MLOOP))
    {
      const size_t elem_size = CustomData_sizeof(cd_type);
      const void *src = layer.data;

      void *dst = MEM_new_array_uninitialized(new_faces.total_size(), elem_size, __func__);

      valid_faces.foreach_index(
          [&](const int64_t src_i, const int64_t dst_i) {
            CustomData_copy_elements(cd_type,
                                     POINTER_OFFSET(src, elem_size * old_faces[src_i].start()),
                                     POINTER_OFFSET(dst, elem_size * new_faces[dst_i].start()),
                                     new_faces[dst_i].size());
          },
          exec_mode::grain_size(512));

      layer.sharing_info->remove_user_and_delete_if_last();
      layer.data = dst;
      layer.sharing_info = implicit_sharing::info_for_mem_free(dst);
    }
  }

  mesh.faces_num = new_faces.size();
  mesh.corners_num = new_faces.total_size();
  mesh.face_offset_indices = new_face_offsets.release().data;
  mesh.runtime->face_offsets_sharing_info->remove_user_and_delete_if_last();
  mesh.runtime->face_offsets_sharing_info = implicit_sharing::info_for_mem_free(
      mesh.face_offset_indices);

  mesh.tag_topology_changed();
}

static void remove_invalid_edges(Mesh &mesh, const IndexMask &valid_edges)
{
  const int valid_edges_num = valid_edges.size();

  for (bke::Attribute &attr : mesh.attribute_storage.wrap()) {
    if (attr.domain() != AttrDomain::Edge) {
      continue;
    }
    const CPPType &type = attribute_type_to_cpp_type(attr.data_type());
    switch (attr.storage_type()) {
      case AttrStorageType::Array: {
        const auto &src_data = std::get<Attribute::ArrayData>(attr.data());
        auto dst_data = Attribute::ArrayData::from_uninitialized(type, valid_edges_num);
        array_utils::gather(GSpan(type, src_data.data, mesh.edges_num),
                            valid_edges,
                            GMutableSpan(type, dst_data.data, valid_edges_num));
        attr.assign_data(std::move(dst_data));
        break;
      }
      case AttrStorageType::Single:
        break;
    }
  }

  for (CustomDataLayer &layer : MutableSpan(mesh.edge_data.layers, mesh.edge_data.totlayer)) {
    if (layer.type == CD_ORIGINDEX) {
      const Span src(static_cast<const int *>(layer.data), mesh.edges_num);

      int *dst_data = MEM_new_array_uninitialized<int>(valid_edges_num, __func__);
      MutableSpan dst(dst_data, valid_edges_num);

      array_utils::gather(src, valid_edges, dst);

      layer.sharing_info->remove_user_and_delete_if_last();
      layer.data = dst_data;
      layer.sharing_info = implicit_sharing::info_for_mem_free(dst_data);
    }
  }

  mesh.edges_num = valid_edges.size();

  mesh.tag_topology_changed();
}

static bool validate_vertex_groups(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
{
  const Span<MDeformVert> dverts = mesh.deform_verts();
  if (dverts.is_empty()) {
    return true;
  }
  const int vertex_groups_num = mesh.vertex_group_names.count();
  Mutex mutex;
  Vector<std::pair<int, Vector<std::string, 0>>> all_errors;
  Vector<std::pair<int, Vector<MDeformWeight, 0>>> replacements;
  threading::parallel_for(dverts.index_range(), 2048, [&](const IndexRange range) {
    for (const int vert : range) {
      const MDeformVert &dvert = dverts[vert];

      Vector<std::string> errors;
      bool invalid = false;
      Vector<MDeformWeight, 64> fixed_weights;
      for (const MDeformWeight &dw : Span(dvert.dw, dvert.totweight)) {
        const uint def_nr = dw.def_nr;
        if (dw.def_nr >= vertex_groups_num) {
          invalid = true;
          if (verbose) {
            std::lock_guard lock(mutex);
            errors.append(fmt::format("Vertex {} has invalid deform group {}", vert, def_nr));
          }
          continue;
        }
        if (!std::isfinite(dw.weight)) {
          invalid = true;
          if (verbose) {
            std::lock_guard lock(mutex);
            errors.append(fmt::format(
                "Vertex {} deform group {} has invalid weight {}", vert, def_nr, dw.weight));
          }
          if (mesh_mut) {
            fixed_weights.append({def_nr, 0.0f});
          }
          continue;
        }
        if (dw.weight < 0.0f || dw.weight > 1.0f) {
          invalid = true;
          if (verbose) {
            std::lock_guard lock(mutex);
            errors.append(fmt::format(
                "Vertex {} deform group {} has invalid weight {}", vert, def_nr, dw.weight));
          }
          if (mesh_mut) {
            fixed_weights.append({def_nr, std::clamp(dw.weight, 0.0f, 1.0f)});
          }
          continue;
        }
        if (mesh_mut) {
          fixed_weights.append(dw);
        }
      }

      if (invalid) {
        std::lock_guard lock(mutex);
        all_errors.append({vert, std::move(errors)});
        replacements.append({vert, std::move(fixed_weights)});
      }
    }
  });
  if (replacements.is_empty()) {
    return true;
  }
  if (verbose) {
    for (const auto &[vert, errors] : all_errors) {
      for (const std::string &error : errors) {
        CLOG_ERROR(&LOG, "%s", error.c_str());
      }
    }
  }
  if (mesh_mut) {
    MutableSpan<MDeformVert> dverts = mesh_mut->deform_verts_for_write();
    for (auto &[vert, weights] : replacements) {
      MEM_delete(dverts[vert].dw);
      dverts[vert].totweight = weights.size();
      dverts[vert].dw = weights.release().data;
    }
  }
  return false;
}

static bool validate_material_indices(const Mesh &mesh,
                                      const bool only_check_negative,
                                      const bool verbose,
                                      Mesh *mesh_mut)
{
  const IndexRange materials_range(only_check_negative ? std::numeric_limits<int>::max() :
                                                         std::max(int(mesh.totcol), 1));
  const bke::AttributeAccessor attributes = mesh.attributes();
  const VArray material_indices = *attributes.lookup<int>("material_index", bke::AttrDomain::Face);
  if (!material_indices) {
    return true;
  }
  if (const std::optional<int> index = material_indices.get_if_single()) {
    if (!materials_range.contains(*index)) {
      if (mesh_mut) {
        mesh_mut->attributes_for_write().remove("material_index");
      }
      return false;
    }
    return true;
  }
  const VArraySpan<int> material_indices_span(material_indices);
  IndexMaskMemory memory;
  const IndexMask invalid_indices = IndexMask::from_predicate(
      material_indices.index_range(), memory, [&](const int face) {
        return !materials_range.contains(material_indices_span[face]);
      });
  if (invalid_indices.is_empty()) {
    return true;
  }
  if (verbose) {
    invalid_indices.foreach_index([&](const int face) {
      CLOG_ERROR(&LOG, "Face %d has invalid material index %d", face, material_indices_span[face]);
    });
  }
  if (mesh_mut) {
    bke::MutableAttributeAccessor attributes = mesh_mut->attributes_for_write();
    bke::SpanAttributeWriter material_indices = attributes.lookup_for_write_span<int>(
        "material_index");
    index_mask::masked_fill(material_indices.span, 0, invalid_indices);
    material_indices.finish();
    DEG_id_tag_update(&mesh_mut->id, ID_RECALC_GEOMETRY_ALL_MODES);
  }
  return false;
}

static bool validate_selection_history(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
{
  if (!mesh.mselect) {
    return true;
  }
  const Span<MSelect> mselect(mesh.mselect, mesh.totselect);
  IndexMaskMemory memory;
  const IndexMask invalid = IndexMask::from_predicate(
      mselect.index_range(), memory, [&](const int i) {
        if (mselect[i].index < 0) {
          return true;
        }
        const int domain_size = [&]() {
          switch (mselect[i].type) {
            case ME_VSEL:
              return mesh.verts_num;
            case ME_ESEL:
              return mesh.edges_num;
            case ME_FSEL:
              return mesh.faces_num;
          }
          return 0;
        }();
        if (mselect[i].index >= domain_size) {
          return true;
        }
        return false;
      });
  if (invalid.is_empty()) {
    return true;
  }

  if (verbose) {
    invalid.foreach_index([&](const int i) {
      CLOG_ERROR(&LOG,
                 "Selection element (%d, %d) has invalid index, must not be negative",
                 mselect[i].type,
                 mselect[i].index);
    });
  }
  if (mesh_mut) {
    MEM_SAFE_DELETE(mesh_mut->mselect);
  }

  return false;
}

static IndexMask get_invalid_float_mask(const Span<float> values,
                                        const int floats_per_item,
                                        IndexMaskMemory &memory)
{
  if (floats_per_item == 0) {
    return {};
  }
  const int num_items = values.size() / floats_per_item;

  return IndexMask::from_predicate(IndexRange(num_items), memory, [&](const int64_t index) {
    const Span<float> item_floats = values.slice(index * floats_per_item, floats_per_item);
    return std::any_of(item_floats.begin(), item_floats.end(), [](const float value) {
      return !std::isfinite(value);
    });
  });
}

static void validate_float_attribute(const bke::AttributeIter &iter,
                                     const int floats_per_item,
                                     const bool verbose,
                                     bool &all_attributes_valid,
                                     Mesh *mesh_mut)
{
  const GVArraySpan span = *iter.get();
  const Span<float> float_span(static_cast<const float *>(span.data()),
                               span.size_in_bytes() / sizeof(float));
  IndexMaskMemory memory;
  const IndexMask invalid = get_invalid_float_mask(float_span, floats_per_item, memory);
  if (invalid.is_empty()) {
    return;
  }
  if (verbose) {
    print_error_with_indices(invalid, "Attribute {} has invalid values", iter.name);
  }
  all_attributes_valid = false;
  if (mesh_mut) {
    bke::MutableAttributeAccessor attributes = mesh_mut->attributes_for_write();
    bke::GSpanAttributeWriter attr = attributes.lookup_for_write_span(iter.name);
    const CPPType &type = attr.span.type();
    type.fill_assign_indices(type.default_value(), attr.span.data(), invalid);
    attr.finish();
  }
}

static void validate_bool_attribute(const bke::AttributeIter &iter,
                                    const bool verbose,
                                    bool &all_attributes_valid,
                                    Mesh *mesh_mut)
{
  const VArraySpan<bool> span = *iter.get<bool>();
  const Span<int8_t> int_span = span.cast<int8_t>();
  IndexMaskMemory memory;
  const IndexMask invalid = IndexMask::from_predicate(
      int_span.index_range(), memory, [&](const int i) { return !ELEM(int_span[i], 0, 1); });
  if (invalid.is_empty()) {
    return;
  }
  if (verbose) {
    print_error_with_indices(invalid, "Attribute {} has invalid values", iter.name);
  }
  all_attributes_valid = false;
  if (mesh_mut) {
    bke::MutableAttributeAccessor attributes = mesh_mut->attributes_for_write();
    bke::SpanAttributeWriter<bool> attr = attributes.lookup_for_write_span<bool>(iter.name);
    index_mask::masked_fill(attr.span, true, invalid);
    attr.finish();
  }
}

static bool validate_generic_attributes(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
{
  bool all_attributes_valid = true;
  mesh.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
    switch (iter.data_type) {
      case AttrType::Bool:
        validate_bool_attribute(iter, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::Int8:
        break;
      case AttrType::Int16_2D:
        break;
      case AttrType::Int32:
        break;
      case AttrType::Int32_2D:
        break;
      case AttrType::Float:
        validate_float_attribute(iter, 1, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::Float2:
        validate_float_attribute(iter, 2, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::Float3:
        validate_float_attribute(iter, 3, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::ColorFloat:
        validate_float_attribute(iter, 4, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::Quaternion:
        validate_float_attribute(iter, 4, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::Float4x4:
        validate_float_attribute(iter, 16, verbose, all_attributes_valid, mesh_mut);
        break;
      case AttrType::ColorByte:
        break;
      case AttrType::String:
        break;
      case AttrType::Float4:
        validate_float_attribute(iter, 4, verbose, all_attributes_valid, mesh_mut);
        break;
    }
  });
  return all_attributes_valid;
}

static bool validate_mdisps(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
{
  const MDisps *mdisps = static_cast<const MDisps *>(
      CustomData_get_layer(&mesh.corner_data, CD_MDISPS));
  if (!mdisps) {
    return true;
  }

  IndexMaskMemory memory;
  const IndexMask invalid = IndexMask::from_predicate(
      IndexRange(mesh.corners_num), memory, [&](const int i) {
        const Span<float> disps = Span(reinterpret_cast<const float3 *>(mdisps[i].disps),
                                       mdisps[i].totdisp)
                                      .cast<float>();
        return std::any_of(
            disps.begin(), disps.end(), [&](const float &v) { return !std::isfinite(v); });
      });

  if (invalid.is_empty()) {
    return true;
  }

  if (verbose) {
    print_error_with_indices(invalid, "Multires displacement has invalid values");
  }

  if (mesh_mut) {
    if (MDisps *mdisp_mut = static_cast<MDisps *>(
            CustomData_get_layer_for_write(&mesh_mut->corner_data, CD_MDISPS, mesh.corners_num)))
    {
      invalid.foreach_index(
          [&](const int i) {
            MutableSpan<float> disps = MutableSpan(reinterpret_cast<float3 *>(mdisp_mut[i].disps),
                                                   mdisp_mut[i].totdisp)
                                           .cast<float>();
            for (float &disp_component : disps) {
              disp_component = std::isfinite(disp_component) ? disp_component : 0.0f;
            }
          },
          exec_mode::grain_size(512));
    }
  }
  return false;
}

static bool mesh_validate_impl(const Mesh &mesh,
                               const bool verbose,
                               const bool allow_missing_edges,
                               Mesh *mesh_mut)
{
  IndexMaskMemory memory;

  IndexMask valid_edges(mesh.edges_num);

  const IndexMask edges_bad_verts = find_edges_bad_verts(mesh, memory, verbose);
  valid_edges = IndexMask::from_difference(valid_edges, edges_bad_verts, memory);

  EdgeMap unique_edges;
  const IndexMask edges_duplicate = find_edges_duplicates(
      mesh, valid_edges, memory, verbose, unique_edges);
  valid_edges = IndexMask::from_difference(valid_edges, edges_duplicate, memory);

  IndexMask valid_faces(mesh.faces_num);

  const IndexMask faces_bad_offsets = find_faces_bad_offsets(mesh, memory, verbose);
  valid_faces = IndexMask::from_difference(valid_faces, faces_bad_offsets, memory);

  const IndexMask faces_bad_verts = find_faces_bad_verts(mesh, valid_faces, memory, verbose);
  valid_faces = IndexMask::from_difference(valid_faces, faces_bad_verts, memory);

  const IndexMask faces_duplicate_verts = find_faces_duplicate_verts(
      mesh, valid_faces, memory, verbose);
  valid_faces = IndexMask::from_difference(valid_faces, faces_duplicate_verts, memory);

  const IndexMask duplicate_faces = find_duplicate_faces(mesh, valid_faces, memory, verbose);
  valid_faces = IndexMask::from_difference(valid_faces, duplicate_faces, memory);

  const IndexMask faces_missing_edges = find_faces_missing_edges(
      mesh, valid_faces, unique_edges, memory, verbose && !allow_missing_edges);
  const IndexMask valid_and_missing_edge_faces = valid_faces;
  valid_faces = IndexMask::from_difference(valid_faces, faces_missing_edges, memory);

  const bool any_corner_edges_bad = find_faces_bad_edges(mesh, valid_faces, memory, verbose);

  const int64_t valid_faces_count = allow_missing_edges ? valid_and_missing_edge_faces.size() :
                                                          valid_faces.size();
  bool valid = valid_edges.size() == mesh.edges_num && valid_faces_count == mesh.faces_num &&
               !any_corner_edges_bad;

  if (mesh_mut) {
    Mesh &mesh = *mesh_mut;
    if (valid_and_missing_edge_faces.size() < mesh.faces_num) {
      remove_invalid_faces(mesh, valid_and_missing_edge_faces);
    }

    const bool any_edges_removed = valid_edges.size() < mesh.edges_num;
    if (any_edges_removed) {
      remove_invalid_edges(mesh, valid_edges);
    }

    /* Regenerate edges if edges are missing or corner edges are bad. */
    if (any_corner_edges_bad || any_edges_removed || !faces_missing_edges.is_empty()) {
      mesh_calc_edges(mesh, true, false);
    }
  }

  valid &= validate_vertex_groups(mesh, verbose, mesh_mut);
  valid &= validate_material_indices(mesh, true, verbose, mesh_mut);
  valid &= validate_selection_history(mesh, verbose, mesh_mut);
  valid &= validate_generic_attributes(mesh, verbose, mesh_mut);
  valid &= validate_mdisps(mesh, verbose, mesh_mut);

  if (valid) {
    return true;
  }

  if (mesh_mut) {
    DEG_id_tag_update(&mesh_mut->id, ID_RECALC_GEOMETRY_ALL_MODES);
  }

  return false;
}

bool mesh_validate(Mesh &mesh, const bool verbose, const bool allow_missing_edges)
{
  if (verbose) {
    CLOG_INFO(&LOG, "Validating Mesh: %s", mesh.id.name + 2);
  }
  return mesh_validate_impl(mesh, verbose, allow_missing_edges, &mesh);
}

bool mesh_is_valid(const Mesh &mesh, const bool verbose)
{
  return mesh_validate_impl(mesh, verbose, false, nullptr);
}

bool mesh_validate_material_indices(Mesh &mesh)
{
  return validate_material_indices(mesh, false, false, &mesh);
}

IndexMask mesh_find_faces_duplicate_verts(const Mesh &mesh, IndexMaskMemory &memory)
{
  const IndexMask valid_faces(mesh.faces().size());
  return find_faces_duplicate_verts(mesh, valid_faces, memory, false);
}

}  // namespace bke
}  // namespace blender
