//     Copyright 2025, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file

// This file is included from another C file, help IDEs to still parse it on
// its own.
#ifdef __IDE_ONLY__
#include "nuitka/prelude.h"
#endif

// Our replacement for "PyType_IsSubtype"
bool Nuitka_Type_IsSubtype(PyTypeObject *a, PyTypeObject *b) {
    CHECK_OBJECT(a);
    CHECK_OBJECT(b);

#if PYTHON_VERSION < 0x300
    if (!(a->tp_flags & Py_TPFLAGS_HAVE_CLASS)) {
        return b == a || b == &PyBaseObject_Type;
    }
#endif

    PyObject *mro = a->tp_mro;
    CHECK_OBJECT_X(mro);

    if (likely(mro != NULL)) {
        assert(PyTuple_Check(mro));

        Py_ssize_t n = PyTuple_GET_SIZE(mro);

        for (Py_ssize_t i = 0; i < n; i++) {
            if (PyTuple_GET_ITEM(mro, i) == (PyObject *)b) {
                return true;
            }
        }

        return false;
    } else {
        // Fallback for uninitialized classes to base class scan
        do {
            if (a == b) {
                return true;
            }
            a = a->tp_base;
        } while (a != NULL);

        return (b == &PyBaseObject_Type);
    }
}

// TODO: We cannot really do this, until Nuitka_TypeLookup (_PyType_Lookup) is
// not also a call to an API, we just become wasteful here. What will make sense
// is to make specialized variants for not sub class checks, like
// PyExc_GeneratorExit and PyExc_StopIteration by caching the descriptor
// "checker" for them and then calling the "func" behind them more or less
// directly. These could be created during startup and be very fast to use.

#if 0
int Nuitka_Object_IsSubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
{
    // TODO: Checking for a type is nothing the core does, could have a second variant
    if (PyType_CheckExact(cls)) {
        // Only a quick test for an exact match, but then give up.
        if (derived == cls) {
            return 1;
        }

        // Too hard for us.
        return PyObject_IsSubclass(derived, cls);
    }

    // TODO: Checking for a tuple is nothing the core does, could have a second variant
    if (PyTuple_Check(cls)) {
        if (Py_EnterRecursiveCall(" in __subclasscheck__")) {
            return -1;
        }

        Py_ssize_t n = PyTuple_GET_SIZE(cls);
        int r = 0;

        for (Py_ssize_t i = 0; i < n; ++i) {
            PyObject *item = PyTuple_GET_ITEM(cls, i);

            r = Nuitka_Object_IsSubclass(tstate, derived, item);

            if (r != 0) {
                break;
            }
        }

        Py_LeaveRecursiveCall();

        return r;
    }

    // TODO: For many of our uses, we know it.
    PyObject *checker = Nuitka_TypeLookup((PyTypeObject *)cls, const_str_plain___subclasscheck__);

    if (checker != NULL) {
        descrgetfunc f = Py_TYPE(checker)->tp_descr_get;

        if (f == NULL) {
            Py_INCREF(checker);
        } else {
            checker = f(checker, cls, (PyObject *)(Py_TYPE(cls)));
        }
    }

    if (checker != NULL) {
        int ok = -1;

        if (Py_EnterRecursiveCall(" in __subclasscheck__")) {
            Py_DECREF(checker);
            return ok;
        }

        PyObject *res = CALL_FUNCTION_WITH_SINGLE_ARG(checker, derived);

        Py_LeaveRecursiveCall();

        Py_DECREF(checker);

        if (res != NULL) {
            ok = CHECK_IF_TRUE(res);
            Py_DECREF(res);
        }
        return ok;
    } else if (HAS_ERROR_OCCURRED(tstate)) {
        return -1;
    }

    // Too hard for us.
    return PyObject_IsSubclass(derived, cls);
}
#endif

getattrofunc PyObject_GenericGetAttr_resolved;
setattrofunc PyObject_GenericSetAttr_resolved;

