/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
// This file is used for testing the FFI API.
// NOTE: TVM_FFI_DLL_EXPORT_INCLUDE_METADATA=1 is set via CMake target_compile_definitions
#include <dlpack/dlpack.h>
#include <tvm/ffi/any.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/dict.h>
#include <tvm/ffi/container/list.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/enum.h>
#include <tvm/ffi/extra/c_env_api.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/accessor.h>
#include <tvm/ffi/reflection/enum_def.h>
#include <tvm/ffi/reflection/registry.h>

#include <chrono>
#include <iostream>
#include <thread>
#include <utility>

namespace tvm {
namespace ffi {

// Step 1: Define the object class (stores the actual data)
class TestIntPairObj : public tvm::ffi::Object {
 public:
  int64_t a;
  int64_t b;

  TestIntPairObj() = default;
  TestIntPairObj(int64_t a, int64_t b) : a(a), b(b) {}

  // Required: declare type information
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestIntPair", TestIntPairObj, tvm::ffi::Object);
};

// Step 2: Define the reference wrapper (user-facing interface)
class TestIntPair : public tvm::ffi::ObjectRef {
 public:
  // Constructor
  explicit TestIntPair(int64_t a, int64_t b) {
    data_ = tvm::ffi::make_object<TestIntPairObj>(a, b);
  }

  int64_t Sum() const { return get()->a + get()->b; }

  // Required: define object reference methods
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestIntPair, tvm::ffi::ObjectRef, TestIntPairObj);
};

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  refl::ObjectDef<TestIntPairObj>()
      .def(refl::init<int64_t, int64_t>())
      .def_ro("a", &TestIntPairObj::a, "Field `a`")
      .def_ro("b", &TestIntPairObj::b, "Field `b`")
      .def("sum", &TestIntPair::Sum, "Method to compute sum of a and b");
  refl::TypeAttrDef<TestIntPairObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<TestIntPair>);
}

// C++-backed enum used by the Python ``Enum`` tests to exercise both
// ``EnumDef``-registered entries and the Python ``ClassVar``-based binding.
class TestEnumVariantObj : public tvm::ffi::EnumObj {
 public:
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestEnumVariant", TestEnumVariantObj,
                                    tvm::ffi::EnumObj);
};

class TestEnumVariant : public tvm::ffi::Enum {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestEnumVariant, tvm::ffi::Enum, TestEnumVariantObj);
};

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  // ObjectDef registers the type on destruction, so the temporary is intentional;
  // silence clang-tidy's bugprone-unused-raii since the RAII finalisation is the point.
  refl::ObjectDef<TestEnumVariantObj>(refl::init(false));  // NOLINT(bugprone-unused-raii)
  refl::TypeAttrDef<TestEnumVariantObj>().def(
      refl::type_attr::kConvert, &refl::details::FFIConvertFromAnyViewToObjectRef<TestEnumVariant>);
  refl::EnumDef<TestEnumVariantObj>("Alpha").set_attr("code", int64_t{10});
  refl::EnumDef<TestEnumVariantObj>("Beta").set_attr("code", int64_t{20});
}

class TestObjectBase : public Object {
 public:
  int64_t v_i64;
  double v_f64;
  String v_str;

  int64_t AddI64(int64_t other) const { return v_i64 + other; }

  // declare as one slot, with float as overflow
  static constexpr bool _type_mutable = true;
  static constexpr uint32_t _type_child_slots = 1;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestObjectBase", TestObjectBase, Object);
};

class TestObjectDerived : public TestObjectBase {
 public:
  Map<Any, Any> v_map;
  Array<Any> v_array;

  // declare as one slot, with float as overflow
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestObjectDerived", TestObjectDerived, TestObjectBase);
};

class TestCxxClassBase : public Object {
 public:
  int64_t v_i64 = 0;
  int32_t v_i32 = 0;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxClassBase", TestCxxClassBase, Object);
};

class TestCxxClassDerived : public TestCxxClassBase {
 public:
  double v_f64 = 0.0;
  float v_f32 = 0.0f;

  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxClassDerived", TestCxxClassDerived, TestCxxClassBase);
};

class TestCxxClassDerivedDerived : public TestCxxClassDerived {
 public:
  String v_str;
  bool v_bool = false;

  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxClassDerivedDerived", TestCxxClassDerivedDerived,
                              TestCxxClassDerived);
};

class TestCxxInitSubsetObj : public Object {
 public:
  int64_t required_field = 0;
  int64_t optional_field = -1;
  String note;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxInitSubset", TestCxxInitSubsetObj, Object);
};

class TestCxxKwOnly : public Object {
 public:
  int64_t x = 0;
  int64_t y = 0;
  int64_t z = 0;
  int64_t w = 0;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxKwOnly", TestCxxKwOnly, Object);
};

// Test: auto-generated __ffi_init__ with init(false) and KwOnly(true) per-field.
// No explicit refl::init<>() — the auto-init is generated in ObjectDef's destructor.
class TestCxxAutoInitObj : public Object {
 public:
  int64_t a;  ///< init=True, positional (default)
  int64_t b;  ///< init=False, has default
  int64_t c;  ///< init=True, kw_only
  int64_t d;  ///< init=True, positional, has default

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxAutoInit", TestCxxAutoInitObj, Object);
};

