// Implementation of generic vec[t], when t is a plain type object (possibly optional).
//
// Examples of types supported:
//
//  - vec[str]
//  - vec[str | None]
//  - vec[object]
//  - vec[UserClass]

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "librt_vecs.h"
#include "vecs_internal.h"

#define VEC_BUF(v) ((VecTBufObject *)((char *)(v).items - offsetof(VecTBufObject, items)))
#define VEC_CAP(v) (VEC_BUF(v)->ob_base.ob_size)
#define VEC_INCREF(v) do { if ((v).items) Py_INCREF(VEC_BUF(v)); } while (0)
#define VEC_DECREF(v) do { if ((v).items) Py_DECREF(VEC_BUF(v)); } while (0)

static inline VecT vec_error() {
    VecT v = { .len = -1 };
    return v;
}

static inline VecTBufObject *alloc_buf(Py_ssize_t size, size_t item_type) {
    VecTBufObject *buf = PyObject_GC_NewVar(VecTBufObject, &VecTBufType, size);
    if (buf == NULL)
        return NULL;
    buf->item_type = item_type;
    Py_INCREF(VEC_BUF_ITEM_TYPE(buf));
    return buf;
}

static inline void vec_track_buffer(VecT *vec) {
    if (vec->items != NULL) {
        PyObject_GC_Track(VEC_T_BUF(*vec));
    }
}

// Alloc a partially initialized vec. If size > 0, caller *must* immediately initialize len,
// and items. Caller *must* also call vec_track_buffer on the returned vec but only
// after initializing the items.
static VecT vec_alloc(Py_ssize_t size, size_t item_type) {
    VecTBufObject *buf;

    if (size == 0) {
        buf = NULL;
    } else {
        buf = alloc_buf(size, item_type);
        if (buf == NULL)
            return vec_error();
    }
    return (VecT) { .items = (buf != NULL) ? buf->items : NULL };
}

// Box a VecT value, stealing 'vec'. On failure, return NULL and decref 'vec'.
PyObject *VecT_Box(VecT vec, size_t item_type) {
    // An unboxed empty vec may have NULL items, but a boxed vec must have a buf
    // allocated, since it contains the item type
    if (vec.items == NULL) {
        VecTBufObject *buf = alloc_buf(0, item_type);
        if (buf == NULL)
            return NULL;
        vec.items = buf->items;
        vec_track_buffer(&vec);
    }
    VecTObject *obj = PyObject_GC_New(VecTObject, &VecTType);
    if (obj == NULL) {
        // items is always defined, so no need for a NULL check
        Py_DECREF(VEC_BUF(vec));
        return NULL;
    }
    obj->vec = vec;
    PyObject_GC_Track(obj);
    return (PyObject *)obj;
}

VecT VecT_Unbox(PyObject *obj, size_t item_type) {
    if (obj->ob_type == &VecTType) {
        VecT result = ((VecTObject *)obj)->vec;
        if (VEC_BUF(result)->item_type == item_type) {
            VEC_INCREF(result);  // TODO: Should we borrow instead?
            return result;
        }
    }
    // TODO: Better error message, with name of type
    PyErr_SetString(PyExc_TypeError, "vec[t] expected");
    return vec_error();
}

VecT VecT_ConvertFromNested(VecNestedBufItem item) {
    return (VecT) { item.len, (PyObject **)item.items };
}

VecT VecT_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type) {
    if (cap < 0) {
        PyErr_SetString(PyExc_ValueError, "capacity must not be negative");
        return vec_error();
    }
    if (cap < size)
        cap = size;
    VecT vec = vec_alloc(cap, item_type);
    if (VEC_IS_ERROR(vec))
        return vec;
    for (Py_ssize_t i = 0; i < cap; i++) {
        vec.items[i] = NULL;
    }
    vec_track_buffer(&vec);
    vec.len = size;
    return vec;
}

static PyObject *vec_repr(PyObject *self) {
    VecTObject *v = (VecTObject *)self;
    return Vec_GenericRepr(self, VEC_BUF(v->vec)->item_type, 0, 1);
}

