// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2025 DBZero Software sp. z o.o.

#pragma once

#include <cstddef>
#include <Python.h>
#include "PyWrapper.hpp"
#include <vector>
#include <unordered_map>
#include "shared_py_object.hpp"
#include <dbzero/core/memory/AccessOptions.hpp>
#include "Migration.hpp"
#include "MemoTypeDecoration.hpp"
#include "MemoObject.hpp"
#include <dbzero/object_model/object/Object.hpp>
#include <dbzero/object_model/object/ObjectImmutableImpl.hpp>
#include <dbzero/object_model/object/ObjectAnyImpl.hpp>

namespace db0::python

{
    
    using AccessType = db0::AccessType;
    
    PyObject *PyAPI_wrapPyClass(PyObject *self, PyObject *, PyObject *kwargs);
    // create a memo object stub
    MemoObject* MemoObjectStub_new(PyTypeObject *type);
    
    void MemoObject_drop(MemoObject* self);
    
    // check if memo type has been marked as singleton
    bool PyMemoType_IsSingleton(PyTypeObject *type);
    
    template <typename MemoImplT>
    PyObject *MemoObject_DescribeObject(MemoImplT *);
    
    void MemoType_get_info(PyTypeObject *type, PyObject *dict);
    void MemoType_close(PyTypeObject *type);
    
    template <typename MemoImplT>
    PyObject *MemoObject_set_prefix(MemoImplT *, const char *prefix_name);
    
    PyObject *tryGetAttributes(PyTypeObject *type);

    // Try retrieving a memo member cast to a specific type
    // type ignored for non-memo members
    template <typename MemoImplT>
    PyObject *tryGetAttrAs(MemoImplT *, PyObject *attr, PyTypeObject *);
    
    template <typename MemoImplT>
    PyObject *tryLoadMemo(MemoImplT *memo_obj, PyObject* kwargs,  PyObject* exclude,
        std::unordered_set<const void*> *load_stack_ptr = nullptr, bool load_all = false);
    
    // check for a memo type (i.e. generated by PyAPI_wrapPyClass)
    template <typename MemoImplT> bool PyMemo_Check(PyObject *);
    template <typename MemoImplT> bool PyMemoType_Check(PyTypeObject *);
    
    bool PyAnyMemo_Check(PyObject *);
    bool PyAnyMemoType_Check(PyTypeObject *);
    
    PyObject *PyAPI_PyMemo_Check(PyObject *self, PyObject *const * args, Py_ssize_t nargs);
    
    // Binary (shallow) compare 2 objects or 2 versions of the same memo object (e.g. from different snapshots)
    // NOTE: ref-counts are not compared (only user-assigned members)
    // @return true if objects are identical
    PyObject *tryCompareMemo(MemoObject *, MemoObject *);
    
    PyObject *PyAPI_getSchema(PyObject *, PyObject *const *args, Py_ssize_t nargs);
    PyObject* executeLoadFunction(PyObject *load_method, PyObject *kwargs, PyObject *py_exclude,
        std::unordered_set<const void*> *load_stack_ptr);
    
    extern template bool PyMemo_Check<MemoObject>(PyObject *);
    extern template bool PyMemo_Check<MemoImmutableObject>(PyObject *);
    extern template bool PyMemoType_Check<MemoObject>(PyTypeObject *);
    extern template bool PyMemoType_Check<MemoImmutableObject>(PyTypeObject *);
    extern template PyObject *MemoObject_DescribeObject<MemoObject>(MemoObject *);
    extern template PyObject *MemoObject_DescribeObject<MemoImmutableObject>(MemoImmutableObject *);
    extern template PyObject *MemoObject_set_prefix<MemoObject>(MemoObject *, const char *);
    extern template PyObject *MemoObject_set_prefix<MemoImmutableObject>(MemoImmutableObject *, const char *);
    extern template PyObject *tryGetAttrAs<MemoObject>(MemoObject *, PyObject *, PyTypeObject *);
    extern template PyObject *tryGetAttrAs<MemoImmutableObject>(MemoImmutableObject *, PyObject *, PyTypeObject *);
    extern template PyObject *tryLoadMemo<MemoObject>(MemoObject *, PyObject*, PyObject*,
        std::unordered_set<const void*> *, bool load_all);
    extern template PyObject *tryLoadMemo<MemoImmutableObject>(MemoImmutableObject *, PyObject*, PyObject*,
        std::unordered_set<const void*> *, bool load_all);
    
}