// Test: auto-generated init with all fields init=True (no Init/KwOnly traits).
class TestCxxAutoInitSimpleObj : public Object {
 public:
  int64_t x;
  int64_t y;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxAutoInitSimple", TestCxxAutoInitSimpleObj, Object);
};

// Test: auto-generated init with all fields excluded from init.
class TestCxxAutoInitAllInitOffObj : public Object {
 public:
  int64_t x = 7;     ///< init=False, has reflection default
  int64_t y = 0;     ///< init=False, has reflection default
  int64_t z = 1234;  ///< init=False, no reflection default (creator default is kept)

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxAutoInitAllInitOff", TestCxxAutoInitAllInitOffObj,
                              Object);
};

// Test: mixed positional + kw_only + defaults + init=False field.
class TestCxxAutoInitKwOnlyDefaultsObj : public Object {
 public:
  int64_t p_required;  ///< init=True, positional, required
  int64_t p_default;   ///< init=True, positional, default=11
  int64_t k_required;  ///< init=True, kw_only, required
  int64_t k_default;   ///< init=True, kw_only, default=22
  int64_t hidden;      ///< init=False, default=33

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxAutoInitKwOnlyDefaults",
                              TestCxxAutoInitKwOnlyDefaultsObj, Object);
};

// Test: inheritance + auto-generated init.
class TestCxxAutoInitParentObj : public Object {
 public:
  int64_t parent_required;
  int64_t parent_default;

  static constexpr bool _type_mutable = true;
  static constexpr uint32_t _type_child_slots = 1;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxAutoInitParent", TestCxxAutoInitParentObj, Object);
};

class TestCxxAutoInitChildObj : public TestCxxAutoInitParentObj {
 public:
  int64_t child_required;
  int64_t child_kw_only;

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestCxxAutoInitChild", TestCxxAutoInitChildObj,
                                    TestCxxAutoInitParentObj);
};

// Test: init(false) at class level suppresses auto-generated __ffi_init__.
class TestCxxNoAutoInitObj : public Object {
 public:
  int64_t x;
  int64_t y;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestCxxNoAutoInit", TestCxxNoAutoInitObj, Object);
};

class TestDeepCopyEdgesObj : public Object {
 public:
  Any v_any;
  ObjectRef v_obj;

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestDeepCopyEdges", TestDeepCopyEdgesObj, Object);
};

class TestNonCopyable : public Object {
 public:
  int64_t value;

  explicit TestNonCopyable(int64_t value) : value(value) {}
  TestNonCopyable(const TestNonCopyable&) = delete;
  TestNonCopyable& operator=(const TestNonCopyable&) = delete;

  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestNonCopyable", TestNonCopyable, Object);
};

class TestUnregisteredBaseObject : public Object {
 public:
  int64_t v1;
  explicit TestUnregisteredBaseObject(int64_t v1) : v1(v1) {}
  int64_t GetV1PlusOne() const { return v1 + 1; }
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestUnregisteredBaseObject", TestUnregisteredBaseObject,
                              Object);
};

class TestUnregisteredObject : public TestUnregisteredBaseObject {
 public:
  int64_t v2;
  explicit TestUnregisteredObject(int64_t v1, int64_t v2)
      : TestUnregisteredBaseObject(v1), v2(v2) {}
  int64_t GetV2PlusTwo() const { return v2 + 2; }
  TVM_FFI_DECLARE_OBJECT_INFO("testing.TestUnregisteredObject", TestUnregisteredObject,
                              TestUnregisteredBaseObject);
};

class TestCompareObj : public Object {
 public:
  int64_t key;
  String name;
  int64_t ignored_field;

  TestCompareObj() = default;
  TestCompareObj(int64_t key, String name, int64_t ignored_field)
      : key(key), name(std::move(name)), ignored_field(ignored_field) {}

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestCompare", TestCompareObj, Object);
};

class TestCompare : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestCompare, ObjectRef, TestCompareObj);
};

class TestHashObj : public Object {
 public:
  int64_t key;
  String name;
  int64_t hash_ignored;

  TestHashObj() = default;
  TestHashObj(int64_t key, String name, int64_t hash_ignored)
      : key(key), name(std::move(name)), hash_ignored(hash_ignored) {}

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestHash", TestHashObj, Object);
};

class TestHash : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestHash, ObjectRef, TestHashObj);
};

class TestCustomHashObj : public Object {
 public:
  int64_t key;
  String label;

  TestCustomHashObj() = default;
  TestCustomHashObj(int64_t key, String label) : key(key), label(std::move(label)) {}

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestCustomHash", TestCustomHashObj, Object);
};

class TestCustomHash : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestCustomHash, ObjectRef, TestCustomHashObj);
};

class TestCustomCompareObj : public Object {
 public:
  int64_t key;
  String label;