static PyObject *vec_get_item(PyObject *o, Py_ssize_t i) {
    VecT v = ((VecTObject *)o)->vec;
    if ((size_t)i < (size_t)v.len) {
        PyObject *item = v.items[i];
        Py_INCREF(item);
        return item;
    } else if ((size_t)i + (size_t)v.len < (size_t)v.len) {
        PyObject *item = v.items[i + v.len];
        Py_INCREF(item);
        return item;
    } else {
        PyErr_SetString(PyExc_IndexError, "index out of range");
        return NULL;
    }
}

VecT VecT_Slice(VecT vec, int64_t start, int64_t end) {
    if (start < 0)
        start += vec.len;
    if (end < 0)
        end += vec.len;
    if (start < 0)
        start = 0;
    if (start >= vec.len)
        start = vec.len;
    if (end < start)
        end = start;
    if (end > vec.len)
        end = vec.len;
    int64_t slicelength = end - start;
    if (slicelength == 0)
        return (VecT) { .len = 0, .items = NULL };
    VecT res = vec_alloc(slicelength, VEC_BUF(vec)->item_type);
    if (VEC_IS_ERROR(res))
        return res;
    res.len = slicelength;
    for (Py_ssize_t i = 0; i < slicelength; i++) {
        PyObject *item = vec.items[start + i];
        Py_INCREF(item);
        res.items[i] = item;
    }
    vec_track_buffer(&res);
    return res;
}

static PyObject *vec_subscript(PyObject *self, PyObject *item) {
    VecT vec = ((VecTObject *)self)->vec;
    if (PyIndex_Check(item)) {
        Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
        if (i == -1 && PyErr_Occurred())
            return NULL;
        if ((size_t)i < (size_t)vec.len) {
            PyObject *result = vec.items[i];
            Py_INCREF(result);
            return result;
        } else if ((size_t)i + (size_t)vec.len < (size_t)vec.len) {
            PyObject *result = vec.items[i + vec.len];
            Py_INCREF(result);
            return result;
        } else {
            PyErr_SetString(PyExc_IndexError, "index out of range");
            return NULL;
        }
    } else if (PySlice_Check(item)) {
        Py_ssize_t start, stop, step;
        if (PySlice_Unpack(item, &start, &stop, &step) < 0)
            return NULL;
        Py_ssize_t slicelength = PySlice_AdjustIndices(vec.len, &start, &stop, step);
        VecT res = vec_alloc(slicelength, VEC_BUF(vec)->item_type);
        if (VEC_IS_ERROR(res))
            return NULL;
        res.len = slicelength;
        Py_ssize_t j = start;
        for (Py_ssize_t i = 0; i < slicelength; i++) {
            PyObject *item = vec.items[j];
            Py_INCREF(item);
            res.items[i] = item;
            j += step;
        }
        vec_track_buffer(&res);
        PyObject *result = VecT_Box(res, VEC_BUF(vec)->item_type);
        if (result == NULL) {
            VEC_DECREF(res);
        }
        return result;
    } else {
        PyErr_Format(PyExc_TypeError, "vec indices must be integers or slices, not %.100s",
                     item->ob_type->tp_name);
        return NULL;
    }
}

static int vec_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) {
    VecT v = ((VecTObject *)self)->vec;
    if (!VecT_ItemCheck(v, o, VEC_BUF(v)->item_type))
        return -1;
    if ((size_t)i < (size_t)v.len) {
        PyObject *old = v.items[i];
        Py_INCREF(o);
        v.items[i] = o;
        Py_XDECREF(old);
        return 0;
    } else if ((size_t)i + (size_t)v.len < (size_t)v.len) {
        PyObject *old = v.items[i + v.len];
        Py_INCREF(o);
        v.items[i + v.len] = o;
        Py_XDECREF(old);
        return 0;
    } else {
        PyErr_SetString(PyExc_IndexError, "index out of range");
        return -1;
    }
}

static int vec_contains(PyObject *self, PyObject *value) {
    VecT v = ((VecTObject *)self)->vec;
    for (Py_ssize_t i = 0; i < v.len; i++) {
        PyObject *item = v.items[i];
        if (item == value) {
            return 1;
        }
        Py_INCREF(item);
        int cmp = PyObject_RichCompareBool(item, value, Py_EQ);
        Py_DECREF(item);
        if (cmp != 0)
            return cmp;  // 1 if equal, -1 on error
    }
    return 0;
}