// Our wrapper for "PyType_Ready" that takes care of trying to avoid DLL entry
// points for generic attributes. spell-checker: ignore aiter
void Nuitka_PyType_Ready(PyTypeObject *type, PyTypeObject *base, bool generic_get_attr, bool generic_set_attr,
                         bool self_iter, bool await_self_iter, bool await_self_aiter) {
    assert(type->tp_base == NULL);

    PyObject_GenericGetAttr_resolved = PyBaseObject_Type.tp_getattro;
    PyObject_GenericSetAttr_resolved = PyBaseObject_Type.tp_setattro;

    type->tp_base = base;

#if PYTHON_VERSION >= 0x3d0
    if (base != NULL) {
        // Assert that we don't accidentally miss setting pre-header flags
        // that the base type has, as it causes GC segfaults.
        assert(_PyType_PreHeaderSize(type) == _PyType_PreHeaderSize(base));
    }
#endif

    if (generic_get_attr) {
        assert(type->tp_getattro == NULL);
        type->tp_getattro = PyObject_GenericGetAttr_resolved;
    }

    if (generic_set_attr) {
        assert(type->tp_setattro == NULL);
        type->tp_setattro = PyObject_GenericSetAttr_resolved;
    }

    if (self_iter) {
        assert(type->tp_iter == NULL);
        type->tp_iter = PyObject_SelfIter;
    }

#if PYTHON_VERSION >= 0x350
    if (await_self_iter) {
        assert(type->tp_as_async->am_await == NULL);
        type->tp_as_async->am_await = PyObject_SelfIter;
    }

    if (await_self_aiter) {
        assert(type->tp_as_async->am_aiter == NULL);
        type->tp_as_async->am_aiter = PyObject_SelfIter;
    }
#else
    assert(!await_self_iter);
    assert(!await_self_aiter);
#endif

#if PYTHON_VERSION >= 0x3a0
    type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
#endif

    NUITKA_MAY_BE_UNUSED int res = PyType_Ready(type);
    assert(res >= 0);
}

#if PYTHON_VERSION >= 0x3c0

typedef struct {
    PyObject_HEAD PyObject *name;
    PyObject *type_params;
    PyObject *compute_value;
    PyObject *value;
    PyObject *module;
} typealiasobject;

static PyTypeObject *getTypeAliasType(void) {
    static PyTypeObject *type_alias_type = NULL;

    if (type_alias_type == NULL) {

        PyObject *typing_module = PyImport_ImportModule("_typing");
        CHECK_OBJECT(typing_module);

        type_alias_type = (PyTypeObject *)PyObject_GetAttrString(typing_module, "TypeAliasType");
        CHECK_OBJECT(type_alias_type);
    }

    return type_alias_type;
}

PyObject *MAKE_TYPE_ALIAS(PyObject *name, PyObject *type_params, PyObject *value, PyObject *module_name) {
    // TODO: For Python 3.13 we can use the intrinsic.

    typealiasobject *ta = Nuitka_GC_New(getTypeAliasType());

    // TODO: Lets follow Python new inline function in the future, this is 3.12
    // only code, so we can use it here.
    ta->name = Py_NewRef(name);
    ta->type_params = Py_IsNone(type_params) ? NULL : Py_XNewRef(type_params);
    ta->compute_value = NULL;
    ta->value = Py_XNewRef(value);
    ta->module = Py_NewRef(module_name);

    Nuitka_GC_Track(ta);

    return (PyObject *)ta;
}

typedef struct {
    PyObject_HEAD PyObject *name;
    PyObject *bound;
    PyObject *evaluate_bound;
    PyObject *constraints;
    PyObject *evaluate_constraints;
#if PYTHON_VERSION >= 0x3d0
    PyObject *default_value;
    PyObject *evaluate_default;
#endif
    bool covariant;
    bool contravariant;
    bool infer_variance;
} typevarobject;