  TestCustomCompareObj() = default;
  TestCustomCompareObj(int64_t key, String label) : key(key), label(std::move(label)) {}

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestCustomCompare", TestCustomCompareObj, Object);
};

class TestCustomCompare : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestCustomCompare, ObjectRef, TestCustomCompareObj);
};

// Test object with __ffi_eq__ but deliberately no __ffi_hash__.
// Used to verify the RecursiveHash guard that rejects eq-without-hash types.
class TestEqWithoutHashObj : public Object {
 public:
  int64_t key;
  String label;

  TestEqWithoutHashObj() = default;
  TestEqWithoutHashObj(int64_t key, String label) : key(key), label(std::move(label)) {}

  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestEqWithoutHash", TestEqWithoutHashObj, Object);
};

class TestEqWithoutHash : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TestEqWithoutHash, ObjectRef, TestEqWithoutHashObj);
};

// NOLINTNEXTLINE(performance-unnecessary-value-param)
TVM_FFI_NO_INLINE void TestRaiseError(String kind, String msg) {
  // keep name and no liner for testing backtrace
  throw ffi::Error(kind, msg, TVMFFIBacktrace(__FILE__, __LINE__, TVM_FFI_FUNC_SIG, 0));
}

TVM_FFI_NO_INLINE void TestApply(PackedArgs args, Any* ret) {
  // keep name and no liner for testing backtrace
  auto f = args[0].cast<Function>();
  f.CallPacked(args.Slice(1), ret);
}

// NOLINTNEXTLINE(bugprone-reserved-identifier)
int __add_one_c_symbol(void*, const TVMFFIAny* args, int32_t num_args, TVMFFIAny* ret) {
  TVM_FFI_SAFE_CALL_BEGIN();
  int x = reinterpret_cast<const AnyView*>(args)[0].cast<int>();
  reinterpret_cast<Any*>(ret)[0] = x + 1;
  TVM_FFI_SAFE_CALL_END();
}