static PyObject *vec_richcompare(PyObject *self, PyObject *other, int op) {
    PyObject *res;
    if (op == Py_EQ || op == Py_NE) {
        if (other->ob_type != &VecTType) {
            res = op == Py_EQ ? Py_False : Py_True;
        } else {
            VecT x = ((VecTObject *)self)->vec;
            VecT y = ((VecTObject *)other)->vec;
            if (VEC_BUF(x)->item_type != VEC_BUF(y)->item_type) {
                res = op == Py_EQ ? Py_False : Py_True;
            } else {
                // TODO: why pointers to len?
                return Vec_GenericRichcompare(&x.len, x.items, &y.len, y.items, op);
            }
        }
    } else
        res = Py_NotImplemented;
    Py_INCREF(res);
    return res;
}

// Append item to 'vec', stealing 'vec'. Return 'vec' with item appended.
VecT VecT_Append(VecT vec, PyObject *x, size_t item_type) {
    if (vec.items == NULL) {
        VecT new = vec_alloc(1, item_type);
        if (VEC_IS_ERROR(new))
            return new;
        Py_INCREF(x);
        new.len = 1;
        new.items[0] = x;
        vec_track_buffer(&new);
        return new;
    }
    Py_ssize_t cap = VEC_CAP(vec);
    Py_INCREF(x);
    if (vec.len < cap) {
        // Slot may have duplicate ref from prior remove/pop
        Py_XSETREF(vec.items[vec.len], x);
        vec.len++;
        return vec;
    } else {
        Py_ssize_t new_size = Vec_GrowCapacity(cap);
        // TODO: Avoid initializing to zero here
        VecT new = vec_alloc(new_size, VEC_BUF(vec)->item_type);
        if (VEC_IS_ERROR(new)) {
            Py_DECREF(x);
            // The input vec is being consumed/stolen by this function, so on error
            // we must decref it to avoid leaking the buffer.
            VEC_DECREF(vec);
            return new;
        }
        // Copy items to new vec.
        memcpy(new.items, vec.items, sizeof(PyObject *) * vec.len);
        memset(new.items + vec.len, 0, sizeof(PyObject *) * (new_size - vec.len));
        VecTBufObject *old_buf = VEC_BUF(vec);
        if (Py_REFCNT(old_buf) > 1) {
            // Other references to old buffer exist; INCREF items in new buffer
            // so old buffer keeps valid references for aliases.
            for (Py_ssize_t i = 0; i < vec.len; i++)
                Py_XINCREF(new.items[i]);
        } else {
            // No aliases; transfer ownership by clearing old buffer items.
            memset(vec.items, 0, sizeof(PyObject *) * vec.len);
        }
        new.items[vec.len] = x;
        new.len = vec.len + 1;
        vec_track_buffer(&new);
        VEC_DECREF(vec);
        return new;
    }
}

// Extend 'vec' with items from 'iterable', stealing 'vec'.
// Return extended 'vec', or error vec on failure.
VecT VecT_Extend(VecT vec, PyObject *iterable, size_t item_type) {
    if (VecT_Check(iterable)) {
        VecT src = ((VecTObject *)iterable)->vec;
        if (src.items != NULL && VEC_BUF(src)->item_type == item_type) {
            return VecT_ExtendVec(vec, src, item_type);
        }
    }

    PyObject *iter = PyObject_GetIter(iterable);
    if (iter == NULL) {
        VEC_DECREF(vec);
        return vec_error();
    }
    PyObject *item;
    while ((item = PyIter_Next(iter)) != NULL) {
        if (!VecT_ItemCheck(vec, item, item_type)) {
            Py_DECREF(iter);
            VEC_DECREF(vec);
            Py_DECREF(item);
            return vec_error();
        }
        vec = VecT_Append(vec, item, item_type);
        Py_DECREF(item);
        if (VEC_IS_ERROR(vec)) {
            Py_DECREF(iter);
            return vec_error();
        }
    }
    Py_DECREF(iter);
    if (PyErr_Occurred()) {
        VEC_DECREF(vec);
        return vec_error();
    }
    return vec;
}

