############### ExceptStar ##########################
# I'd really like to use _PyxExc_PrepReraiseStar but it isn't exported publicly
# so reimplement it here in Cython

cimport cython

cdef extern from *:
    # All these functions can return NULL as a valid outcome, therefore wrap them
    # in something that returns a dummy object in this case.
    # The """""" are to stop Cython stripping pre-processor macros like comments.
    """
    """"""#if CYTHON_USE_OWN_PREP_RERAISE_STAR
    static PyObject *__Pyx_Safe_PyException_GetTraceback(PyObject *exc, PyObject *dummy_null) {
        PyObject *out = PyException_GetTraceback(exc);
        if (out) return out;
        if (PyErr_Occurred()) return NULL;
        return Py_NewRef(dummy_null);
    }

    static PyObject *__Pyx_Safe_PyException_GetCause(PyObject *exc, PyObject *dummy_null) {
        PyObject *out = PyException_GetCause(exc);
        if (out) return out;
        if (PyErr_Occurred()) return NULL;
        return Py_NewRef(dummy_null);
    }

    static PyObject *__Pyx_Safe_PyException_GetContext(PyObject *exc, PyObject *dummy_null) {
        PyObject *out = PyException_GetContext(exc);
        if (out) return out;
        if (PyErr_Occurred()) return NULL;
        return Py_NewRef(dummy_null);
    }
    """"""#endif
    """
    object __Pyx_Safe_PyException_GetTraceback(object, object)
    object __Pyx_Safe_PyException_GetCause(object, object)
    object __Pyx_Safe_PyException_GetContext(object, object)

    cdef void *PyExc_BaseExceptionGroup


@cython.c_compile_guard("CYTHON_USE_OWN_PREP_RERAISE_STAR")
@cname("__Pyx_split_into_same_metadata")
cdef tuple[list,list] split_into_same_metadata(original, list exceptions):
    # returns a list with the same cause and a list with different causes
    cdef list same = []
    cdef list different = []

    dummy_null = object()

    original_notes = getattr(original, "__notes__", dummy_null)
    original_traceback = __Pyx_Safe_PyException_GetTraceback(original, dummy_null)
    original_cause = __Pyx_Safe_PyException_GetCause(original, dummy_null)
    original_context = __Pyx_Safe_PyException_GetContext(original, dummy_null)

    for e in exceptions:
        if e is None:
            continue
        # "dummy_null" object will always pass the "is" test.
        if (getattr(e, "__notes__", dummy_null) is original_notes and
                __Pyx_Safe_PyException_GetTraceback(e, dummy_null) is original_traceback and
                __Pyx_Safe_PyException_GetCause(e, dummy_null) is original_cause and
                __Pyx_Safe_PyException_GetContext(e, dummy_null) is original_context):
            same.append(e)
        else:
            different.append(e)

    return same, different

@cython.c_compile_guard("CYTHON_USE_OWN_PREP_RERAISE_STAR")
@cname("__Pyx_except_star_leafs")
cdef set get_leafs(list keep):
    # get a set with ids of all the leafs
    cdef list to_process = list(keep)
    cdef set leafs = set()
    for e_or_eg in to_process:
        if not isinstance(e_or_eg, <type>PyExc_BaseExceptionGroup):
            leafs.add(id(e_or_eg))
        else:
            to_process.extend(e_or_eg.exceptions)
    return leafs

@cython.c_compile_guard("CYTHON_USE_OWN_PREP_RERAISE_STAR")
@cname("__Pyx_exception_group_projection")
cdef exception_group_projection(orig, list keep):
    leafs = get_leafs(keep)

    # BaseExceptionGroup.split requires an actual Python function - a Cython callable won't do
    func = eval("lambda x: id(x) in leafs", {'leafs': leafs})
    return orig.split(func)[0]

@cython.c_compile_guard("CYTHON_USE_OWN_PREP_RERAISE_STAR")
@cname("__Pyx__PyExc_PrepReraiseStar")
cdef prep_reraise_star(orig, list excs):
    cdef list reraised, raised
    if not excs:
        return None
    if not isinstance(orig, <type>PyExc_BaseExceptionGroup):
        assert len(excs) == 1 or len(excs) == 2 and excs[1] is None
        return excs[0]
    reraised, raised = split_into_same_metadata(orig, excs)
    reraised_eg = exception_group_projection(orig, reraised)
    if not raised:
        return reraised_eg
    if reraised_eg is not None:
        raised.append(reraised_eg)
    if len(raised) > 1:
        return (<type>PyExc_BaseExceptionGroup)("", raised)
    return raised[0]