void _mlir_add_one_c_symbol(void** packed_args) {
  void* handle = *reinterpret_cast<void**>(packed_args[0]);
  const TVMFFIAny* args = *reinterpret_cast<const TVMFFIAny**>(packed_args[1]);
  int32_t num_args = *reinterpret_cast<int32_t*>(packed_args[2]);
  TVMFFIAny* rv = *reinterpret_cast<TVMFFIAny**>(packed_args[3]);
  int* ret_code = reinterpret_cast<int*>(packed_args[4]);
  *ret_code = __add_one_c_symbol(handle, args, num_args, rv);
}

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;

  refl::ObjectDef<TestObjectBase>()
      .def_rw("v_i64", &TestObjectBase::v_i64, refl::default_value(10), "i64 field")
      .def_ro("v_f64", &TestObjectBase::v_f64, refl::default_value(10.0))
      .def_rw("v_str", &TestObjectBase::v_str, refl::default_value("hello"))
      .def("add_i64", &TestObjectBase::AddI64, "add_i64 method");

  refl::ObjectDef<TestObjectDerived>()
      .def_rw("v_map", &TestObjectDerived::v_map)
      .def_rw("v_array", &TestObjectDerived::v_array);

  refl::ObjectDef<TestCxxClassBase>()
      .def_rw("v_i64", &TestCxxClassBase::v_i64, refl::repr(false))
      .def_rw("v_i32", &TestCxxClassBase::v_i32, refl::repr(false));

  refl::ObjectDef<TestCxxClassDerived>()
      .def_rw("v_f64", &TestCxxClassDerived::v_f64)
      .def_rw("v_f32", &TestCxxClassDerived::v_f32, refl::default_value(float{8.0f}));

  refl::ObjectDef<TestCxxClassDerivedDerived>()
      .def_rw("v_str", &TestCxxClassDerivedDerived::v_str, refl::default_value(String("default")))
      .def_rw("v_bool", &TestCxxClassDerivedDerived::v_bool);

  refl::ObjectDef<TestCxxInitSubsetObj>()
      .def_rw("required_field", &TestCxxInitSubsetObj::required_field)
      .def_rw("optional_field", &TestCxxInitSubsetObj::optional_field, refl::init(false),
              refl::default_value(int64_t{-1}))
      .def_rw("note", &TestCxxInitSubsetObj::note, refl::init(false),
              refl::default_value(String("default")));

  refl::ObjectDef<TestCxxKwOnly>()
      .def_rw("x", &TestCxxKwOnly::x, refl::kw_only(true))
      .def_rw("y", &TestCxxKwOnly::y, refl::kw_only(true))
      .def_rw("z", &TestCxxKwOnly::z, refl::kw_only(true))
      .def_rw("w", &TestCxxKwOnly::w, refl::kw_only(true), refl::default_value(int64_t{100}));

  // No refl::init<>() — auto-generates __ffi_init__ in ObjectDef destructor.
  refl::ObjectDef<TestCxxAutoInitObj>()
      .def_rw("a", &TestCxxAutoInitObj::a)
      .def_rw("b", &TestCxxAutoInitObj::b, refl::init(false), refl::default_value(int64_t{42}))
      .def_rw("c", &TestCxxAutoInitObj::c, refl::kw_only(true))
      .def_rw("d", &TestCxxAutoInitObj::d, refl::default_value(int64_t{99}));

  refl::ObjectDef<TestCxxAutoInitSimpleObj>()
      .def_rw("x", &TestCxxAutoInitSimpleObj::x)
      .def_rw("y", &TestCxxAutoInitSimpleObj::y);

  refl::ObjectDef<TestCxxAutoInitAllInitOffObj>()
      .def_rw("x", &TestCxxAutoInitAllInitOffObj::x, refl::init(false),
              refl::default_value(int64_t{7}))
      .def_rw("y", &TestCxxAutoInitAllInitOffObj::y, refl::init(false),
              refl::default_value(int64_t{9}))
      .def_rw("z", &TestCxxAutoInitAllInitOffObj::z, refl::init(false));

  refl::ObjectDef<TestCxxAutoInitKwOnlyDefaultsObj>()
      .def_rw("p_required", &TestCxxAutoInitKwOnlyDefaultsObj::p_required)
      .def_rw("p_default", &TestCxxAutoInitKwOnlyDefaultsObj::p_default,
              refl::default_value(int64_t{11}))
      .def_rw("k_required", &TestCxxAutoInitKwOnlyDefaultsObj::k_required, refl::kw_only(true))
      .def_rw("k_default", &TestCxxAutoInitKwOnlyDefaultsObj::k_default, refl::kw_only(true),
              refl::default_value(int64_t{22}))
      .def_rw("hidden", &TestCxxAutoInitKwOnlyDefaultsObj::hidden, refl::init(false),
              refl::default_value(int64_t{33}));

  refl::ObjectDef<TestCxxAutoInitParentObj>()
      .def_rw("parent_required", &TestCxxAutoInitParentObj::parent_required)
      .def_rw("parent_default", &TestCxxAutoInitParentObj::parent_default,
              refl::default_value(int64_t{5}));

  refl::ObjectDef<TestCxxAutoInitChildObj>()
      .def_rw("child_required", &TestCxxAutoInitChildObj::child_required)
      .def_rw("child_kw_only", &TestCxxAutoInitChildObj::child_kw_only, refl::kw_only(true));

  // init(false) at class level: has fields, has creator, but no __ffi_init__.
  refl::ObjectDef<TestCxxNoAutoInitObj>(refl::init(false))
      .def_rw("x", &TestCxxNoAutoInitObj::x)
      .def_rw("y", &TestCxxNoAutoInitObj::y);

  refl::ObjectDef<TestDeepCopyEdgesObj>()
      .def_rw("v_any", &TestDeepCopyEdgesObj::v_any)
      .def_rw("v_obj", &TestDeepCopyEdgesObj::v_obj);

  refl::ObjectDef<TestNonCopyable>()
      .def(refl::init<int64_t>())
      .def_ro("value", &TestNonCopyable::value);

  refl::ObjectDef<TestUnregisteredBaseObject>()
      .def(refl::init<int64_t>(), "Constructor of TestUnregisteredBaseObject")
      .def_ro("v1", &TestUnregisteredBaseObject::v1)
      .def("get_v1_plus_one", &TestUnregisteredBaseObject::GetV1PlusOne,
           "Get (v1 + 1) from TestUnregisteredBaseObject");

  refl::ObjectDef<TestUnregisteredObject>()
      .def(refl::init<int64_t, int64_t>(), "Constructor of TestUnregisteredObject")
      .def_ro("v2", &TestUnregisteredObject::v2)
      .def("get_v2_plus_two", &TestUnregisteredObject::GetV2PlusTwo,
           "Get (v2 + 2) from TestUnregisteredObject");

  refl::ObjectDef<TestCompareObj>()
      .def(refl::init<int64_t, String, int64_t>())
      .def_ro("key", &TestCompareObj::key)
      .def_ro("name", &TestCompareObj::name)
      .def_ro("ignored_field", &TestCompareObj::ignored_field, refl::compare(false));

  refl::ObjectDef<TestHashObj>()
      .def(refl::init<int64_t, String, int64_t>())
      .def_ro("key", &TestHashObj::key)
      .def_ro("name", &TestHashObj::name)
      .def_ro("hash_ignored", &TestHashObj::hash_ignored, refl::hash(false));

  refl::ObjectDef<TestCustomHashObj>()
      .def(refl::init<int64_t, String>())
      .def_ro("key", &TestCustomHashObj::key)
      .def_ro("label", &TestCustomHashObj::label);

  refl::ObjectDef<TestCustomCompareObj>()
      .def(refl::init<int64_t, String>())
      .def_ro("key", &TestCustomCompareObj::key)
      .def_ro("label", &TestCustomCompareObj::label);

  refl::ObjectDef<TestEqWithoutHashObj>()
      .def(refl::init<int64_t, String>())
      .def_ro("key", &TestEqWithoutHashObj::key)
      .def_ro("label", &TestEqWithoutHashObj::label);

  refl::GlobalDef()
      .def("testing.test_raise_error", TestRaiseError)
      .def("testing.add_one", [](int x) { return x + 1; })
      .def_packed("testing.nop", [](PackedArgs args, Any* ret) {})
      .def_packed("testing.echo", [](PackedArgs args, Any* ret) { *ret = args[0]; })
      .def_packed("testing.apply", TestApply)
      .def("testing.run_check_signal",
           [](int nsec) {
             for (int i = 0; i < nsec; ++i) {
               if (TVMFFIEnvCheckSignals() != 0) {
                 throw ffi::EnvErrorAlreadySet();
               }
               std::this_thread::sleep_for(std::chrono::seconds(1));
             }
             std::cout << "Function finished without catching signal" << std::endl;
           })
      .def("testing.object_use_count", [](const Object* obj) { return obj->use_count(); })
      .def("testing.make_unregistered_object",
           []() { return ObjectRef(make_object<TestUnregisteredObject>(41, 42)); })
      .def("testing.get_add_one_c_symbol",
           []() {
             TVMFFISafeCallType symbol = __add_one_c_symbol;
             // NOLINTNEXTLINE(bugprone-casting-through-void)
             return reinterpret_cast<int64_t>(reinterpret_cast<void*>(symbol));
           })
      .def("testing.get_mlir_add_one_c_symbol",
           []() {
             // NOLINTNEXTLINE(bugprone-casting-through-void)
             return reinterpret_cast<int64_t>(reinterpret_cast<void*>(_mlir_add_one_c_symbol));
           })
      .def("testing.optional_tensor_view_has_value",
           [](const Optional<TensorView>& t) { return t.has_value(); })
      .def("testing.enum_variant_get",
           [](const String& name) -> Enum { return EnumObj::Get<TestEnumVariantObj>(name); })
      .def_method("testing.TestIntPairSum", &TestIntPair::Sum, "Get sum of the pair")
      // Container-with-tensor test helpers for DLPack container conversion
      // NOLINTBEGIN(performance-unnecessary-value-param)
      .def("testing.make_array_with_tensor", [](Tensor t) -> Array<Any> { return {std::move(t)}; })
      .def("testing.make_array_with_mixed",
           [](Tensor t, int64_t x) -> Array<Any> { return {std::move(t), x, String("hello")}; })
      .def("testing.make_nested_array_with_tensor",
           [](const Tensor& t) -> Array<Any> {
             Array<Any> inner{t, 42};
             return {std::move(inner), t};
           })
      .def("testing.make_list_with_tensor",
           [](Tensor t, int64_t x) -> List<Any> {
             List<Any> result;
             result.push_back(std::move(t));
             result.push_back(x);
             return result;
           })
      .def("testing.make_map_with_tensor",
           [](Tensor t) -> Map<String, Any> {
             Map<String, Any> result;
             result.Set("tensor", std::move(t));
             result.Set("value", 42);
             result.Set("name", String("test"));
             return result;
           })
      .def("testing.make_dict_with_tensor",
           [](Tensor t) -> Dict<String, Any> {
             Dict<String, Any> result;
             result.Set("tensor", std::move(t));
             result.Set("value", 42);
             return result;
           })
      .def("testing.make_empty_array_with_tensor_input",
           [](const Tensor& t) -> Array<Any> { return Array<Any>(); })
      .def("testing.make_nested_map_with_tensor",
           [](const Tensor& t1, Tensor t2) -> Map<String, Any> {
             Array<Any> arr{t1, std::move(t2)};
             Map<String, Any> inner;
             inner.Set("t", t1);
             Map<String, Any> result;
             result.Set("array", std::move(arr));
             result.Set("map", std::move(inner));
             result.Set("scalar", 99);
             return result;
           });
  // NOLINTEND(performance-unnecessary-value-param)
}

}  // namespace ffi
}  // namespace tvm