// Extend 'dst' with items from 'src' vec, stealing 'dst', borrowing 'src'.
// Return extended vec, or error vec on failure.
VecT VecT_ExtendVec(VecT dst, VecT src, size_t item_type) {
    if (src.len == 0)
        return dst;
    if (src.len > PY_SSIZE_T_MAX - dst.len) {
        PyErr_NoMemory();
        VEC_DECREF(dst);
        return vec_error();
    }
    Py_ssize_t new_len = dst.len + src.len;
    if (dst.items == NULL) {
        // dst is empty, allocate new buf
        VecT new = vec_alloc(new_len, item_type);
        if (VEC_IS_ERROR(new)) {
            VEC_DECREF(dst);
            return new;
        }
        for (Py_ssize_t i = 0; i < src.len; i++) {
            Py_INCREF(src.items[i]);
            new.items[i] = src.items[i];
        }
        memset(new.items + src.len, 0, sizeof(PyObject *) * (new_len - src.len));
        new.len = new_len;
        vec_track_buffer(&new);
        return new;
    }
    Py_ssize_t cap = VEC_CAP(dst);
    if (new_len <= cap && dst.items != src.items) {
        // Fast path: enough capacity and no aliasing
        for (Py_ssize_t i = 0; i < src.len; i++) {
            Py_INCREF(src.items[i]);
            // Slot may have duplicate ref from prior remove/pop
            Py_XSETREF(dst.items[dst.len + i], src.items[i]);
        }
        dst.len = new_len;
        return dst;
    }
    // Need to reallocate (or dst and src share a buffer)
    Py_ssize_t new_cap = Vec_GrowCapacityTo(cap, new_len);
    int aliased = dst.items == src.items;
    VecT new = vec_alloc(new_cap, VEC_BUF(dst)->item_type);
    if (VEC_IS_ERROR(new)) {
        VEC_DECREF(dst);
        return new;
    }
    if (aliased) {
        // dst and src share a buffer -- incref all items instead of
        // moving refs, to avoid mutating the shared buffer
        for (Py_ssize_t i = 0; i < dst.len; i++) {
            Py_INCREF(dst.items[i]);
            new.items[i] = dst.items[i];
        }
    } else {
        memcpy(new.items, dst.items, sizeof(PyObject *) * dst.len);
        VecTBufObject *dst_buf = VEC_BUF(dst);
        if (Py_REFCNT(dst_buf) > 1) {
            for (Py_ssize_t i = 0; i < dst.len; i++)
                Py_XINCREF(new.items[i]);
        } else {
            memset(dst.items, 0, sizeof(PyObject *) * dst.len);
        }
    }
    // Copy src items (incref each)
    for (Py_ssize_t i = 0; i < src.len; i++) {
        Py_INCREF(src.items[i]);
        new.items[dst.len + i] = src.items[i];
    }
    memset(new.items + new_len, 0, sizeof(PyObject *) * (new_cap - new_len));
    new.len = new_len;
    vec_track_buffer(&new);
    VEC_DECREF(dst);
    return new;
}

// Convert vec to list, stealing 'v'.
PyObject *VecT_ToList(VecT v) {
    Py_ssize_t n = v.len;
    PyObject *list = PyList_New(n);
    if (list == NULL) {
        VEC_DECREF(v);
        return NULL;
    }
    if (n > 0 && Py_REFCNT(VEC_BUF(v)) == 1) {
        for (Py_ssize_t i = 0; i < n; i++) {
            PyList_SET_ITEM(list, i, v.items[i]);
            v.items[i] = NULL;
        }
    } else {
        for (Py_ssize_t i = 0; i < n; i++) {
            PyObject *item = v.items[i];
            Py_INCREF(item);
            PyList_SET_ITEM(list, i, item);
        }
    }
    VEC_DECREF(v);
    return list;
}

// Convert vec to tuple, stealing 'v'.
PyObject *VecT_ToTuple(VecT v) {
    Py_ssize_t n = v.len;
    PyObject *tuple = PyTuple_New(n);
    if (tuple == NULL) {
        VEC_DECREF(v);
        return NULL;
    }
    if (n > 0 && Py_REFCNT(VEC_BUF(v)) == 1) {
        for (Py_ssize_t i = 0; i < n; i++) {
            PyTuple_SET_ITEM(tuple, i, v.items[i]);
            v.items[i] = NULL;
        }
    } else {
        for (Py_ssize_t i = 0; i < n; i++) {
            PyObject *item = v.items[i];
            Py_INCREF(item);
            PyTuple_SET_ITEM(tuple, i, item);
        }
    }
    VEC_DECREF(v);
    return tuple;
}

