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

/** \file
 * \ingroup pygen
 *
 * Extend upon CPython's API, filling in some gaps, these functions use PyC_
 * prefix to distinguish them apart from CPython.
 */

/* Future-proof, See https://docs.python.org/3/c-api/arg.html#strings-and-buffers */
#define PY_SSIZE_T_CLEAN

#include <Python.h>
#include <frameobject.h>

#include "BLI_utildefines.h" /* for bool */

#include "DNA_vec_types.h" /* for rcti */

#include "py_capi_utils.hh"

#include "python_utildefines.hh"

#ifndef MATH_STANDALONE
#  include "MEM_guardedalloc.h"

#  include "BLI_string_utf8.h"
#endif

#ifdef _WIN32
#  include "BLI_math_base.h" /* isfinite() */
#endif

namespace blender {

/* -------------------------------------------------------------------- */
/** \name Fast Python to C Array Conversion for Primitive Types
 * \{ */

int PyC_AsArray_FAST(void *array,
                     const size_t array_item_size,
                     PyObject *value_fast,
                     const Py_ssize_t length,
                     const PyTypeObject *type,
                     const char *error_prefix)
{
  const Py_ssize_t value_len = PySequence_Fast_GET_SIZE(value_fast);
  PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
  Py_ssize_t i;

  BLI_assert(PyList_Check(value_fast) || PyTuple_Check(value_fast));

  if (value_len != length) {
    PyErr_Format(PyExc_TypeError,
                 "%.200s: invalid sequence length. expected %d, got %d",
                 error_prefix,
                 length,
                 value_len);
    return -1;
  }

  /* for each type */
  if (type == &PyFloat_Type) {
    switch (array_item_size) {
      case sizeof(double): {
        double *array_double = static_cast<double *>(array);
        for (i = 0; i < length; i++) {
          array_double[i] = PyFloat_AsDouble(value_fast_items[i]);
        }
        break;
      }
      case sizeof(float): {
        float *array_float = static_cast<float *>(array);
        for (i = 0; i < length; i++) {
          array_float[i] = PyFloat_AsDouble(value_fast_items[i]);
        }
        break;
      }
      default: {
        /* Internal error. */
        BLI_assert_unreachable();
      }
    }
  }
  else if (type == &PyLong_Type) {
    switch (array_item_size) {
      case sizeof(int64_t): {
        int64_t *array_int = static_cast<int64_t *>(array);
        for (i = 0; i < length; i++) {
          array_int[i] = PyC_Long_AsI64(value_fast_items[i]);
        }
        break;
      }
      case sizeof(int32_t): {
        int32_t *array_int = static_cast<int32_t *>(array);
        for (i = 0; i < length; i++) {
          array_int[i] = PyC_Long_AsI32(value_fast_items[i]);
        }
        break;
      }
      case sizeof(int16_t): {
        int16_t *array_int = static_cast<int16_t *>(array);
        for (i = 0; i < length; i++) {
          array_int[i] = PyC_Long_AsI16(value_fast_items[i]);
        }
        break;
      }
      case sizeof(int8_t): {
        int8_t *array_int = static_cast<int8_t *>(array);
        for (i = 0; i < length; i++) {
          array_int[i] = PyC_Long_AsI8(value_fast_items[i]);
        }
        break;
      }
      default: {
        /* Internal error. */
        BLI_assert_unreachable();
      }
    }
  }
  else if (type == &PyBool_Type) {
    switch (array_item_size) {
      case sizeof(int64_t): {
        int64_t *array_bool = static_cast<int64_t *>(array);
        for (i = 0; i < length; i++) {
          array_bool[i] = (PyLong_AsLong(value_fast_items[i]) != 0);
        }
        break;
      }
      case sizeof(int32_t): {
        int32_t *array_bool = static_cast<int32_t *>(array);
        for (i = 0; i < length; i++) {
          array_bool[i] = (PyLong_AsLong(value_fast_items[i]) != 0);
        }
        break;
      }
      case sizeof(int16_t): {
        int16_t *array_bool = static_cast<int16_t *>(array);
        for (i = 0; i < length; i++) {
          array_bool[i] = (PyLong_AsLong(value_fast_items[i]) != 0);
        }
        break;
      }
      case sizeof(int8_t): {
        int8_t *array_bool = static_cast<int8_t *>(array);
        for (i = 0; i < length; i++) {
          array_bool[i] = (PyLong_AsLong(value_fast_items[i]) != 0);
        }
        break;
      }
      default: {
        /* Internal error. */
        BLI_assert_unreachable();
      }
    }
  }
  else {
    PyErr_Format(PyExc_TypeError, "%s: internal error %s is invalid", error_prefix, type->tp_name);
    return -1;
  }

  if (PyErr_Occurred()) {
    PyErr_Format(PyExc_TypeError,
                 "%s: one or more items could not be used as a %s",
                 error_prefix,
                 type->tp_name);
    return -1;
  }

  return 0;
}

int PyC_AsArray(void *array,
                const size_t array_item_size,
                PyObject *value,
                const Py_ssize_t length,
                const PyTypeObject *type,
                const char *error_prefix)
{
  PyObject *value_fast;
  int ret;

  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    return -1;
  }

  ret = PyC_AsArray_FAST(array, array_item_size, value_fast, length, type, error_prefix);
  Py_DECREF(value_fast);
  return ret;
}

static int PyC_AsArray_Multi_impl(void **array_p,
                                  const size_t array_item_size,
                                  PyObject *value,
                                  const int *dims,
                                  const int dims_len,
                                  const PyTypeObject *type,
                                  const char *error_prefix);

static int PyC_AsArray_Multi_FAST_impl(void **array_p,
                                       const size_t array_item_size,
                                       PyObject *value_fast,
                                       const int *dims,
                                       const int dims_len,
                                       const PyTypeObject *type,
                                       const char *error_prefix)
{
  const Py_ssize_t value_len = PySequence_Fast_GET_SIZE(value_fast);
  const int length = dims[0];

  if (dims_len == 1) {
    if (PyC_AsArray_FAST(*array_p, array_item_size, value_fast, length, type, error_prefix) == -1)
    {
      return -1;
    }
    *array_p = POINTER_OFFSET(*array_p, array_item_size * length);
  }
  else {
    if (value_len != length) {
      PyErr_Format(PyExc_TypeError,
                   "%.200s: invalid sequence length. expected %d, got %d",
                   error_prefix,
                   length,
                   value_len);
      return -1;
    }

    PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
    const int *dims_next = dims + 1;
    const int dims_next_len = dims_len - 1;

    for (int i = 0; i < length; i++) {
      if (PyC_AsArray_Multi_impl(array_p,
                                 array_item_size,
                                 value_fast_items[i],
                                 dims_next,
                                 dims_next_len,
                                 type,
                                 error_prefix) == -1)
      {
        return -1;
      }
    }
  }
  return 0;
}

static int PyC_AsArray_Multi_impl(void **array_p,
                                  const size_t array_item_size,
                                  PyObject *value,
                                  const int *dims,
                                  const int dims_len,
                                  const PyTypeObject *type,
                                  const char *error_prefix)
{
  PyObject *value_fast;
  int ret;

  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    return -1;
  }

  ret = PyC_AsArray_Multi_FAST_impl(
      array_p, array_item_size, value_fast, dims, dims_len, type, error_prefix);
  Py_DECREF(value_fast);
  return ret;
}

int PyC_AsArray_Multi_FAST(void *array,
                           const size_t array_item_size,
                           PyObject *value_fast,
                           const int *dims,
                           const int dims_len,
                           const PyTypeObject *type,
                           const char *error_prefix)
{
  return PyC_AsArray_Multi_FAST_impl(
      &array, array_item_size, value_fast, dims, dims_len, type, error_prefix);
}

int PyC_AsArray_Multi(void *array,
                      const size_t array_item_size,
                      PyObject *value,
                      const int *dims,
                      const int dims_len,
                      const PyTypeObject *type,
                      const char *error_prefix)
{
  return PyC_AsArray_Multi_impl(
      &array, array_item_size, value, dims, dims_len, type, error_prefix);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Typed Tuple Packing
 *
 * \note See #PyC_Tuple_Pack_* macros that take multiple arguments.
 * \{ */

PyObject *PyC_Tuple_PackArray_F32(const float *array, uint len)
{
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(array[i]));
  }
  return tuple;
}