// -----------------------------------------------------------------------------
// Additional comprehensive schema coverage
// -----------------------------------------------------------------------------
namespace tvm {
namespace ffi {

// -----------------------------------------------------------------------------
// Implementation functions for schema testing
// -----------------------------------------------------------------------------
namespace schema_test_impl {

// Simple types
int64_t schema_id_int(int64_t x) { return x; }
double schema_id_float(double x) { return x; }
bool schema_id_bool(bool x) { return x; }
DLDevice schema_id_device(DLDevice d) { return d; }
DLDataType schema_id_dtype(DLDataType dt) { return dt; }
String schema_id_string(String s) { return s; }
Bytes schema_id_bytes(Bytes b) { return b; }
Function schema_id_func(Function f) { return f; }
TypedFunction<void(int64_t, float, Function)> schema_id_func_typed(
    TypedFunction<void(int64_t, float, Function)> f) {
  return f;
}
Any schema_id_any(Any a) { return a; }
ObjectRef schema_id_object(ObjectRef o) { return o; }
DLTensor* schema_id_dltensor(DLTensor* t) { return t; }
Tensor schema_id_tensor(Tensor t) { return t; }
void schema_tensor_view_input(TensorView t) {}

// Optional types
Optional<int64_t> schema_id_opt_int(Optional<int64_t> o) { return o; }
Optional<String> schema_id_opt_str(Optional<String> o) { return o; }
Optional<ObjectRef> schema_id_opt_obj(Optional<ObjectRef> o) { return o; }

// Array types
Array<int64_t> schema_id_arr_int(Array<int64_t> arr) { return arr; }
Array<String> schema_id_arr_str(Array<String> arr) { return arr; }
Array<ObjectRef> schema_id_arr_obj(Array<ObjectRef> arr) { return arr; }
const ArrayObj* schema_id_arr(const ArrayObj* arr) { return arr; }

// Map types
Map<String, int64_t> schema_id_map_str_int(Map<String, int64_t> m) { return m; }
Map<String, String> schema_id_map_str_str(Map<String, String> m) { return m; }
Map<String, ObjectRef> schema_id_map_str_obj(Map<String, ObjectRef> m) { return m; }
const MapObj* schema_id_map(const MapObj* m) { return m; }

// Variant types
Variant<int64_t, String> schema_id_variant_int_str(Variant<int64_t, String> v) { return v; }
Variant<int64_t, String, Array<int64_t>> schema_variant_mix(
    Variant<int64_t, String, Array<int64_t>> v) {
  return v;
}

// List types
List<int64_t> schema_id_list_int(List<int64_t> lst) { return lst; }
List<String> schema_id_list_str(List<String> lst) { return lst; }
List<ObjectRef> schema_id_list_obj(List<ObjectRef> lst) { return lst; }

// Dict types
Dict<String, int64_t> schema_id_dict_str_int(Dict<String, int64_t> d) { return d; }
Dict<String, String> schema_id_dict_str_str(Dict<String, String> d) { return d; }

// Complex nested types
Map<String, Array<int64_t>> schema_arr_map_opt(const Array<Optional<int64_t>>& arr,
                                               Map<String, Array<int64_t>> mp,
                                               const Optional<String>& os) {
  // no-op combine
  if (os.has_value()) {
    Array<int64_t> extra;
    for (const auto& i : arr) {
      if (i.has_value()) extra.push_back(i.value());
    }
    mp.Set(os.value(), extra);
  }
  return mp;
}

// Edge cases
int64_t schema_no_args() { return 1; }
void schema_no_return(int64_t x) {}
void schema_no_args_no_return() {}

// Member function pattern
int64_t test_int_pair_sum_wrapper(const TestIntPair& target) { return target.Sum(); }

// Documentation export
int64_t test_add_with_docstring(int64_t a, int64_t b) { return a + b; }

}  // namespace schema_test_impl

// A class with a wide variety of field types and method signatures
class SchemaAllTypesObj : public Object {
 public:
  // POD and builtin types
  bool v_bool{true};
  int64_t v_int;
  double v_float;
  DLDevice v_device;
  DLDataType v_dtype;