// Remove item from 'vec', stealing 'vec'. Return 'vec' with item removed.
VecT VecT_Remove(VecT v, PyObject *arg) {
    PyObject **items = v.items;
    for (Py_ssize_t i = 0; i < v.len; i++) {
        int match = 0;
        if (items[i] == arg)
            match = 1;
        else {
            int itemcmp = PyObject_RichCompareBool(items[i], arg, Py_EQ);
            if (itemcmp < 0) {
                // The input v is being consumed/stolen by this function, so on error
                // we must decref it to avoid leaking the buffer.
                VEC_DECREF(v);
                return vec_error();
            }
            match = itemcmp;
        }
        if (match) {
            if (i < v.len - 1) {
                Py_CLEAR(items[i]);
                for (; i < v.len - 1; i++) {
                    items[i] = items[i + 1];
                }
                // Keep a duplicate item, since there could be another reference
                // to the buffer with a longer length, and they expect a valid reference.
                Py_XINCREF(items[v.len - 1]);
            }
            v.len--;
            // Return the stolen reference without INCREF
            return v;
        }
    }
    PyErr_SetString(PyExc_ValueError, "vec.remove(x): x not in vec");
    // The input v is being consumed/stolen by this function, so on error
    // we must decref it to avoid leaking the buffer.
    VEC_DECREF(v);
    return vec_error();
}

// Pop item from 'vec', stealing 'vec'. Return struct with modified 'vec' and the popped item.
VecTPopResult VecT_Pop(VecT v, Py_ssize_t index) {
    VecTPopResult result;

    if (index < 0)
        index += v.len;

    if (index < 0 || index >= v.len) {
        PyErr_SetString(PyExc_IndexError, "index out of range");
        // The input v is being consumed/stolen by this function, so on error
        // we must decref it to avoid leaking the buffer.
        VEC_DECREF(v);
        result.f0 = vec_error();
        result.f1 = NULL;
        return result;
    }

    PyObject **items = v.items;
    result.f1 = items[index];
    for (Py_ssize_t i = index; i < v.len - 1; i++)
        items[i] = items[i + 1];
    // Keep duplicate item, since there could be another reference
    // to the buffer with a longer length, and they expect a valid reference.
    Py_XINCREF(items[v.len - 1]);
    v.len--;
    // Return the stolen reference without INCREF
    result.f0 = v;
    return result;
}

static int
VecT_traverse(VecTObject *self, visitproc visit, void *arg)
{
    if (self->vec.items)
        Py_VISIT(VEC_BUF(self->vec));
    return 0;
}

static int
VecT_clear(VecTObject *self)
{
    if (self->vec.items) {
        Py_DECREF(VEC_BUF(self->vec));
        self->vec.items = NULL;
    }
    return 0;
}

static void
VecT_dealloc(VecTObject *self)
{
    PyObject_GC_UnTrack(self);
    Py_TRASHCAN_BEGIN(self, VecT_dealloc)
    VecT_clear(self);
    Py_TYPE(self)->tp_free((PyObject *)self);
    Py_TRASHCAN_END
}

static int
VecTBuf_traverse(VecTBufObject *self, visitproc visit, void *arg)
{
    Py_VISIT(VEC_BUF_ITEM_TYPE(self));
    for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) {
        Py_VISIT(self->items[i]);
    }
    return 0;
}

static inline int
VecTBuf_clear(VecTBufObject *self)
{
    if (self->item_type) {
        Py_DECREF(VEC_BUF_ITEM_TYPE(self));
        self->item_type = 0;
    }
    for (Py_ssize_t i = 0; i < VEC_BUF_SIZE(self); i++) {
        Py_CLEAR(self->items[i]);
    }
    return 0;
}

static void
VecTBuf_dealloc(VecTBufObject *self)
{
    PyObject_GC_UnTrack(self);
    Py_TRASHCAN_BEGIN(self, VecTBuf_dealloc)
    VecTBuf_clear(self);
    Py_TYPE(self)->tp_free((PyObject *)self);
    Py_TRASHCAN_END
}

static Py_ssize_t vec_length(PyObject *o) {
    return ((VecTObject *)o)->vec.len;
}