PyObject *PyC_Tuple_PackArray_F64(const double *array, uint len)
{
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(tuple, i, PyFloat_FromDouble(array[i]));
  }
  return tuple;
}

PyObject *PyC_Tuple_PackArray_I32(const int *array, uint len)
{
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(tuple, i, PyLong_FromLong(array[i]));
  }
  return tuple;
}

PyObject *PyC_Tuple_PackArray_I32FromBool(const int *array, uint len)
{
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(tuple, i, PyBool_FromLong(array[i]));
  }
  return tuple;
}

PyObject *PyC_Tuple_PackArray_Bool(const bool *array, uint len)
{
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(tuple, i, PyBool_FromLong(array[i]));
  }
  return tuple;
}

PyObject *PyC_Tuple_PackArray_String(const char **array, uint len)
{
  /* Not part of numeric array packing but useful none the less. */
  PyObject *tuple = PyTuple_New(len);
  for (uint i = 0; i < len; i++) {
    if (PyObject *value = PyUnicode_FromString(array[i])) {
      PyTuple_SET_ITEM(tuple, i, value);
    }
    else {
      Py_DECREF(tuple);
      tuple = nullptr;
      break;
    }
  }
  return tuple;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Typed Tuple Packing (Multi-Dimensional)
 * \{ */

static PyObject *PyC_Tuple_PackArray_Multi_F32_impl(const float **array_p,
                                                    const int dims[],
                                                    const int dims_len)
{
  const int len = dims[0];
  if (dims_len == 1) {
    PyObject *tuple = PyC_Tuple_PackArray_F32(*array_p, len);
    *array_p = (*array_p) + len;
    return tuple;
  }
  PyObject *tuple = PyTuple_New(dims[0]);
  const int *dims_next = dims + 1;
  const int dims_next_len = dims_len - 1;
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(
        tuple, i, PyC_Tuple_PackArray_Multi_F32_impl(array_p, dims_next, dims_next_len));
  }
  return tuple;
}
PyObject *PyC_Tuple_PackArray_Multi_F32(const float *array, const int dims[], const int dims_len)
{
  return PyC_Tuple_PackArray_Multi_F32_impl(&array, dims, dims_len);
}

static PyObject *PyC_Tuple_PackArray_Multi_F64_impl(const double **array_p,
                                                    const int dims[],
                                                    const int dims_len)
{
  const int len = dims[0];
  if (dims_len == 1) {
    PyObject *tuple = PyC_Tuple_PackArray_F64(*array_p, len);
    *array_p = (*array_p) + len;
    return tuple;
  }
  PyObject *tuple = PyTuple_New(dims[0]);
  const int *dims_next = dims + 1;
  const int dims_next_len = dims_len - 1;
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(
        tuple, i, PyC_Tuple_PackArray_Multi_F64_impl(array_p, dims_next, dims_next_len));
  }
  return tuple;
}
PyObject *PyC_Tuple_PackArray_Multi_F64(const double *array, const int dims[], const int dims_len)
{
  return PyC_Tuple_PackArray_Multi_F64_impl(&array, dims, dims_len);
}

static PyObject *PyC_Tuple_PackArray_Multi_I32_impl(const int **array_p,
                                                    const int dims[],
                                                    const int dims_len)
{
  const int len = dims[0];
  if (dims_len == 1) {
    PyObject *tuple = PyC_Tuple_PackArray_I32(*array_p, len);
    *array_p = (*array_p) + len;
    return tuple;
  }
  PyObject *tuple = PyTuple_New(dims[0]);
  const int *dims_next = dims + 1;
  const int dims_next_len = dims_len - 1;
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(
        tuple, i, PyC_Tuple_PackArray_Multi_I32_impl(array_p, dims_next, dims_next_len));
  }
  return tuple;
}
PyObject *PyC_Tuple_PackArray_Multi_I32(const int *array, const int dims[], const int dims_len)
{
  return PyC_Tuple_PackArray_Multi_I32_impl(&array, dims, dims_len);
}