  // Atomic object types
  String v_string;
  Bytes v_bytes;

  // Containers and combinations
  Optional<int64_t> v_opt_int;
  Optional<String> v_opt_str;
  Array<int64_t> v_arr_int;
  Array<String> v_arr_str;
  Map<String, int64_t> v_map_str_int;
  Map<String, Array<int64_t>> v_map_str_arr_int;
  Variant<String, Array<int64_t>, Map<String, int64_t>> v_variant;
  Optional<Array<Variant<int64_t, String>>> v_opt_arr_variant;

  // Constructor used by refl::init in make_with
  SchemaAllTypesObj(int64_t vi, double vf, String s)  // NOLINT(*): explicit not necessary here
      : v_int(vi),
        v_float(vf),
        v_device(TVMFFIDLDeviceFromIntPair(kDLCPU, 0)),
        v_dtype(StringToDLDataType("float32")),
        v_string(std::move(s)),
        v_variant(String("v")) {}

  // Some methods to exercise RegisterMethod
  int64_t AddInt(int64_t x) const { return v_int + x; }
  Array<int64_t> AppendInt(Array<int64_t> xs, int64_t y) const {
    xs.push_back(y);
    return xs;
  }
  Optional<String> MaybeConcat(Optional<String> a, Optional<String> b) const {
    if (a.has_value() && b.has_value()) return String(a.value() + b.value());
    if (a.has_value()) return a;
    if (b.has_value()) return b;
    return Optional<String>(std::nullopt);
  }
  Map<String, Array<int64_t>> MergeMap(Map<String, Array<int64_t>> lhs,
                                       // NOLINTNEXTLINE(performance-unnecessary-value-param)
                                       Map<String, Array<int64_t>> rhs) const {
    for (const auto& kv : rhs) {
      if (!lhs.count(kv.first)) {
        lhs.Set(kv.first, kv.second);
      } else {
        Array<int64_t> arr = lhs[kv.first];
        for (const auto& v : kv.second) arr.push_back(v);
        lhs.Set(kv.first, arr);
      }
    }
    return lhs;
  }