static PyMappingMethods VecTMapping = {
    .mp_length = vec_length,
    .mp_subscript = vec_subscript,
};

static PySequenceMethods VecTSequence = {
    .sq_item = vec_get_item,
    .sq_ass_item = vec_ass_item,
    .sq_contains = vec_contains,
};

static PyMethodDef vec_methods[] = {
    {NULL, NULL, 0, NULL},  /* Sentinel */
};

// Iterator type for vec[T] (reference types)

typedef struct {
    PyObject_HEAD
    VecT vec;             // Unboxed vec (keeps buffer alive via items reference)
    Py_ssize_t index;     // Current iteration index
} VecTIterObject;

PyTypeObject VecTIterType;

static PyObject *VecT_iter(PyObject *self) {
    VecTIterObject *it = PyObject_GC_New(VecTIterObject, &VecTIterType);
    if (it == NULL)
        return NULL;
    it->vec = ((VecTObject *)self)->vec;
    VEC_INCREF(it->vec);
    it->index = 0;
    PyObject_GC_Track(it);
    return (PyObject *)it;
}

static int
VecTIter_traverse(VecTIterObject *self, visitproc visit, void *arg)
{
    if (self->vec.items)
        Py_VISIT(VEC_BUF(self->vec));
    return 0;
}

static int
VecTIter_clear(VecTIterObject *self)
{
    if (self->vec.items) {
        Py_DECREF(VEC_BUF(self->vec));
        self->vec.items = NULL;
    }
    return 0;
}

static void VecTIter_dealloc(VecTIterObject *self) {
    PyObject_GC_UnTrack(self);
    VEC_DECREF(self->vec);
    PyObject_GC_Del(self);
}

static PyObject *VecTIter_next(VecTIterObject *self) {
    if (self->vec.items == NULL)
        return NULL;
    if (self->index < self->vec.len) {
        PyObject *item = self->vec.items[self->index];
        self->index++;
        Py_INCREF(item);
        return item;
    }
    VEC_DECREF(self->vec);
    self->vec.items = NULL;
    return NULL;  // StopIteration
}

static PyObject *VecTIter_len(VecTIterObject *self, PyObject *Py_UNUSED(ignored)) {
    if (self->vec.items == NULL)
        return PyLong_FromSsize_t(0);
    Py_ssize_t remaining = self->vec.len - self->index;
    if (remaining < 0)
        remaining = 0;
    return PyLong_FromSsize_t(remaining);
}

static PyMethodDef VecTIter_methods[] = {
    {"__length_hint__", (PyCFunction)VecTIter_len, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL},
};

PyTypeObject VecTIterType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "vec_iterator",
    .tp_basicsize = sizeof(VecTIterObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
    .tp_traverse = (traverseproc)VecTIter_traverse,
    .tp_clear = (inquiry)VecTIter_clear,
    .tp_dealloc = (destructor)VecTIter_dealloc,
    .tp_iter = PyObject_SelfIter,
    .tp_iternext = (iternextfunc)VecTIter_next,
    .tp_methods = VecTIter_methods,
};

PyTypeObject VecTBufType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "vecbuf",
    .tp_doc = "Internal data buffer used by vec objects",
    .tp_basicsize = sizeof(VecTBufObject) - sizeof(PyObject *),
    .tp_itemsize = sizeof(PyObject *),
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
    .tp_traverse = (traverseproc)VecTBuf_traverse,
    //.tp_new = vecbuf_i64_new, //??
    .tp_free = PyObject_GC_Del,
    .tp_clear = (inquiry)VecTBuf_clear,
    .tp_dealloc = (destructor)VecTBuf_dealloc,
};

PyTypeObject VecTType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "vec",
    .tp_doc = "Mutable sequence-like container optimized for compilation with mypyc",
    .tp_basicsize = sizeof(VecTObject),
    .tp_itemsize = 0,
    .tp_base = &VecType,  // Inherit from base vec type for isinstance() support
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
    .tp_traverse = (traverseproc)VecT_traverse,
    .tp_clear = (inquiry)VecT_clear,
    .tp_dealloc = (destructor)VecT_dealloc,
    //.tp_free = PyObject_GC_Del,
    .tp_repr = (reprfunc)vec_repr,
    .tp_iter = VecT_iter,
    .tp_as_sequence = &VecTSequence,
    .tp_as_mapping = &VecTMapping,
    .tp_richcompare = vec_richcompare,
    .tp_methods = vec_methods,
    // TODO: free
};