typedef struct {
    PyObject_HEAD PyObject *name;
#if PYTHON_VERSION >= 0x3d0
    PyObject *default_value;
    PyObject *evaluate_default;
#endif
} typevartupleobject; // Following CPython, spell-checker: ignore typevartupleobject

typedef struct {
    PyObject_HEAD PyObject *name;
    PyObject *bound;
#if PYTHON_VERSION >= 0x3d0
    PyObject *default_value;
    PyObject *evaluate_default;
#endif
    bool covariant;
    bool contravariant;
    bool infer_variance;
} paramspecobject;

static typevarobject *_Nuitka_typevar_alloc(PyThreadState *tstate, PyObject *name, PyObject *bound,
                                            PyObject *evaluate_bound, PyObject *constraints,
                                            PyObject *evaluate_constraints, bool covariant, bool contravariant,
                                            bool infer_variance, PyObject *module) {
    PyTypeObject *tp = tstate->interp->cached_objects.typevar_type;
    typevarobject *result = Nuitka_GC_New(tp);

    result->name = Py_NewRef(name);

    result->bound = Py_XNewRef(bound);
    result->evaluate_bound = Py_XNewRef(evaluate_bound);
    result->constraints = Py_XNewRef(constraints);
    result->evaluate_constraints = Py_XNewRef(evaluate_constraints);
#if PYTHON_VERSION >= 0x3d0
    result->default_value = NULL;
    result->evaluate_default = NULL;
#endif

    result->covariant = covariant;
    result->contravariant = contravariant;
    result->infer_variance = infer_variance;

    Nuitka_GC_Track(result);

    // TODO: Not seen yet.
    if (unlikely(module != NULL)) {
        if (PyObject_SetAttrString((PyObject *)result, "__module__", module) < 0) {
            Py_DECREF(result);
            return NULL;
        }
    }

    return result;
}

static typevartupleobject *_Nuitka_typevartuple_alloc(PyThreadState *tstate, PyObject *name, PyObject *module,
                                                      PyObject *default_value) {
    PyTypeObject *tp = tstate->interp->cached_objects.typevartuple_type;
    typevartupleobject *tvt = Nuitka_GC_New(tp);
    if (tvt == NULL) {
        return NULL;
    }
    tvt->name = Py_NewRef(name);
#if PYTHON_VERSION >= 0x3d0
    tvt->default_value = Py_XNewRef(default_value);
    tvt->evaluate_default = NULL;
#endif
    Nuitka_GC_Track(tvt);
    if (module != NULL) {
        if (PyObject_SetAttrString((PyObject *)tvt, "__module__", module) < 0) {
            Py_DECREF(tvt);
            return NULL;
        }
    }
    return tvt;
}

static paramspecobject *_Nuitka_paramspec_alloc(PyThreadState *tstate, PyObject *name, PyObject *bound,
                                                PyObject *default_value, bool covariant, bool contravariant,
                                                bool infer_variance, PyObject *module) {
    PyTypeObject *tp = tstate->interp->cached_objects.paramspec_type;
    paramspecobject *ps = Nuitka_GC_New(tp);
    if (ps == NULL) {
        return NULL;
    }
    ps->name = Py_NewRef(name);
    ps->bound = Py_XNewRef(bound);
    ps->covariant = covariant;
    ps->contravariant = contravariant;
    ps->infer_variance = infer_variance;
#if PYTHON_VERSION >= 0x3d0
    ps->default_value = Py_XNewRef(default_value);
    ps->evaluate_default = NULL;
#endif
    Nuitka_GC_Track(ps);
    if (module != NULL) {
        if (PyObject_SetAttrString((PyObject *)ps, "__module__", module) < 0) {
            Py_DECREF(ps);
            return NULL;
        }
    }
    return ps;
}