static PyObject *PyC_Tuple_PackArray_Multi_Bool_impl(const bool **array_p,
                                                     const int dims[],
                                                     const int dims_len)
{
  const int len = dims[0];
  if (dims_len == 1) {
    PyObject *tuple = PyC_Tuple_PackArray_Bool(*array_p, len);
    *array_p = (*array_p) + len;
    return tuple;
  }
  PyObject *tuple = PyTuple_New(dims[0]);
  const int *dims_next = dims + 1;
  const int dims_next_len = dims_len - 1;
  for (uint i = 0; i < len; i++) {
    PyTuple_SET_ITEM(
        tuple, i, PyC_Tuple_PackArray_Multi_Bool_impl(array_p, dims_next, dims_next_len));
  }
  return tuple;
}
PyObject *PyC_Tuple_PackArray_Multi_Bool(const bool *array, const int dims[], const int dims_len)
{
  return PyC_Tuple_PackArray_Multi_Bool_impl(&array, dims, dims_len);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Tuple/List Filling
 * \{ */

void PyC_Tuple_Fill(PyObject *tuple, PyObject *value)
{
  const uint tot = PyTuple_GET_SIZE(tuple);
  uint i;

  for (i = 0; i < tot; i++) {
    PyTuple_SET_ITEM(tuple, i, value);
    Py_INCREF(value);
  }
}

void PyC_List_Fill(PyObject *list, PyObject *value)
{
  const uint tot = PyList_GET_SIZE(list);
  uint i;

  for (i = 0; i < tot; i++) {
    PyList_SET_ITEM(list, i, value);
    Py_INCREF(value);
  }
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Bool/Enum Argument Parsing
 * \{ */

int PyC_ParseBool(PyObject *o, void *p)
{
  bool *bool_p = static_cast<bool *>(p);
  long value;
  if (((value = PyLong_AsLong(o)) == -1) || !ELEM(value, 0, 1)) {
    PyErr_Format(PyExc_ValueError, "expected a bool or int (0/1), got %s", Py_TYPE(o)->tp_name);
    return 0;
  }

  *bool_p = value ? true : false;
  return 1;
}

int PyC_ParseTypeOrNone(PyObject *o, void *p)
{
  PyC_TypeOrNone *data = static_cast<PyC_TypeOrNone *>(p);
  if (o == Py_None) {
    *data->value_p = nullptr;
    return 1;
  }
  if (!PyObject_TypeCheck(o, data->type)) {
    PyErr_Format(PyExc_TypeError,
                 "expected %.200s or None, not %.200s",
                 data->type->tp_name,
                 Py_TYPE(o)->tp_name);
    return 0;
  }
  *data->value_p = o;
  return 1;
}

int PyC_ParseOptionalInt(PyObject *o, void *p)
{
  std::optional<int> *value_p = static_cast<std::optional<int> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  const int value = PyC_Long_AsI32(o);
  if (value == -1 && PyErr_Occurred()) {
    return 0;
  }
  *value_p = value;
  return 1;
}

int PyC_ParseOptionalDouble(PyObject *o, void *p)
{
  std::optional<double> *value_p = static_cast<std::optional<double> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  const double value = PyFloat_AsDouble(o);
  if (value == -1.0 && PyErr_Occurred()) {
    return 0;
  }
  *value_p = value;
  return 1;
}

int PyC_ParseOptionalFloat(PyObject *o, void *p)
{
  std::optional<float> *value_p = static_cast<std::optional<float> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  const double value = PyFloat_AsDouble(o);
  if (value == -1.0 && PyErr_Occurred()) {
    return 0;
  }
  *value_p = float(value);
  return 1;
}

int PyC_ParseOptionalUInt(PyObject *o, void *p)
{
  std::optional<uint> *value_p = static_cast<std::optional<uint> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  const uint value = PyC_Long_AsU32(o);
  if (value == uint(-1) && PyErr_Occurred()) {
    return 0;
  }
  *value_p = value;
  return 1;
}

int PyC_ParseOptionalBool(PyObject *o, void *p)
{
  std::optional<bool> *value_p = static_cast<std::optional<bool> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  long value;
  if (((value = PyLong_AsLong(o)) == -1) || !ELEM(value, 0, 1)) {
    PyErr_Format(
        PyExc_ValueError, "expected a bool, int (0/1), or None, got %s", Py_TYPE(o)->tp_name);
    return 0;
  }
  *value_p = value ? true : false;
  return 1;
}

int PyC_ParseRectI(PyObject *o, void *p)
{
  rcti *rect = static_cast<rcti *>(p);
  if (!PyArg_ParseTuple(o, "(ii)(ii)", &rect->xmin, &rect->ymin, &rect->xmax, &rect->ymax)) {
    return 0;
  }
  return 1;
}

int PyC_ParseOptionalRectI(PyObject *o, void *p)
{
  std::optional<rcti> *value_p = static_cast<std::optional<rcti> *>(p);
  if (o == Py_None) {
    value_p->reset();
    return 1;
  }
  rcti rect;
  if (!PyC_ParseRectI(o, &rect)) {
    return 0;
  }
  *value_p = rect;
  return 1;
}

int PyC_ParseStringEnum(PyObject *o, void *p)
{
  PyC_StringEnum *e = static_cast<PyC_StringEnum *>(p);
  const char *value = PyUnicode_AsUTF8(o);
  if (value == nullptr) {
    PyErr_Format(PyExc_ValueError, "expected a string, got %s", Py_TYPE(o)->tp_name);
    return 0;
  }
  int i;
  for (i = 0; e->items[i].id; i++) {
    if (STREQ(e->items[i].id, value)) {
      e->value_found = e->items[i].value;
      return 1;
    }
  }

  /* Set as a precaution. */
  e->value_found = -1;

  PyObject *enum_items = PyTuple_New(i);
  for (i = 0; e->items[i].id; i++) {
    PyTuple_SET_ITEM(enum_items, i, PyUnicode_FromString(e->items[i].id));
  }
  PyErr_Format(PyExc_ValueError, "expected a string in %S, got '%s'", enum_items, value);
  Py_DECREF(enum_items);
  return 0;
}

const char *PyC_StringEnum_FindIDFromValue(const PyC_StringEnumItems *items, const int value)
{
  for (int i = 0; items[i].id; i++) {
    if (items[i].value == value) {
      return items[i].id;
    }
  }
  return nullptr;
}

int PyC_CheckArgs_DeepCopy(PyObject *args)
{
  PyObject *dummy_pydict;
  return PyArg_ParseTuple(args, "|O!:__deepcopy__", &PyDict_Type, &dummy_pydict) != 0;
}

/** \} */

#ifndef MATH_STANDALONE

/* -------------------------------------------------------------------- */
/** \name Simple Printing (for debugging)
 *
 * These are useful to run directly from a debugger to be able to inspect the state.
 * \{ */

void PyC_ObSpit(const char *name, PyObject *var)
{
  const char *null_str = "<null>";
  fprintf(stderr, "<%s> : ", name);
  if (var == nullptr) {
    fprintf(stderr, "%s\n", null_str);
  }
  else {
    PyObject_Print(var, stderr, 0);
    const PyTypeObject *type = Py_TYPE(var);
    fprintf(stderr,
            " ref:%d, ptr:%p, type: %s\n",
            int(var->ob_refcnt),
            static_cast<void *>(var),
            type ? type->tp_name : null_str);
  }
}

void PyC_ObSpitStr(char *result, size_t result_maxncpy, PyObject *var)
{
  /* No name, creator of string can manage that. */
  const char *null_str = "<null>";
  if (var == nullptr) {
    BLI_strncpy_utf8(result, null_str, result_maxncpy);
  }
  else {
    const PyTypeObject *type = Py_TYPE(var);
    PyObject *var_str = PyObject_Repr(var);
    if (var_str == nullptr) {
      /* We could print error here,
       * but this may be used for generating errors - so don't for now. */
      PyErr_Clear();
    }
    BLI_snprintf_utf8(result,
                      result_maxncpy,
                      " ref=%d, ptr=%p, type=%s, value=%.200s",
                      int(var->ob_refcnt),
                      static_cast<void *>(var),
                      type ? type->tp_name : null_str,
                      var_str ? PyUnicode_AsUTF8(var_str) : "<error>");
    if (var_str != nullptr) {
      Py_DECREF(var_str);
    }
  }
}

void PyC_LineSpit()
{

  const char *filename;
  int lineno;

  /* NOTE: allow calling from outside python (RNA). */
  if (!PyC_IsInterpreterActive()) {
    fprintf(stderr, "Python line lookup failed, interpreter inactive\n");
    return;
  }

  PyErr_Clear();
  PyC_FileAndNum(&filename, &lineno);

  fprintf(stderr, "%s:%d\n", filename, lineno);
}

void PyC_StackSpit()
{
  /* NOTE: allow calling from outside python (RNA). */
  if (!PyC_IsInterpreterActive()) {
    fprintf(stderr, "Python line lookup failed, interpreter inactive\n");
    return;
  }

  /* lame but handy */
  const PyGILState_STATE gilstate = PyGILState_Ensure();
  PyRun_SimpleString("__import__('traceback').print_stack()");
  PyGILState_Release(gilstate);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Access Current Frame File Name & Line Number
 * \{ */

void PyC_FileAndNum(const char **r_filename, int *r_lineno)
{
  PyFrameObject *frame;
  PyCodeObject *code;

  if (r_filename) {
    *r_filename = nullptr;
  }
  if (r_lineno) {
    *r_lineno = -1;
  }

  if (!(frame = PyEval_GetFrame())) {
    return;
  }
  if (!(code = PyFrame_GetCode(frame))) {
    return;
  }

  /* when executing a script */
  if (r_filename) {
    *r_filename = PyUnicode_AsUTF8(code->co_filename);
  }

  /* when executing a module */
  if (r_filename && *r_filename == nullptr) {
    /* try an alternative method to get the r_filename - module based
     * references below are all borrowed (double checked) */
    PyObject *mod_name = PyDict_GetItemString(PyEval_GetGlobals(), "__name__");
    if (mod_name) {
      PyObject *mod = PyDict_GetItem(PyImport_GetModuleDict(), mod_name);
      if (mod) {
        PyObject *mod_file = PyModule_GetFilenameObject(mod);
        if (mod_file) {
          *r_filename = PyUnicode_AsUTF8(mod_name);
          Py_DECREF(mod_file);
        }
        else {
          PyErr_Clear();
        }
      }

      /* unlikely, fallback */
      if (*r_filename == nullptr) {
        *r_filename = PyUnicode_AsUTF8(mod_name);
      }
    }
  }

  if (r_lineno) {
    *r_lineno = PyFrame_GetLineNumber(frame);
  }

  Py_DECREF(code);
}

void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno)
{
  if (!PyC_IsInterpreterActive()) {
    return;
  }

  PyC_FileAndNum(r_filename, r_lineno);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Object Access Utilities
 * \{ */

PyObject *PyC_Object_GetAttrStringArgs(PyObject *o, Py_ssize_t n, ...)
{
  /* NOTE: Would be nice if python had this built in. */

  Py_ssize_t i;
  PyObject *item = o;
  const char *attr;

  va_list vargs;

  va_start(vargs, n);
  for (i = 0; i < n; i++) {
    attr = va_arg(vargs, char *);
    item = PyObject_GetAttrString(item, attr);

    if (item) {
      Py_DECREF(item);
    }
    else {
      /* python will set the error value here */
      break;
    }
  }
  va_end(vargs);

  Py_XINCREF(item); /* final value has is increfed, to match PyObject_GetAttrString */
  return item;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Frozen Set Creation
 * \{ */

PyObject *PyC_FrozenSetFromStrings(const char **strings)
{
  const char **str;
  PyObject *ret;

  ret = PyFrozenSet_New(nullptr);

  for (str = strings; *str; str++) {
    PyObject *py_str = PyUnicode_FromString(*str);
    PySet_Add(ret, py_str);
    Py_DECREF(py_str);
  }

  return ret;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Exception Utilities
 * \{ */

PyObject *PyC_Err_Format_Prefix(PyObject *exception_type_prefix, const char *format, ...)
{
  PyObject *error_value_as_unicode = nullptr;

  if (PyErr_Occurred()) {
    PyObject *error_type, *error_value, *error_traceback;
    PyErr_Fetch(&error_type, &error_value, &error_traceback);

    if (PyUnicode_Check(error_value)) {
      error_value_as_unicode = error_value;
      Py_INCREF(error_value_as_unicode);
    }
    else {
      error_value_as_unicode = PyUnicode_FromFormat(
          "%.200s(%S)", Py_TYPE(error_value)->tp_name, error_value);
    }
    PyErr_Restore(error_type, error_value, error_traceback);
  }

  va_list args;

  va_start(args, format);
  PyObject *error_value_format = PyUnicode_FromFormatV(format, args); /* Can fail and be null. */
  va_end(args);

  if (error_value_as_unicode) {
    if (error_value_format) {
      PyObject *error_value_format_prev = error_value_format;
      error_value_format = PyUnicode_FromFormat(
          "%S, %S", error_value_format, error_value_as_unicode);
      Py_DECREF(error_value_format_prev);
    }
    else {
      /* Should never happen, hints at incorrect API use or memory corruption. */
      error_value_format = PyUnicode_FromFormat("(internal error), %S", error_value_as_unicode);
    }
    Py_DECREF(error_value_as_unicode);
  }

  PyErr_SetObject(exception_type_prefix, error_value_format);
  Py_XDECREF(error_value_format);

  /* Strange to always return null, matches #PyErr_Format. */
  return nullptr;
}

PyObject *PyC_Err_SetString_Prefix(PyObject *exception_type_prefix, const char *str)
{
  return PyC_Err_Format_Prefix(exception_type_prefix, "%s", str);
}

void PyC_Err_PrintWithFunc(PyObject *py_func)
{
  /* since we return to C code we can't leave the error */
  PyCodeObject *f_code = reinterpret_cast<PyCodeObject *>(PyFunction_GET_CODE(py_func));
  PyErr_Print();

  /* use py style error */
  fprintf(stderr,
          "File \"%s\", line %d, in %s\n",
          PyUnicode_AsUTF8(f_code->co_filename),
          f_code->co_firstlineno,
          PyUnicode_AsUTF8((reinterpret_cast<PyFunctionObject *>(py_func))->func_name));
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Exception Buffer Access
 * \{ */

/**
 * Get exit code `sys.exit(..)` was called with, see #pyc_exception_buffer_handle_system_exit.
 */
static std::optional<int> g_system_exit_code;
std::optional<int> PyC_ExceptionSystemExitCode()
{
  return g_system_exit_code;
}

bool PyC_Err_CaptureSystemExitCode()
{
  if (!PyErr_ExceptionMatches(PyExc_SystemExit)) {
    return false;
  }

  /* Get exit code and put back exception. */
  PyObject *exc_obj = PyErr_GetRaisedException();
  PyObject *code_obj = ((PySystemExitObject *)exc_obj)->code;
  g_system_exit_code = 0;
  if (code_obj && code_obj != Py_None) {
    g_system_exit_code = PyLong_Check(code_obj) ? int(PyLong_AsLong(code_obj)) : 1;
  }
  PyErr_SetRaisedException(exc_obj);
  return true;
}

/**
 * When a script calls `sys.exit(..)` it is expected that Blender quits,
 * internally this raises as `SystemExit` exception which this function detects.
 *
 * Historically Blender would call `PyErr_Print` when encountering an error.
 * In some cases `PyErr_Print` is still called, but not all.
 * When only #PyC_ExceptionBuffer is used to access the error, it's not desirable
 * that `sys.exit()` fails to exit, causing `sys.exit(..)` to arbitrarily work depending
 * on the internals of Blender's error handling.
 * To avoid this discrepancy, detect when `PyErr_Print` *would* exit and call it in that case.
 * It's important to call `PyErr_Print` (instead of simply exiting), because the exception
 * may contain a message which the user should see.
 *
 * \note No need to handle freeing resources here, Python's `atexit` is used to cleanup
 * Blender's state when Python requests an exit (via `bpy_atexit` callback).
 */
static void pyc_exception_buffer_handle_system_exit()
{
  if (!PyC_Err_CaptureSystemExitCode()) {
    return;
  }
  /* Inspecting, follow Python's logic in #_Py_HandleSystemExit & treat as a regular exception. */
#  if 0 /* FIXME: */
  if (_Py_GetConfig()->inspect) {
    return;
  }
#  endif

  /* NOTE(@ideasman42): A `SystemExit` exception will exit immediately (unless inspecting).
   * So print the error and exit now. Without this #PyErr_Display shows the error stack-trace
   * as a regular exception (as if something went wrong) and fail to exit.
   * see: #99966 for additional context.
   *
   * Arguably accessing a `SystemExit` exception as a buffer should be supported without exiting.
   * (by temporarily enabling inspection for example) however - it's not obvious exactly when this
   * should be enabled and complicates the Python API by introducing different kinds of execution.
   * Since the rule of thumb is for Blender's embedded Python to match stand-alone Python,
   * favor exiting when a `SystemExit` is raised.
   * Especially since this exception more likely to be used for background/batch-processing
   * utilities where exiting immediately makes sense, the possibility of this being called
   * indirectly from python-drivers or modal-operators is less of a concern. */
  PyErr_Print();
}

PyObject *PyC_ExceptionBuffer()
{
  BLI_assert(PyErr_Occurred());

  pyc_exception_buffer_handle_system_exit();

  /* The resulting exception as a string (return value). */
  PyObject *result = nullptr;

  PyObject *error_type, *error_value, *error_traceback;
  PyErr_Fetch(&error_type, &error_value, &error_traceback);

  /* Normalizing is needed because it's possible the error value is a string which
   * #PyErr_Display will fail to print. */
  PyErr_NormalizeException(&error_type, &error_value, &error_traceback);

  /* `io.StringIO()`. */
  PyObject *string_io = nullptr;
  PyObject *string_io_mod = nullptr;
  PyObject *string_io_getvalue = nullptr;
  if ((string_io_mod = PyImport_ImportModule("io")) &&
      (string_io = PyObject_CallMethod(string_io_mod, "StringIO", nullptr)) &&
      (string_io_getvalue = PyObject_GetAttrString(string_io, "getvalue")))
  {
    PyObject *stderr_backup = PySys_GetObject("stderr"); /* Borrowed. */
    /* Since these were borrowed we don't want them freed when replaced. */
    Py_INCREF(stderr_backup);
    PySys_SetObject("stderr", string_io); /* Freed when overwriting. */

    PyErr_Display(error_type, error_value, error_traceback);

    result = PyObject_CallObject(string_io_getvalue, nullptr);
    PySys_SetObject("stderr", stderr_backup);
    Py_DECREF(stderr_backup); /* Now `sys` owns the reference again. */
  }
  else {
    PySys_WriteStderr("Internal error creating: io.StringIO()!\n");
    if (UNLIKELY(PyErr_Occurred())) {
      PyErr_Print(); /* Show the error accessing `io.StringIO`. */
    }
    PyErr_Display(error_type, error_value, error_traceback);
  }

  Py_XDECREF(string_io_mod);
  Py_XDECREF(string_io_getvalue);
  Py_XDECREF(string_io); /* Free the original reference. */

  if (result == nullptr) {
    result = PyObject_Str(error_value);
    /* Python does this too. */
    if (UNLIKELY(result == nullptr)) {
      result = PyUnicode_FromFormat("<unprintable %s object>", Py_TYPE(error_value)->tp_name);
    }
  }

  PyErr_Restore(error_type, error_value, error_traceback);

  return result;
}

PyObject *PyC_ExceptionBuffer_Simple()
{
  BLI_assert(PyErr_Occurred());

  pyc_exception_buffer_handle_system_exit();

  /* The resulting exception as a string (return value). */
  PyObject *result = nullptr;

  PyObject *error_type, *error_value, *error_traceback;

  PyErr_Fetch(&error_type, &error_value, &error_traceback);

  if (PyErr_GivenExceptionMatches(error_type, PyExc_SyntaxError)) {
    /* Special exception for syntax errors,
     * in these cases the full error is verbose and not very useful,
     * just use the initial text so we know what the error is. */
    if (PyTuple_CheckExact(error_value) && PyTuple_GET_SIZE(error_value) >= 1) {
      result = PyObject_Str(PyTuple_GET_ITEM(error_value, 0));
    }
  }

  if (result == nullptr) {
    result = PyObject_Str(error_value);
    /* Python does this too. */
    if (UNLIKELY(result == nullptr)) {
      result = PyUnicode_FromFormat("<unprintable %s object>", Py_TYPE(error_value)->tp_name);
    }
  }

  PyErr_Restore(error_type, error_value, error_traceback);

  return result;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Unicode Conversion
 *
 * In some cases we need to coerce strings, avoid doing this inline.
 * \{ */

const char *PyC_UnicodeAsBytesAndSize(PyObject *py_str, Py_ssize_t *r_size, PyObject **r_coerce)
{
  const char *result;

  result = PyUnicode_AsUTF8AndSize(py_str, r_size);

  if (result) {
    /* 99% of the time this is enough but we better support non unicode
     * chars since blender doesn't limit this */
    return result;
  }

  PyErr_Clear();

  if (PyBytes_Check(py_str)) {
    *r_size = PyBytes_GET_SIZE(py_str);
    return PyBytes_AS_STRING(py_str);
  }
  if ((*r_coerce = PyUnicode_EncodeFSDefault(py_str))) {
    *r_size = PyBytes_GET_SIZE(*r_coerce);
    return PyBytes_AS_STRING(*r_coerce);
  }

  /* leave error raised from EncodeFS */
  return nullptr;
}

const char *PyC_UnicodeAsBytes(PyObject *py_str, PyObject **r_coerce)
{
  const char *result;

  result = PyUnicode_AsUTF8(py_str);

  if (result) {
    /* 99% of the time this is enough but we better support non unicode
     * chars since blender doesn't limit this. */
    return result;
  }

  PyErr_Clear();

  if (PyBytes_Check(py_str)) {
    return PyBytes_AS_STRING(py_str);
  }
  if ((*r_coerce = PyUnicode_EncodeFSDefault(py_str))) {
    return PyBytes_AS_STRING(*r_coerce);
  }

  /* leave error raised from EncodeFS */
  return nullptr;
}

PyObject *PyC_UnicodeFromBytesAndSize(const char *str, Py_ssize_t size)
{
  PyObject *result = PyUnicode_FromStringAndSize(str, size);
  if (result) {
    /* 99% of the time this is enough but we better support non unicode
     * chars since blender doesn't limit this */
    return result;
  }

  PyErr_Clear();
  /* this means paths will always be accessible once converted, on all OS's */
  result = PyUnicode_DecodeFSDefaultAndSize(str, size);
  return result;
}

PyObject *PyC_UnicodeFromBytes(const char *str)
{
  return PyC_UnicodeFromBytesAndSize(str, strlen(str));
}

PyObject *PyC_UnicodeFromStdStr(const std::string &str)
{
  return PyC_UnicodeFromBytesAndSize(str.c_str(), str.length());
}

int PyC_ParseUnicodeAsBytesAndSize(PyObject *o, void *p)
{
  PyC_UnicodeAsBytesAndSize_Data *data = static_cast<PyC_UnicodeAsBytesAndSize_Data *>(p);
  if (UNLIKELY(o == nullptr)) {
    /* Signal to cleanup. */
    Py_CLEAR(data->value_coerce);
    return 1;
  }
  /* The value must be cleared. */
  BLI_assert(!(data->value_coerce || data->value || data->value_len));
  data->value = PyC_UnicodeAsBytesAndSize(o, &data->value_len, &data->value_coerce);
  if (data->value == nullptr) {
    /* Leave the error as-is. */
    return 0;
  }
  /* Needed to #Py_DECREF `data->value_coerce` on future failure. */
  return data->value_coerce ? Py_CLEANUP_SUPPORTED : 1;
}

int PyC_ParseUnicodeAsBytesAndSize_OrNone(PyObject *o, void *p)
{
  if (o == Py_None) {
    PyC_UnicodeAsBytesAndSize_Data *data = static_cast<PyC_UnicodeAsBytesAndSize_Data *>(p);
    BLI_assert(!(data->value_coerce || data->value || data->value_len));
    UNUSED_VARS_NDEBUG(data);
    return 1;
  }
  return PyC_ParseUnicodeAsBytesAndSize(o, p);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Name Space Creation/Manipulation
 * \{ */

PyObject *PyC_DefaultNameSpace(const char *filename)
{
  PyObject *modules = PyImport_GetModuleDict();
  PyObject *builtins = PyEval_GetBuiltins();
  PyObject *mod_main = PyModule_New("__main__");
  PyDict_SetItemString(modules, "__main__", mod_main);
  Py_DECREF(mod_main); /* `sys.modules` owns now. */
  PyModule_AddStringConstant(mod_main, "__name__", "__main__");

  /* This won't map to a real file when executing text-blocks and buttons.
   * In this case an identifier is typically used that is surrounded by angle-brackets.
   * It's mainly helpful for the UI and messages to show *something*. */
  PyModule_AddObject(mod_main, "__file__", PyC_UnicodeFromBytes(filename));
  PyModule_AddObjectRef(mod_main, "__builtins__", builtins);

  return PyModule_GetDict(mod_main);
}

bool PyC_NameSpace_ImportArray(PyObject *py_dict, const char *imports[])
{
  for (int i = 0; imports[i]; i++) {
    PyObject *name = PyUnicode_FromString(imports[i]);
    PyObject *mod = PyImport_ImportModuleLevelObject(name, nullptr, nullptr, nullptr, 0);
    bool ok = false;
    if (mod) {
      PyDict_SetItem(py_dict, name, mod);
      ok = true;
      Py_DECREF(mod);
    }
    Py_DECREF(name);

    if (!ok) {
      return false;
    }
  }
  return true;
}

PyObject *PyC_MainModule_Backup()
{
  PyObject *modules = PyImport_GetModuleDict();
  PyObject *main_mod = PyDict_GetItemString(modules, "__main__");
  if (main_mod) {
    /* Ensure the backed up module is kept until it's ownership */
    /* is transferred back to `sys.modules`. */
    Py_INCREF(main_mod);
  }
  return main_mod;
}

void PyC_MainModule_Restore(PyObject *main_mod)
{
  PyObject *modules = PyImport_GetModuleDict();
  if (main_mod) {
    PyDict_SetItemString(modules, "__main__", main_mod);
    Py_DECREF(main_mod);
  }
  else {
    PyDict_DelItemString(modules, "__main__");
  }
}

int PyC_Module_AddToSysModules(PyObject *sys_modules, PyObject *module)
{
  /* It would be OK to remove this assert if we ever wanted to add to a non-standard module dict.
   * Currently it's only ever expected that they match, hence the assert. */
  BLI_assert(sys_modules == PyImport_GetModuleDict());
  PyObject *name = PyModule_GetNameObject(module);
  if (name == nullptr) {
    return -1;
  }
  int result = PyDict_SetItem(sys_modules, name, module);
  Py_DECREF(name);
  return result;
}

bool PyC_IsInterpreterActive()
{
  /* instead of PyThreadState_Get, which calls Py_FatalError */
  return (PyThreadState_GetDict() != nullptr);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #Py_SetPythonHome Wrapper
 * \{ */

void PyC_RunQuicky(const char *filepath, int n, ...)
{
  /* NOTE: Would be nice if python had this built in
   * See: https://developer.blender.org/docs/handbook/tooling/pyfromc/ */

  FILE *fp = fopen(filepath, "r");

  if (fp) {
    const PyGILState_STATE gilstate = PyGILState_Ensure();

    va_list vargs;

    Py_ssize_t *sizes = static_cast<Py_ssize_t *>(PyMem_MALLOC(sizeof(*sizes) * (n / 2)));
    int i;

    PyObject *py_dict = PyC_DefaultNameSpace(filepath);
    PyObject *values = PyList_New(n / 2); /* namespace owns this, don't free */

    PyObject *py_result, *ret;

    PyObject *struct_mod = PyImport_ImportModule("struct");
    PyObject *calcsize = PyObject_GetAttrString(struct_mod, "calcsize"); /* struct.calcsize */
    PyObject *pack = PyObject_GetAttrString(struct_mod, "pack");         /* struct.pack */
    PyObject *unpack = PyObject_GetAttrString(struct_mod, "unpack");     /* struct.unpack */

    Py_DECREF(struct_mod);

    va_start(vargs, n);
    for (i = 0; i * 2 < n; i++) {
      const char *format = va_arg(vargs, char *);
      void *ptr = va_arg(vargs, void *);

      ret = PyObject_CallFunction(calcsize, "s", format);

      if (ret) {
        sizes[i] = PyLong_AsLong(ret);
        Py_DECREF(ret);
        ret = PyObject_CallFunction(unpack, "sy#", format, static_cast<char *>(ptr), sizes[i]);
      }

      if (ret == nullptr) {
        printf("%s error, line:%d\n", __func__, __LINE__);
        PyErr_Print();

        PyList_SET_ITEM(values, i, Py_NewRef(Py_None)); /* hold user */

        sizes[i] = 0;
      }
      else {
        if (PyTuple_GET_SIZE(ret) == 1) {
          /* convenience, convert single tuples into single values */
          PyObject *tmp = PyTuple_GET_ITEM(ret, 0);
          Py_INCREF(tmp);
          Py_DECREF(ret);
          ret = tmp;
        }

        PyList_SET_ITEM(values, i, ret); /* hold user */
      }
    }
    va_end(vargs);

    /* set the value so we can access it */
    PyDict_SetItemString(py_dict, "values", values);
    Py_DECREF(values);

    py_result = PyRun_File(fp, filepath, Py_file_input, py_dict, py_dict);

    fclose(fp);

    if (py_result) {

      /* we could skip this but then only slice assignment would work
       * better not be so strict */
      values = PyDict_GetItemString(py_dict, "values");

      if (values && PyList_Check(values)) {

        /* don't use the result */
        Py_DECREF(py_result);
        py_result = nullptr;

        /* now get the values back */
        va_start(vargs, n);
        for (i = 0; i * 2 < n; i++) {
          const char *format = va_arg(vargs, char *);
          void *ptr = va_arg(vargs, void *);

          PyObject *item;
          PyObject *item_new;
          /* prepend the string formatting and remake the tuple */
          item = PyList_GET_ITEM(values, i);
          if (PyTuple_CheckExact(item)) {
            int ofs = PyTuple_GET_SIZE(item);
            item_new = PyTuple_New(ofs + 1);
            while (ofs--) {
              PyObject *member = PyTuple_GET_ITEM(item, ofs);
              PyTuple_SET_ITEM(item_new, ofs + 1, member);
              Py_INCREF(member);
            }

            PyTuple_SET_ITEM(item_new, 0, PyUnicode_FromString(format));
          }
          else {
            item_new = Py_BuildValue("sO", format, item);
          }

          ret = PyObject_Call(pack, item_new, nullptr);

          if (ret) {
            /* copy the bytes back into memory */
            memcpy(ptr, PyBytes_AS_STRING(ret), sizes[i]);
            Py_DECREF(ret);
          }
          else {
            printf("%s error on arg '%d', line:%d\n", __func__, i, __LINE__);
            PyC_ObSpit("failed converting:", item_new);
            PyErr_Print();
          }

          Py_DECREF(item_new);
        }
        va_end(vargs);
      }
      else {
        printf("%s error, 'values' not a list, line:%d\n", __func__, __LINE__);
      }
    }
    else {
      printf("%s error line:%d\n", __func__, __LINE__);
      PyErr_Print();
    }

    Py_DECREF(calcsize);
    Py_DECREF(pack);
    Py_DECREF(unpack);

    PyMem_FREE(sizes);

    PyGILState_Release(gilstate);
  }
  else {
    fprintf(stderr, "%s: '%s' missing\n", __func__, filepath);
  }
}

void *PyC_RNA_AsPointer(PyObject *value, const char *type_name)
{
  if (!STREQ(Py_TYPE(value)->tp_name, type_name)) {
    PyErr_Format(PyExc_TypeError,
                 "expected '%.200s' type found '%.200s' instead",
                 type_name,
                 Py_TYPE(value)->tp_name);
    return nullptr;
  }

  PyObject *as_pointer = PyObject_GetAttrString(value, "as_pointer");
  if ((as_pointer == nullptr) || !PyCallable_Check(as_pointer)) {
    Py_XDECREF(as_pointer);
    PyErr_Format(PyExc_TypeError, "Invalid %.200s pointer", type_name);
    return nullptr;
  }

  /* Must be a `type_name` object. */
  PyObject *pointer = PyObject_CallObject(as_pointer, nullptr);
  Py_DECREF(as_pointer);

  if (pointer == nullptr) {
    PyErr_SetString(PyExc_SystemError, "value.as_pointer() failed");
    return nullptr;
  }
  void *result = PyLong_AsVoidPtr(pointer);
  Py_DECREF(pointer);
  if (!result) {
    PyErr_SetString(PyExc_SystemError, "value.as_pointer() failed");
  }

  return result;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Flag Set Utilities (#PyC_FlagSet)
 *
 * Convert to/from Python set of strings to an int flag.
 * \{ */

PyObject *PyC_FlagSet_AsString(const PyC_FlagSet *item)
{
  PyObject *py_items = PyList_New(0);
  for (; item->identifier; item++) {
    PyList_APPEND(py_items, PyUnicode_FromString(item->identifier));
  }
  PyObject *py_string = PyObject_Repr(py_items);
  Py_DECREF(py_items);
  return py_string;
}

int PyC_FlagSet_ValueFromID_int(const PyC_FlagSet *item, const char *identifier, int *r_value)
{
  for (; item->identifier; item++) {
    if (STREQ(item->identifier, identifier)) {
      *r_value = item->value;
      return 1;
    }
  }

  return 0;
}

int PyC_FlagSet_ValueFromID(const PyC_FlagSet *item,
                            const char *identifier,
                            int *r_value,
                            const char *error_prefix)
{
  if (PyC_FlagSet_ValueFromID_int(item, identifier, r_value) == 0) {
    PyObject *enum_str = PyC_FlagSet_AsString(item);
    PyErr_Format(
        PyExc_ValueError, "%s: '%.200s' not found in (%U)", error_prefix, identifier, enum_str);
    Py_DECREF(enum_str);
    return -1;
  }

  return 0;
}

int PyC_FlagSet_ToBitfield(const PyC_FlagSet *items,
                           PyObject *value,
                           int *r_value,
                           const char *error_prefix)
{
  /* set of enum items, concatenate all values with OR */
  int ret, flag = 0;

  if (!PySet_Check(value)) {
    PyErr_Format(PyExc_TypeError,
                 "%.200s expected a set, not %.200s",
                 error_prefix,
                 Py_TYPE(value)->tp_name);
    return -1;
  }

  *r_value = 0;

  if (PySet_GET_SIZE(value) > 0) {
    PyObject *it = PyObject_GetIter(value);
    PyObject *key;
    while ((key = PyIter_Next(it))) {
      /* Borrow from the set. */
      Py_DECREF(key);

      const char *param = PyUnicode_AsUTF8(key);
      if (param == nullptr) {
        PyErr_Format(PyExc_TypeError,
                     "%.200s set must contain strings, not %.200s",
                     error_prefix,
                     Py_TYPE(key)->tp_name);
        break;
      }

      if (PyC_FlagSet_ValueFromID(items, param, &ret, error_prefix) < 0) {
        break;
      }

      flag |= ret;
    }
    Py_DECREF(it);
    if (key != nullptr) {
      return -1;
    }
  }

  *r_value = flag;
  return 0;
}

PyObject *PyC_FlagSet_FromBitfield(PyC_FlagSet *items, int flag)
{
  PyObject *ret = PySet_New(nullptr);
  PyObject *pystr;

  for (; items->identifier; items++) {
    if (items->value & flag) {
      pystr = PyUnicode_FromString(items->identifier);
      PySet_Add(ret, pystr);
      Py_DECREF(pystr);
    }
  }

  return ret;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Run String (Evaluate to Primitive Types)
 * \{ */

[[nodiscard]] static PyObject *pyc_run_string_as_py_object(const char *imports[],
                                                           const char *imports_star[],
                                                           const char *expr,
                                                           const char *filename)
    ATTR_NONNULL(3, 4);
static PyObject *pyc_run_string_as_py_object(const char *imports[],
                                             const char *imports_star[],
                                             const char *expr,
                                             const char *filename)
{
  PyObject *main_mod = PyC_MainModule_Backup();

  PyObject *py_dict = PyC_DefaultNameSpace(filename);

  if (imports_star) {
    for (int i = 0; imports_star[i]; i++) {
      PyObject *mod = PyImport_ImportModule(imports_star[i]);
      if (mod) {
        /* Don't overwrite existing values (override=0). */
        PyDict_Merge(py_dict, PyModule_GetDict(mod), 0);
        Py_DECREF(mod);
      }
      else { /* Highly unlikely but possibly. */
        PyErr_Print();
      }
    }
  }

  PyObject *retval;
  if (imports && !PyC_NameSpace_ImportArray(py_dict, imports)) {
    retval = nullptr; /* Failure. */
  }
  else {
    retval = PyRun_String(expr, Py_eval_input, py_dict, py_dict);
  }

  PyC_MainModule_Restore(main_mod);

  return retval;
}

bool PyC_RunString_AsNumber(const char *imports[],
                            const char *expr,
                            const char *filename,
                            double *r_value)
{
  int ok = -1;
  const char *imports_star[] = {"math", nullptr};
  if (PyObject *retval = pyc_run_string_as_py_object(imports, imports_star, expr, filename)) {
    double val;

    if (PyTuple_Check(retval)) {
      /* Users my have typed in `10km, 2m`, accumulate all values. */
      val = 0.0;
      for (int i = 0; i < PyTuple_GET_SIZE(retval); i++) {
        const double val_item = PyFloat_AsDouble(PyTuple_GET_ITEM(retval, i));
        if (val_item == -1 && PyErr_Occurred()) {
          val = -1;
          break;
        }
        val += val_item;
      }
    }
    else {
      val = PyFloat_AsDouble(retval);
    }
    Py_DECREF(retval);

    if (val == -1 && PyErr_Occurred()) {
      ok = false;
    }
    else if (!isfinite(val)) {
      *r_value = 0.0;
      ok = true;
    }
    else {
      *r_value = val;
      ok = true;
    }
  }
  else {
    ok = false;
  }
  BLI_assert(ok != -1);
  return bool(ok);
}

bool PyC_RunString_AsIntPtr(const char *imports[],
                            const char *expr,
                            const char *filename,
                            intptr_t *r_value)
{
  int ok = -1;
  if (PyObject *retval = pyc_run_string_as_py_object(imports, nullptr, expr, filename)) {
    const intptr_t val = intptr_t(PyLong_AsVoidPtr(retval));
    if (val == 0 && PyErr_Occurred()) {
      ok = false;
    }
    else {
      *r_value = val;
      ok = true;
    }

    Py_DECREF(retval);
  }
  else {
    ok = false;
  }
  BLI_assert(ok != -1);
  return ok;
}

bool PyC_RunString_AsStringAndSize(const char *imports[],
                                   const char *expr,
                                   const char *filename,
                                   char **r_value,
                                   size_t *r_value_size)
{
  int ok = -1;
  if (PyObject *retval = pyc_run_string_as_py_object(imports, nullptr, expr, filename)) {
    Py_ssize_t val_len;
    const char *val = PyUnicode_AsUTF8AndSize(retval, &val_len);
    if (val == nullptr && PyErr_Occurred()) {
      ok = false;
    }
    else {
      char *val_alloc = MEM_new_array_uninitialized<char>(size_t(val_len) + 1, __func__);
      memcpy(val_alloc, val, (size_t(val_len) + 1) * sizeof(*val_alloc));
      *r_value = val_alloc;
      *r_value_size = val_len;
      ok = true;
    }
    Py_DECREF(retval);
  }
  else {
    ok = false;
  }
  BLI_assert(ok != -1);
  return ok;
}

bool PyC_RunString_AsString(const char *imports[],
                            const char *expr,
                            const char *filename,
                            char **r_value)
{
  size_t value_size;
  return PyC_RunString_AsStringAndSize(imports, expr, filename, r_value, &value_size);
}

bool PyC_RunString_AsStringAndSizeOrNone(const char *imports[],
                                         const char *expr,
                                         const char *filename,
                                         char **r_value,
                                         size_t *r_value_size)
{
  int ok = -1;
  if (PyObject *retval = pyc_run_string_as_py_object(imports, nullptr, expr, filename)) {
    if (retval == Py_None) {
      *r_value = nullptr;
      *r_value_size = 0;
      ok = true;
    }
    else {
      Py_ssize_t val_len;
      const char *val = PyUnicode_AsUTF8AndSize(retval, &val_len);
      if (val == nullptr && PyErr_Occurred()) {
        ok = false;
      }
      else {
        char *val_alloc = MEM_new_array_uninitialized<char>(size_t(val_len) + 1, __func__);
        memcpy(val_alloc, val, (size_t(val_len) + 1) * sizeof(*val_alloc));
        *r_value = val_alloc;
        *r_value_size = val_len;
        ok = true;
      }
    }
    Py_DECREF(retval);
  }
  else {
    ok = false;
  }
  BLI_assert(ok != -1);
  return bool(ok);
}

bool PyC_RunString_AsStringOrNone(const char *imports[],
                                  const char *expr,
                                  const char *filename,
                                  char **r_value)
{
  size_t value_size;
  return PyC_RunString_AsStringAndSizeOrNone(imports, expr, filename, r_value, &value_size);
}

/** \} */

#endif /* #ifndef MATH_STANDALONE */

/* -------------------------------------------------------------------- */
/** \name Std Files Flush
 *
 * \{ */

void PyC_StdFilesFlush()
{
  /* This is ported from CPython's internal #flush_std_files (2025-03-31). The code is a bit
   * different because the original code uses some internal APIs.
   *
   * This is approximately equivalent to:
   * \code{.py}
   * try:
   *     sys.stdout.flush()
   *     sys.stderr.flush()
   * except Exception:
   *     pass
   * \endcode
   */
  PyObject *py_flush = PyUnicode_FromString("flush");
  BLI_assert(py_flush);
  for (const char *name : {"stdout", "stderr"}) {
    PyObject *py_file = PySys_GetObject(name);
    if (!py_file) {
      PyErr_Clear();
      continue;
    }
    PyObject *py_flush_retval = PyObject_CallMethodNoArgs(py_file, py_flush);
    if (py_flush_retval) {
      Py_DECREF(py_flush_retval);
    }
    else {
      PyErr_Clear();
    }
  }
  Py_DECREF(py_flush);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Int Conversion
 *
 * \note Python doesn't provide overflow checks for specific bit-widths.
 *
 * \{ */

/* Compiler optimizes out redundant checks. */
#if defined(__GNUC__) && !defined(__clang__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wtype-limits"

#endif

/* #PyLong_AsUnsignedLong, unlike #PyLong_AsLong, does not fall back to calling #PyNumber_Index
 * when its argument is not a `PyLongObject` instance. To match parsing signed integer types with
 * #PyLong_AsLong, this function performs the #PyNumber_Index fallback, if necessary, before
 * calling #PyLong_AsUnsignedLong. */
static ulong pyc_Long_AsUnsignedLong(PyObject *value)
{
  if (value == nullptr) {
    /* Let PyLong_AsUnsignedLong handle error raising. */
    return PyLong_AsUnsignedLong(value);
  }

  if (PyLong_Check(value)) {
    return PyLong_AsUnsignedLong(value);
  }

  /* Call `__index__` like PyLong_AsLong. */
  PyObject *value_converted = PyNumber_Index(value);
  if (value_converted == nullptr) {
    /* A `TypeError` will have been raised. */
    return ulong(-1);
  }
  ulong to_return = PyLong_AsUnsignedLong(value_converted);
  Py_DECREF(value_converted);
  return to_return;
}

int PyC_Long_AsBool(PyObject *value)
{
  const int test = PyLong_AsInt(value);
  if (UNLIKELY(test == -1 && PyErr_Occurred())) {
    return -1;
  }
  if (UNLIKELY(uint(test) > 1)) {
    PyErr_SetString(PyExc_TypeError, "Python number not a bool (0/1)");
    return -1;
  }
  return test;
}

int8_t PyC_Long_AsI8(PyObject *value)
{
  const int test = PyLong_AsInt(value);
  if (UNLIKELY(test == -1 && PyErr_Occurred())) {
    return -1;
  }
  if (UNLIKELY(test < INT8_MIN || test > INT8_MAX)) {
    PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C int8");
    return -1;
  }
  return int8_t(test);
}

int16_t PyC_Long_AsI16(PyObject *value)
{
  const int test = PyLong_AsInt(value);
  if (UNLIKELY(test == -1 && PyErr_Occurred())) {
    return -1;
  }
  if (UNLIKELY(test < INT16_MIN || test > INT16_MAX)) {
    PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C int16");
    return -1;
  }
  return int16_t(test);
}

/* Inlined in header:
 * PyC_Long_AsI32
 * PyC_Long_AsI64
 */

uint8_t PyC_Long_AsU8(PyObject *value)
{
  const ulong test = pyc_Long_AsUnsignedLong(value);
  if (UNLIKELY(test == ulong(-1) && PyErr_Occurred())) {
    return uint8_t(-1);
  }
  if (UNLIKELY(test > UINT8_MAX)) {
    PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C uint8");
    return uint8_t(-1);
  }
  return uint8_t(test);
}

uint16_t PyC_Long_AsU16(PyObject *value)
{
  const ulong test = pyc_Long_AsUnsignedLong(value);
  if (UNLIKELY(test == ulong(-1) && PyErr_Occurred())) {
    return uint16_t(-1);
  }
  if (UNLIKELY(test > UINT16_MAX)) {
    PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C uint16");
    return uint16_t(-1);
  }
  return uint16_t(test);
}

uint32_t PyC_Long_AsU32(PyObject *value)
{
  const ulong test = pyc_Long_AsUnsignedLong(value);
  if (UNLIKELY(test == ulong(-1) && PyErr_Occurred())) {
    return uint32_t(-1);
  }
  if (UNLIKELY(test > UINT32_MAX)) {
    PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C uint32");
    return uint32_t(-1);
  }
  return uint32_t(test);
}

uint64_t PyC_Long_AsU64(PyObject *value)
{
  if (value == nullptr) {
    /* Let PyLong_AsUnsignedLongLong handle error raising. */
    return uint64_t(PyLong_AsUnsignedLongLong(value));
  }

  if (PyLong_Check(value)) {
    return uint64_t(PyLong_AsUnsignedLongLong(value));
  }

  /* Call `__index__` like PyLong_AsLongLong. */
  PyObject *value_converted = PyNumber_Index(value);
  if (value_converted == nullptr) {
    /* A `TypeError` will have been raised. */
    return uint64_t(-1);
  }

  uint64_t to_return = uint64_t(PyLong_AsUnsignedLongLong(value_converted));
  Py_DECREF(value_converted);
  return to_return;
}

#if defined(__GNUC__) && !defined(__clang__)
#  pragma GCC diagnostic pop
#endif

/** \} */

/* -------------------------------------------------------------------- */
/** \name Py_buffer Utils
 * \{ */

char PyC_StructFmt_type_from_str(const char *typestr)
{
  switch (typestr[0]) {
    case '!':
    case '<':
    case '=':
    case '>':
    case '@':
      return typestr[1];
    default:
      return typestr[0];
  }
}

bool PyC_StructFmt_type_is_float_any(char format)
{
  switch (format) {
    case 'f':
    case 'd':
    case 'e':
      return true;
    default:
      return false;
  }
}

bool PyC_StructFmt_type_is_int_any(char format)
{
  switch (format) {
    case 'i':
    case 'I':
    case 'l':
    case 'L':
    case 'h':
    case 'H':
    case 'b':
    case 'B':
    case 'q':
    case 'Q':
    case 'n':
    case 'N':
    case 'P':
      return true;
    default:
      return false;
  }
}

bool PyC_StructFmt_type_is_byte(char format)
{
  switch (format) {
    case 'c':
    case 's':
    case 'p':
      return true;
    default:
      return false;
  }
}

bool PyC_StructFmt_type_is_bool(char format)
{
  switch (format) {
    case '?':
      return true;
    default:
      return false;
  }
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Dict Utilities
 * \{ */

bool PyC_Dict_CheckKeysAreStrings(PyObject *dict)
{
  PyObject *key;
  Py_ssize_t pos = 0;
  while (PyDict_Next(dict, &pos, &key, nullptr)) {
    if (!PyUnicode_Check(key)) {
      return false;
    }
  }
  return true;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Owned MemoryView
 *
 * A memory view where the memory is owned.
 * NOTE(@ideasman42): Python doesn't provide a convenient zero-copy way
 * for owned memory to be returned from a `memoryview`.
 *
 * This types only purpose is to expose a `memoryview` which our own custom free.
 * The type itself is not expected to be exposed to users.
 * So we could switch to an alternative API if it is ever supported.
 * \{ */

#ifndef MATH_STANDALONE

struct BPyMemoryViewOwned {
  PyObject_VAR_HEAD;
  /**
   * Snapshot of the caller's #Py_buffer.
   * - `info.shape` and `info.strides` point into `shape_and_stride`.
   * - `info.obj` is always null.
   */
  Py_buffer info;
  /**
   * Flexible array of `2 * info.ndim`.
   * Layout:
   * - `shape_and_stride[0..ndim]`: shape.
   * - `shape_and_stride[ndim..2 * ndim]`: strides.
   */
  Py_ssize_t shape_and_stride[];
};

static void bpy_memoryview_buffer_dealloc(BPyMemoryViewOwned *self)
{
  /* `info.buf` is asserted non-null at construction.
   * #MEM_delete_void also accepts null defensively. */
  MEM_delete_void(self->info.buf);
  Py_TYPE(self)->tp_free(self);
}

static int bpy_memoryview_buffer_getbuffer(BPyMemoryViewOwned *self, Py_buffer *view, int flags)
{
  if (self->info.readonly && (flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) {
    PyErr_SetString(PyExc_BufferError, "BPyMemoryViewOwned: object is not writable");
    return -1;
  }
  *view = self->info;
  view->obj = Py_NewRef(reinterpret_cast<PyObject *>(self));
  /* Honor what the caller actually requested. */
  if ((flags & PyBUF_FORMAT) != PyBUF_FORMAT) {
    view->format = nullptr;
  }
  if ((flags & PyBUF_ND) != PyBUF_ND) {
    view->shape = nullptr;
  }
  if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) {
    view->strides = nullptr;
  }
  return 0;
}

static PyBufferProcs bpy_memoryview_buffer_as_buffer = {
    /*bf_getbuffer*/ reinterpret_cast<getbufferproc>(bpy_memoryview_buffer_getbuffer),
    /*bf_releasebuffer*/ nullptr,
};

static PyTypeObject BPyMemoryViewOwned_Type = {
    PyVarObject_HEAD_INIT(nullptr, 0)
    /*tp_name*/ "BPyMemoryViewOwned",
    /*tp_basicsize*/ sizeof(BPyMemoryViewOwned),
    /*tp_itemsize*/ sizeof(Py_ssize_t),
    /*tp_dealloc*/ reinterpret_cast<destructor>(bpy_memoryview_buffer_dealloc),
    /*tp_vectorcall_offset*/ 0,
    /*tp_getattr*/ nullptr,
    /*tp_setattr*/ nullptr,
    /*tp_as_async*/ nullptr,
    /*tp_repr*/ nullptr,
    /*tp_as_number*/ nullptr,
    /*tp_as_sequence*/ nullptr,
    /*tp_as_mapping*/ nullptr,
    /*tp_hash*/ nullptr,
    /*tp_call*/ nullptr,
    /*tp_str*/ nullptr,
    /*tp_getattro*/ nullptr,
    /*tp_setattro*/ nullptr,
    /*tp_as_buffer*/ &bpy_memoryview_buffer_as_buffer,
    /* `Py_TPFLAGS_HAVE_GC` (and the matching `tp_traverse` / `tp_clear`) is not
     * needed: the wrapper holds no Python references - `info.obj` /
     * `info.suboffsets` / `info.internal` are explicitly nulled at construction
     * and the buffer is plain memory. */
    /*tp_flags*/ Py_TPFLAGS_DEFAULT,
    /*tp_doc*/ nullptr,
};

PyObject *PyC_MemoryView_FromBufferOwned(const Py_buffer *info)
{
  BLI_assert(info->buf != nullptr);
  BLI_assert(info->ndim >= 1);
  BLI_assert(info->itemsize > 0);
  BLI_assert(info->format != nullptr);
  BLI_assert(info->shape != nullptr);
  BLI_assert(info->strides != nullptr);

  if (PyType_Ready(&BPyMemoryViewOwned_Type) < 0) [[unlikely]] {
    MEM_delete_void(info->buf);
    return nullptr;
  }
  BPyMemoryViewOwned *wrapper = PyObject_NewVar(
      BPyMemoryViewOwned, &BPyMemoryViewOwned_Type, Py_ssize_t(2) * info->ndim);
  if (wrapper == nullptr) [[unlikely]] {
    MEM_delete_void(info->buf);
    return nullptr;
  }
  wrapper->info = *info;
  wrapper->info.obj = nullptr;
  wrapper->info.shape = wrapper->shape_and_stride;
  wrapper->info.strides = wrapper->shape_and_stride + info->ndim;
  wrapper->info.suboffsets = nullptr;
  wrapper->info.internal = nullptr;
  const size_t dim_size = size_t(info->ndim) * sizeof(Py_ssize_t);
  memcpy(wrapper->shape_and_stride, info->shape, dim_size);
  memcpy(wrapper->shape_and_stride + info->ndim, info->strides, dim_size);
  /* `wrapper` now owns `info->buf`, its `tp_dealloc` will free it. */

  PyObject *mv = PyMemoryView_FromObject(reinterpret_cast<PyObject *>(wrapper));
  Py_DECREF(wrapper);
  return mv;
}

#endif /* !MATH_STANDALONE */

/** \} */

}  // namespace blender