static inline VecT vec_from_sequence(
        size_t item_type, PyObject *seq, int64_t cap, const int is_list) {
    Py_ssize_t n = is_list ? PyList_GET_SIZE(seq) : PyTuple_GET_SIZE(seq);
    Py_ssize_t alloc_size = n > cap ? n : cap;
    VecT v = vec_alloc(alloc_size, item_type);
    if (VEC_IS_ERROR(v))
        return vec_error();
#ifdef Py_GIL_DISABLED
    if (is_list) {
        // Other threads could be mutating the list concurrently, so use strong references
        Py_ssize_t actual = 0;
        for (Py_ssize_t i = 0; i < PyList_GET_SIZE(seq) && i < n; i++) {
            PyObject *item = PyList_GetItemRef(seq, i);
            if (unlikely(item == NULL)) {
                // Race condition: list shrank between size read and get item
                for (Py_ssize_t j = actual; j < alloc_size; j++)
                    v.items[j] = NULL;
                VEC_DECREF(v);
                return vec_error();
            }
            if (!VecT_ItemCheck(v, item, item_type)) {
                Py_DECREF(item);
                for (Py_ssize_t j = actual; j < alloc_size; j++)
                    v.items[j] = NULL;
                VEC_DECREF(v);
                return vec_error();
            }
            v.items[actual++] = item;
        }
        for (Py_ssize_t j = actual; j < alloc_size; j++)
            v.items[j] = NULL;
        vec_track_buffer(&v);
        v.len = actual;
        return v;
    }
    // Tuples are immutable, so fall through to the shared loop below
#endif
    for (Py_ssize_t i = 0; i < n; i++) {
        PyObject *item = is_list ? PyList_GET_ITEM(seq, i) : PyTuple_GET_ITEM(seq, i);
        if (!VecT_ItemCheck(v, item, item_type)) {
            for (Py_ssize_t j = i; j < alloc_size; j++)
                v.items[j] = NULL;
            VEC_DECREF(v);
            return vec_error();
        }
        Py_INCREF(item);
        v.items[i] = item;
    }
    for (Py_ssize_t j = n; j < alloc_size; j++)
        v.items[j] = NULL;
    vec_track_buffer(&v);
    v.len = n;
    return v;
}

VecT VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap) {
    if (PyList_CheckExact(iterable)) {
        return vec_from_sequence(item_type, iterable, cap, 1);
    } else if (PyTuple_CheckExact(iterable)) {
        return vec_from_sequence(item_type, iterable, cap, 0);
    }

    VecT v = vec_alloc(cap, item_type);
    if (VEC_IS_ERROR(v))
        return vec_error();
    if (cap > 0) {
        for (int64_t i = 0; i < cap; i++)
            v.items[i] = NULL;
    }
    v.len = 0;
    vec_track_buffer(&v);

    PyObject *iter = PyObject_GetIter(iterable);
    if (iter == NULL) {
        VEC_DECREF(v);
        return vec_error();
    }
    PyObject *item;
    while ((item = PyIter_Next(iter)) != NULL) {
        if (!VecT_ItemCheck(v, item, item_type)) {
            Py_DECREF(iter);
            VEC_DECREF(v);
            Py_DECREF(item);
            return vec_error();
        }
        v = VecT_Append(v, item, item_type);
        Py_DECREF(item);
        if (VEC_IS_ERROR(v)) {
            Py_DECREF(iter);
            VEC_DECREF(v);
            return vec_error();
        }
    }
    Py_DECREF(iter);
    if (PyErr_Occurred()) {
        VEC_DECREF(v);
        return vec_error();
    }
    return v;
}

VecTAPI Vec_TAPI = {
    &VecTType,
    &VecTBufType,
    VecT_New,
    VecT_Box,
    VecT_Unbox,
    VecT_ConvertFromNested,
    VecT_Append,
    VecT_Pop,
    VecT_Remove,
    VecT_Slice,
    VecT_FromIterable,
    VecT_Extend,
    VecT_ExtendVec,
    VecT_ToList,
    VecT_ToTuple,
};