PyObject *MAKE_TYPE_VAR(PyThreadState *tstate, PyObject *name) {
    // TODO: For Python 3.13 this would work.
    // return _PyIntrinsics_UnaryFunctions[INTRINSIC_TYPEVAR].func(tstate, name);

    return (PyObject *)_Nuitka_typevar_alloc(tstate, name, NULL, NULL, NULL, NULL, false, false, true, NULL);
}

PyObject *MAKE_TYPE_VAR_TUPLE(PyThreadState *tstate, PyObject *name) {
    return (PyObject *)_Nuitka_typevartuple_alloc(tstate, name, NULL, NULL);
}

PyObject *MAKE_PARAM_SPEC(PyThreadState *tstate, PyObject *name) {
    return (PyObject *)_Nuitka_paramspec_alloc(tstate, name, NULL, NULL, false, false, true, NULL);
}

static PyTypeObject *_getTypeGenericAliasType(void) {
    static PyTypeObject *type_generic_alias_type = NULL;

    if (type_generic_alias_type == NULL) {

        PyObject *types_module = IMPORT_HARD_TYPES();

        type_generic_alias_type = (PyTypeObject *)PyObject_GetAttrString(types_module, "GenericAlias");
        CHECK_OBJECT(type_generic_alias_type);
    }

    return type_generic_alias_type;
}

static int _Nuitka_contains_typevartuple(PyTupleObject *params) {
    Py_ssize_t n = PyTuple_GET_SIZE(params);
    PyTypeObject *tp = PyInterpreterState_Get()->cached_objects.typevartuple_type;
    for (Py_ssize_t i = 0; i < n; i++) {
        PyObject *param = PyTuple_GET_ITEM(params, i);
        if (Py_IS_TYPE(param, tp)) {
            return 1;
        }
    }
    return 0;
}

// Match CPython, spell-checker: ignore typevartuple,typevartuples
static PyObject *_Nuitka_unpack_typevartuples(PyThreadState *tstate, PyObject *params) {
    assert(PyTuple_Check(params));

    // TypeVarTuple must be unpacked when passed to Generic, so we do that here.
    if (_Nuitka_contains_typevartuple((PyTupleObject *)params)) {
        Py_ssize_t n = PyTuple_GET_SIZE(params);

        PyObject *new_params = MAKE_TUPLE_EMPTY(tstate, n);
        PyTypeObject *tp = tstate->interp->cached_objects.typevartuple_type;

        for (Py_ssize_t i = 0; i < n; i++) {
            PyObject *param = PyTuple_GET_ITEM(params, i);

            if (Py_IS_TYPE(param, tp)) {
                PyObject *unpacked = _Nuitka_unpack_typevartuples(tstate, param);

                if (unlikely(unpacked == NULL)) {
                    Py_DECREF(new_params);
                    return NULL;
                }

                PyTuple_SET_ITEM(new_params, i, unpacked);
            } else {
                PyTuple_SET_ITEM(new_params, i, Py_NewRef(param));
            }
        }

        return new_params;
    } else {
        return Py_NewRef(params);
    }
}

PyObject *MAKE_TYPE_GENERIC(PyThreadState *tstate, PyObject *params) {
    CHECK_OBJECT(params);
    PyObject *unpacked_params = _Nuitka_unpack_typevartuples(tstate, params);
    CHECK_OBJECT(unpacked_params);
    assert(PyTuple_CheckExact(unpacked_params));

    PyObject *args[2] = {(PyObject *)tstate->interp->cached_objects.generic_type, unpacked_params};

    PyObject *called = (PyObject *)_getTypeGenericAliasType();

    PyObject *result = CALL_FUNCTION_WITH_ARGS2(tstate, called, args);
    Py_DECREF(unpacked_params);

    return MAKE_TUPLE1_0(tstate, result);
}

#endif

//     Part of "Nuitka", an optimizing Python compiler that is compatible and
//     integrates with CPython, but also works on its own.
//
//     Licensed under the GNU Affero General Public License, Version 3 (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.gnu.org/licenses/agpl.txt
//
//     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.