  static constexpr bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("testing.SchemaAllTypes", SchemaAllTypesObj, Object);
};

class SchemaAllTypes : public ObjectRef {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SchemaAllTypes, ObjectRef, SchemaAllTypesObj);
};

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  // Register fields of various types (RegisterField usage)
  refl::ObjectDef<SchemaAllTypesObj>()
      .def_rw("v_bool", &SchemaAllTypesObj::v_bool, "bool field",
              refl::Metadata{{"bool_attr", true},  //
                             {"int_attr", 1},      //
                             {"str_attr", "hello"}})
      .def_rw("v_int", &SchemaAllTypesObj::v_int, refl::default_value(0), "int field")
      .def_rw("v_float", &SchemaAllTypesObj::v_float, refl::default_value(0.0), "float field")
      .def_rw("v_device", &SchemaAllTypesObj::v_device, "device field")
      .def_rw("v_dtype", &SchemaAllTypesObj::v_dtype, "dtype field")
      .def_rw("v_string", &SchemaAllTypesObj::v_string, refl::default_value("s"), "string field")
      .def_rw("v_bytes", &SchemaAllTypesObj::v_bytes, "bytes field")
      .def_rw("v_opt_int", &SchemaAllTypesObj::v_opt_int, "optional int")
      .def_rw("v_opt_str", &SchemaAllTypesObj::v_opt_str, "optional str")
      .def_rw("v_arr_int", &SchemaAllTypesObj::v_arr_int, "array<int>")
      .def_rw("v_arr_str", &SchemaAllTypesObj::v_arr_str, "array<str>")
      .def_rw("v_map_str_int", &SchemaAllTypesObj::v_map_str_int, "map<str,int>")
      .def_rw("v_map_str_arr_int", &SchemaAllTypesObj::v_map_str_arr_int, "map<str,array<int>>")
      .def_rw("v_variant", &SchemaAllTypesObj::v_variant, "variant<str,array<int>,map<str,int>>")
      .def_rw("v_opt_arr_variant", &SchemaAllTypesObj::v_opt_arr_variant,
              "optional<array<variant<int,str>>>")
      // Register methods (RegisterMethod usage)
      .def("add_int", &SchemaAllTypesObj::AddInt, "add int method",
           refl::Metadata{{"bool_attr", true},  //
                          {"int_attr", 1},      //
                          {"str_attr", "hello"}})
      .def("append_int", &SchemaAllTypesObj::AppendInt, "append int to array")
      .def("maybe_concat", &SchemaAllTypesObj::MaybeConcat, "optional concat")
      .def("merge_map", &SchemaAllTypesObj::MergeMap, "merge maps")
      // Register static creator (also a static method)
      .def_static(
          "make_with",
          [](int64_t vi, double vf, String s) {
            return SchemaAllTypes(make_object<SchemaAllTypesObj>(vi, vf, std::move(s)));
          },
          "Constructor with subset of fields");

  // Global typed functions to exercise RegisterFunc with various schemas
  refl::GlobalDef()
      .def("testing.schema_id_int", schema_test_impl::schema_id_int,
           refl::Metadata{{"bool_attr", true},  //
                          {"int_attr", 1},      //
                          {"str_attr", "hello"}})
      .def("testing.schema_id_float", schema_test_impl::schema_id_float)
      .def("testing.schema_id_bool", schema_test_impl::schema_id_bool)
      .def("testing.schema_id_device", schema_test_impl::schema_id_device)
      .def("testing.schema_id_dtype", schema_test_impl::schema_id_dtype)
      .def("testing.schema_id_string", schema_test_impl::schema_id_string)
      .def("testing.schema_id_bytes", schema_test_impl::schema_id_bytes)
      .def("testing.schema_id_func", schema_test_impl::schema_id_func)
      .def("testing.schema_id_func_typed", schema_test_impl::schema_id_func_typed)
      .def("testing.schema_id_any", schema_test_impl::schema_id_any)
      .def("testing.schema_id_object", schema_test_impl::schema_id_object)
      .def("testing.schema_id_dltensor", schema_test_impl::schema_id_dltensor)
      .def("testing.schema_id_tensor", schema_test_impl::schema_id_tensor)
      .def("testing.schema_tensor_view_input", schema_test_impl::schema_tensor_view_input)
      .def("testing.schema_id_opt_int", schema_test_impl::schema_id_opt_int)
      .def("testing.schema_id_opt_str", schema_test_impl::schema_id_opt_str)
      .def("testing.schema_id_opt_obj", schema_test_impl::schema_id_opt_obj)
      .def("testing.schema_id_arr_int", schema_test_impl::schema_id_arr_int)
      .def("testing.schema_id_arr_str", schema_test_impl::schema_id_arr_str)
      .def("testing.schema_id_arr_obj", schema_test_impl::schema_id_arr_obj)
      .def("testing.schema_id_arr", schema_test_impl::schema_id_arr)
      .def("testing.schema_id_list_int", schema_test_impl::schema_id_list_int)
      .def("testing.schema_id_list_str", schema_test_impl::schema_id_list_str)
      .def("testing.schema_id_list_obj", schema_test_impl::schema_id_list_obj)
      .def("testing.schema_id_map_str_int", schema_test_impl::schema_id_map_str_int)
      .def("testing.schema_id_map_str_str", schema_test_impl::schema_id_map_str_str)
      .def("testing.schema_id_map_str_obj", schema_test_impl::schema_id_map_str_obj)
      .def("testing.schema_id_map", schema_test_impl::schema_id_map)
      .def("testing.schema_id_dict_str_int", schema_test_impl::schema_id_dict_str_int)
      .def("testing.schema_id_dict_str_str", schema_test_impl::schema_id_dict_str_str)
      .def("testing.schema_id_variant_int_str", schema_test_impl::schema_id_variant_int_str)
      .def_packed("testing.schema_packed", [](PackedArgs args, Any* ret) {})
      .def("testing.schema_arr_map_opt", schema_test_impl::schema_arr_map_opt)
      .def("testing.schema_variant_mix", schema_test_impl::schema_variant_mix,
           "variant passthrough")
      .def("testing.schema_no_args", schema_test_impl::schema_no_args)
      .def("testing.schema_no_return", schema_test_impl::schema_no_return)
      .def("testing.schema_no_args_no_return", schema_test_impl::schema_no_args_no_return);
  TVMFFIEnvModRegisterSystemLibSymbol("__tvm_ffi_testing.add_one",
                                      reinterpret_cast<void*>(__add_one_c_symbol));
}

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  // Register __ffi_hash__ for TestCustomHash: only hashes `key`, ignores `label`.
  refl::TypeAttrDef<TestCustomHashObj>().def(
      refl::type_attr::kHash, [](const Object* self, const Function& fn_hash) -> int64_t {
        auto* obj = static_cast<const TestCustomHashObj*>(self);
        return fn_hash(AnyView(obj->key)).cast<int64_t>();
      });
  // Register __ffi_hash__ for TestCustomCompare: only hashes `key`, consistent with eq/compare.
  refl::TypeAttrDef<TestCustomCompareObj>().def(
      refl::type_attr::kHash, [](const Object* self, const Function& fn_hash) -> int64_t {
        auto* obj = static_cast<const TestCustomCompareObj*>(self);
        return fn_hash(AnyView(obj->key)).cast<int64_t>();
      });
  // Register __ffi_eq__ for TestCustomCompare: compares only `key`.
  refl::TypeAttrDef<TestCustomCompareObj>().def(
      refl::type_attr::kEq,
      [](const Object* lhs, const Object* rhs, const Function& fn_eq) -> bool {
        auto* a = static_cast<const TestCustomCompareObj*>(lhs);
        auto* b = static_cast<const TestCustomCompareObj*>(rhs);
        return fn_eq(AnyView(a->key), AnyView(b->key)).cast<bool>();
      });
  // Register __ffi_compare__ for TestCustomCompare: three-way ordering on `key`.
  refl::TypeAttrDef<TestCustomCompareObj>().def(
      refl::type_attr::kCompare,
      [](const Object* lhs, const Object* rhs, const Function& fn_cmp) -> int32_t {
        auto* a = static_cast<const TestCustomCompareObj*>(lhs);
        auto* b = static_cast<const TestCustomCompareObj*>(rhs);
        return fn_cmp(AnyView(a->key), AnyView(b->key)).cast<int32_t>();
      });
  // Register __ffi_eq__ for TestEqWithoutHash: deliberately no __ffi_hash__.
  // This exercises the RecursiveHash guard that rejects eq-without-hash types.
  refl::TypeAttrDef<TestEqWithoutHashObj>().def(
      refl::type_attr::kEq,
      [](const Object* lhs, const Object* rhs, const Function& fn_eq) -> bool {
        auto* a = static_cast<const TestEqWithoutHashObj*>(lhs);
        auto* b = static_cast<const TestEqWithoutHashObj*>(rhs);
        return fn_eq(AnyView(a->key), AnyView(b->key)).cast<bool>();
      });
}

}  // namespace ffi
}  // namespace tvm

// -----------------------------------------------------------------------------
// Exported symbols for metadata testing on DLL-exported functions
// -----------------------------------------------------------------------------
// We keep minimal DLL exports here to verify the export mechanism.
TVM_FFI_DLL_EXPORT_TYPED_FUNC(testing_dll_schema_id_int, tvm::ffi::schema_test_impl::schema_id_int)

// Documentation export
TVM_FFI_DLL_EXPORT_TYPED_FUNC(testing_dll_test_add_with_docstring,
                              tvm::ffi::schema_test_impl::test_add_with_docstring);
TVM_FFI_DLL_EXPORT_TYPED_FUNC_DOC(testing_dll_test_add_with_docstring,
                                  R"(Add two integers and return the sum.

Parameters
----------
a : int
    First integer
b : int
    Second integer

Returns
-------
result : int
    Sum of a and b)");

extern "C" TVM_FFI_DLL_EXPORT int TVMFFITestingDummyTarget() { return 0; }
