#include "pythoncapi_compat.h"

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdint.h>
#include "CPy.h"
#include "librt_strings.h"

#define CPY_BOOL_ERROR 2

//
// BytesWriter
//

#define _WRITE_BYTES(data, type, v) \
    do { \
       *(type *)(((BytesWriterObject *)data)->buf + ((BytesWriterObject *)data)->len) = v; \
       ((BytesWriterObject *)data)->len += sizeof(type); \
    } while (0)

static PyTypeObject BytesWriterType;

static bool
_grow_buffer(BytesWriterObject *data, Py_ssize_t n) {
    if (unlikely(n > PY_SSIZE_T_MAX - data->len)) {
        PyErr_NoMemory();
        return false;
    }
    Py_ssize_t target = data->len + n;
    Py_ssize_t size = data->capacity;
    do {
        if (unlikely(size > PY_SSIZE_T_MAX / 2)) {
            PyErr_NoMemory();
            return false;
        }
        size *= 2;
    } while (target >= size);
    char *new_buf;
    if (data->buf == data->data) {
        // Move from embedded buffer to heap-allocated buffer
        new_buf = PyMem_Malloc(size);
        if (new_buf != NULL) {
            memcpy(new_buf, data->data, data->len);
        }
    } else {
        new_buf = PyMem_Realloc(data->buf, size);
    }
    if (unlikely(new_buf == NULL)) {
        PyErr_NoMemory();
        return false;
    }
    data->buf = new_buf;
    data->capacity = size;
    return true;
}

static inline bool
ensure_bytes_writer_size(BytesWriterObject *data, Py_ssize_t n) {
    if (likely(data->capacity - data->len >= n)) {
        return true;
    } else {
        return _grow_buffer(data, n);
    }
}

static inline void
BytesWriter_init_internal(BytesWriterObject *self) {
    self->buf = self->data;
    self->len = 0;
    self->capacity = WRITER_EMBEDDED_BUF_LEN;
}

static PyObject*
BytesWriter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    if (type != &BytesWriterType) {
        PyErr_SetString(PyExc_TypeError, "BytesWriter cannot be subclassed");
        return NULL;
    }

    BytesWriterObject *self = (BytesWriterObject *)type->tp_alloc(type, 0);
    if (self != NULL)
        BytesWriter_init_internal(self);
    return (PyObject *)self;
}

static PyObject *
BytesWriter_internal(void) {
    BytesWriterObject *self = (BytesWriterObject *)BytesWriterType.tp_alloc(&BytesWriterType, 0);
    if (self == NULL)
        return NULL;
    BytesWriter_init_internal(self);
    return (PyObject *)self;
}

static int
BytesWriter_init(BytesWriterObject *self, PyObject *args, PyObject *kwds)
{
    if (!PyArg_ParseTuple(args, "")) {
        return -1;
    }

    if (kwds != NULL && PyDict_Size(kwds) > 0) {
        PyErr_SetString(PyExc_TypeError,
                        "BytesWriter() takes no keyword arguments");
        return -1;
    }

    // Soft reset: free any heap buffer so re-initialization doesn't leak.
    if (self->buf != self->data && self->buf != NULL)
        PyMem_Free(self->buf);
    BytesWriter_init_internal(self);
    return 0;
}

static void
BytesWriter_dealloc(BytesWriterObject *self)
{
    if (self->buf != self->data) {
        PyMem_Free(self->buf);
        self->buf = NULL;
    }
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject*
BytesWriter_getvalue_internal(PyObject *self)
{
    BytesWriterObject *obj = (BytesWriterObject *)self;
    return PyBytes_FromStringAndSize(obj->buf, obj->len);
}

static PyObject*
BytesWriter_repr(BytesWriterObject *self)
{
    PyObject *value = BytesWriter_getvalue_internal((PyObject *)self);
    if (value == NULL) {
        return NULL;
    }
    PyObject *value_repr = PyObject_Repr(value);
    Py_DECREF(value);
    if (value_repr == NULL) {
        return NULL;
    }
    PyObject *result = PyUnicode_FromFormat("BytesWriter(%U)", value_repr);
    Py_DECREF(value_repr);
    return result;
}

static PyObject*
BytesWriter_getvalue(BytesWriterObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyBytes_FromStringAndSize(self->buf, self->len);
}

static Py_ssize_t
BytesWriter_length(BytesWriterObject *self)
{
    return self->len;
}

static PyObject*
BytesWriter_item(BytesWriterObject *self, Py_ssize_t index)
{
    Py_ssize_t length = self->len;

    // Check bounds
    if (index < 0 || index >= length) {
        PyErr_SetString(PyExc_IndexError, "BytesWriter index out of range");
        return NULL;
    }

    // Return the byte at the given index as a Python int
    return PyLong_FromLong((unsigned char)self->buf[index]);
}

static int
BytesWriter_ass_item(BytesWriterObject *self, Py_ssize_t index, PyObject *value)
{
    Py_ssize_t length = self->len;

    // Check bounds
    if (index < 0 || index >= length) {
        PyErr_SetString(PyExc_IndexError, "BytesWriter index out of range");
        return -1;
    }

    // Check that value is not NULL (deletion not supported)
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "BytesWriter does not support item deletion");
        return -1;
    }

    // Convert value to uint8
    uint8_t byte_value = CPyLong_AsUInt8(value);
    if (unlikely(byte_value == CPY_LL_UINT_ERROR && PyErr_Occurred())) {
        CPy_TypeError("u8", value);
        return -1;
    }

    // Assign the byte
    self->buf[index] = (char)byte_value;
    return 0;
}

static PySequenceMethods BytesWriter_as_sequence = {
    .sq_length = (lenfunc)BytesWriter_length,
    .sq_item = (ssizeargfunc)BytesWriter_item,
    .sq_ass_item = (ssizeobjargproc)BytesWriter_ass_item,
};

static PyObject* BytesWriter_append(PyObject *self, PyObject *const *args, size_t nargs);
static PyObject* BytesWriter_write(PyObject *self, PyObject *const *args, size_t nargs);
static PyObject* BytesWriter_truncate(PyObject *self, PyObject *const *args, size_t nargs);

static PyMethodDef BytesWriter_methods[] = {
    {"append", (PyCFunction) BytesWriter_append, METH_FASTCALL,
     PyDoc_STR("Append a single byte to the buffer")
    },
    {"write", (PyCFunction) BytesWriter_write, METH_FASTCALL,
     PyDoc_STR("Append bytes to the buffer")
    },
    {"getvalue", (PyCFunction) BytesWriter_getvalue, METH_NOARGS,
     "Return the buffer content as bytes object"
    },
    {"truncate", (PyCFunction) BytesWriter_truncate, METH_FASTCALL,
     PyDoc_STR("Truncate the buffer to the specified size")
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject BytesWriterType = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "BytesWriter",
    .tp_doc = PyDoc_STR("Memory buffer for building bytes objects from parts"),
    .tp_basicsize = sizeof(BytesWriterObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = BytesWriter_new,
    .tp_init = (initproc) BytesWriter_init,
    .tp_dealloc = (destructor) BytesWriter_dealloc,
    .tp_methods = BytesWriter_methods,
    .tp_as_sequence = &BytesWriter_as_sequence,
    .tp_repr = (reprfunc)BytesWriter_repr,
};

static inline bool
check_bytes_writer(PyObject *data) {
    if (unlikely(Py_TYPE(data) != &BytesWriterType)) {
        PyErr_Format(
            PyExc_TypeError, "data must be a BytesWriter object, got %s", Py_TYPE(data)->tp_name
        );
        return false;
    }
    return true;
}

static char
BytesWriter_write_internal(BytesWriterObject *self, PyObject *value) {
    const char *data;
    Py_ssize_t size;
    if (likely(PyBytes_Check(value))) {
        data = PyBytes_AS_STRING(value);
        size = PyBytes_GET_SIZE(value);
    } else {
        data = PyByteArray_AS_STRING(value);
        size = PyByteArray_GET_SIZE(value);
    }
    // Write bytes content.
    if (!ensure_bytes_writer_size(self, size))
        return CPY_NONE_ERROR;
    memcpy(self->buf + self->len, data, size);
    self->len += size;
    return CPY_NONE;
}

static PyObject*
BytesWriter_write(PyObject *self, PyObject *const *args, size_t nargs) {
    if (unlikely(nargs != 1)) {
        PyErr_Format(PyExc_TypeError,
                     "write() takes exactly 1 argument (%zu given)", nargs);
        return NULL;
    }
    if (!check_bytes_writer(self)) {
        return NULL;
    }
    PyObject *value = args[0];
    if (unlikely(!PyBytes_Check(value) && !PyByteArray_Check(value))) {
        PyErr_SetString(PyExc_TypeError, "value must be a bytes or bytearray object");
        return NULL;
    }
    if (unlikely(BytesWriter_write_internal((BytesWriterObject *)self, value) == CPY_NONE_ERROR)) {
        return NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static inline char
BytesWriter_append_internal(BytesWriterObject *self, uint8_t value) {
    if (!ensure_bytes_writer_size(self, 1))
        return CPY_NONE_ERROR;
    _WRITE_BYTES(self, uint8_t, value);
    return CPY_NONE;
}

static PyObject*
BytesWriter_append(PyObject *self, PyObject *const *args, size_t nargs) {
    if (unlikely(nargs != 1)) {
        PyErr_Format(PyExc_TypeError,
                     "append() takes exactly 1 argument (%zu given)", nargs);
        return NULL;
    }
    if (!check_bytes_writer(self)) {
        return NULL;
    }
    PyObject *value = args[0];
    uint8_t unboxed = CPyLong_AsUInt8(value);
    if (unlikely(unboxed == CPY_LL_UINT_ERROR && PyErr_Occurred())) {
        CPy_TypeError("u8", value);
        return NULL;
    }
    if (unlikely(BytesWriter_append_internal((BytesWriterObject *)self, unboxed) == CPY_NONE_ERROR)) {
        return NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static char
BytesWriter_truncate_internal(PyObject *self, int64_t size) {
    BytesWriterObject *writer = (BytesWriterObject *)self;
    Py_ssize_t current_size = writer->len;

    // Validate size is non-negative
    if (size < 0) {
        PyErr_SetString(PyExc_ValueError, "size must be non-negative");
        return CPY_NONE_ERROR;
    }

    // Validate size doesn't exceed current size
    if (size > current_size) {
        PyErr_SetString(PyExc_ValueError, "size cannot be larger than current buffer size");
        return CPY_NONE_ERROR;
    }

    writer->len = size;
    return CPY_NONE;
}

static PyObject*
BytesWriter_truncate(PyObject *self, PyObject *const *args, size_t nargs) {
    if (unlikely(nargs != 1)) {
        PyErr_Format(PyExc_TypeError,
                     "truncate() takes exactly 1 argument (%zu given)", nargs);
        return NULL;
    }
    if (!check_bytes_writer(self)) {
        return NULL;
    }

    PyObject *size_obj = args[0];
    int overflow;
    long long size = PyLong_AsLongLongAndOverflow(size_obj, &overflow);

    if (size == -1 && PyErr_Occurred()) {
        return NULL;
    }
    if (overflow != 0) {
        PyErr_SetString(PyExc_ValueError, "integer out of range");
        return NULL;
    }

    if (unlikely(BytesWriter_truncate_internal(self, size) == CPY_NONE_ERROR)) {
        return NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static PyTypeObject *
BytesWriter_type_internal(void) {
    return &BytesWriterType;  // Return borrowed reference
};

static CPyTagged
BytesWriter_len_internal(PyObject *self) {
    BytesWriterObject *writer = (BytesWriterObject *)self;
    return writer->len << 1;
}

//
// StringWriter
//

static PyTypeObject StringWriterType;

static void convert_string_data_in_place(char *buf, Py_ssize_t len,
                                         char old_kind, char new_kind);

// Helper to grow string buffer and optionally convert to new kind
// Returns true on success, false on failure (with PyErr set)
// Updates self->buf, self->capacity, and self->kind
static bool
grow_string_buffer_helper(StringWriterObject *self, Py_ssize_t target_capacity, char new_kind) {
    char old_kind = self->kind;
    Py_ssize_t new_capacity = self->capacity;

    // Limit so that (new_capacity * 2) * new_kind stays within Py_ssize_t.
    // new_kind is always 1, 2, or 4, so use a shift instead of division.
    int shift = (new_kind == 4) ? 3 : (new_kind == 2 ? 2 : 1);
    Py_ssize_t cap_limit = PY_SSIZE_T_MAX >> shift;
    while (target_capacity >= new_capacity) {
        if (unlikely(new_capacity > cap_limit)) {
            PyErr_NoMemory();
            return false;
        }
        new_capacity *= 2;
    }

    if (unlikely(new_capacity > cap_limit * 2)) {
        PyErr_NoMemory();
        return false;
    }

    Py_ssize_t size_bytes = new_capacity * new_kind;
    char *new_buf;
    bool from_embedded = (self->buf == self->data);

    if (from_embedded) {
        // Move from embedded buffer to heap-allocated buffer
        new_buf = PyMem_Malloc(size_bytes);
        if (new_buf != NULL) {
            // Copy existing data from embedded buffer
            memcpy(new_buf, self->data, self->len * old_kind);
        }
    } else {
        // Realloc existing heap buffer
        new_buf = PyMem_Realloc(self->buf, size_bytes);
    }

    if (unlikely(new_buf == NULL)) {
        PyErr_NoMemory();
        return false;
    }

    // Convert data if kind changed
    if (old_kind != new_kind) {
        convert_string_data_in_place(new_buf, self->len, old_kind, new_kind);
    }

    self->buf = new_buf;
    self->capacity = new_capacity;
    self->kind = new_kind;
    return true;
}

static bool grow_string_buffer(StringWriterObject *data, Py_ssize_t n) {
    if (unlikely(n > PY_SSIZE_T_MAX - data->len)) {
        PyErr_NoMemory();
        return false;
    }
    return grow_string_buffer_helper(data, data->len + n, data->kind);
}

static inline bool
ensure_string_writer_size(StringWriterObject *data, Py_ssize_t n) {
    if (likely(data->capacity - data->len >= n)) {
        return true;
    } else {
        // Don't inline the grow function since this is slow path and we
        // want to keep this as short as possible for better inlining
        return grow_string_buffer(data, n);
    }
}

static inline void
StringWriter_init_internal(StringWriterObject *self) {
    self->buf = self->data;
    self->kind = 1;
    self->len = 0;
    self->capacity = WRITER_EMBEDDED_BUF_LEN;
}

static PyObject*
StringWriter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    if (type != &StringWriterType) {
        PyErr_SetString(PyExc_TypeError, "StringWriter cannot be subclassed");
        return NULL;
    }

    StringWriterObject *self = (StringWriterObject *)type->tp_alloc(type, 0);
    if (self != NULL)
        StringWriter_init_internal(self);
    return (PyObject *)self;
}

static PyObject *
StringWriter_internal(void) {
    StringWriterObject *self = (StringWriterObject *)StringWriterType.tp_alloc(&StringWriterType, 0);
    if (self == NULL)
        return NULL;
    StringWriter_init_internal(self);
    return (PyObject *)self;
}

static int
StringWriter_init(StringWriterObject *self, PyObject *args, PyObject *kwds)
{
    if (!PyArg_ParseTuple(args, "")) {
        return -1;
    }

    if (kwds != NULL && PyDict_Size(kwds) > 0) {
        PyErr_SetString(PyExc_TypeError,
                        "StringWriter() takes no keyword arguments");
        return -1;
    }

    // Soft reset: free any heap buffer so re-initialization doesn't leak.
    if (self->buf != self->data && self->buf != NULL)
        PyMem_Free(self->buf);
    StringWriter_init_internal(self);
    return 0;
}

static void
StringWriter_dealloc(StringWriterObject *self)
{
    if (self->buf != self->data) {
        PyMem_Free(self->buf);
        self->buf = NULL;
    }
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject*
StringWriter_getvalue_internal(PyObject *self)
{
    StringWriterObject *obj = (StringWriterObject *)self;
    return PyUnicode_FromKindAndData(obj->kind, obj->buf, obj->len);
}

static PyObject*
StringWriter_repr(StringWriterObject *self)
{
    PyObject *value = StringWriter_getvalue_internal((PyObject *)self);
    if (value == NULL) {
        return NULL;
    }
    PyObject *value_repr = PyObject_Repr(value);
    Py_DECREF(value);
    if (value_repr == NULL) {
        return NULL;
    }
    PyObject *result = PyUnicode_FromFormat("StringWriter(%U)", value_repr);
    Py_DECREF(value_repr);
    return result;
}

static PyObject*
StringWriter_getvalue(StringWriterObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromKindAndData(self->kind, self->buf, self->len);
}

static Py_ssize_t
StringWriter_length(StringWriterObject *self)
{
    return self->len;
}

static PyObject*
StringWriter_item(StringWriterObject *self, Py_ssize_t index)
{
    Py_ssize_t length = self->len;

    // Check bounds
    if (index < 0 || index >= length) {
        PyErr_SetString(PyExc_IndexError, "StringWriter index out of range");
        return NULL;
    }

    // Read the character at the given index based on kind using memcpy
    uint32_t value;
    if (self->kind == 1) {
        uint8_t val;
        memcpy(&val, self->buf + index, 1);
        value = val;
    } else if (self->kind == 2) {
        uint16_t val;
        memcpy(&val, self->buf + index * 2, 2);
        value = val;
    } else {
        memcpy(&value, self->buf + index * 4, 4);
    }
    return PyLong_FromLong(value);
}

static PySequenceMethods StringWriter_as_sequence = {
    .sq_length = (lenfunc)StringWriter_length,
    .sq_item = (ssizeargfunc)StringWriter_item,
};

static PyObject* StringWriter_append(PyObject *self, PyObject *const *args, size_t nargs);
static PyObject* StringWriter_write(PyObject *self, PyObject *const *args, size_t nargs);

static PyMethodDef StringWriter_methods[] = {
    {"append", (PyCFunction) StringWriter_append, METH_FASTCALL,
     PyDoc_STR("Append a single character (as int codepoint) to the buffer")
    },
    {"write", (PyCFunction) StringWriter_write, METH_FASTCALL,
     PyDoc_STR("Append a string to the buffer")
    },
    {"getvalue", (PyCFunction) StringWriter_getvalue, METH_NOARGS,
     "Return the buffer content as str object"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject StringWriterType = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "StringWriter",
    .tp_doc = PyDoc_STR("Memory buffer for building string objects from parts"),
    .tp_basicsize = sizeof(StringWriterObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = StringWriter_new,
    .tp_init = (initproc) StringWriter_init,
    .tp_dealloc = (destructor) StringWriter_dealloc,
    .tp_methods = StringWriter_methods,
    .tp_as_sequence = &StringWriter_as_sequence,
    .tp_repr = (reprfunc)StringWriter_repr,
};

static inline bool
check_string_writer(PyObject *data) {
    if (unlikely(Py_TYPE(data) != &StringWriterType)) {
        PyErr_Format(
            PyExc_TypeError, "data must be a StringWriter object, got %s", Py_TYPE(data)->tp_name
        );
        return false;
    }
    return true;
}

static char string_writer_switch_kind(StringWriterObject *self, int32_t value);

static char
StringWriter_write_internal(PyObject *obj, PyObject *value) {
    StringWriterObject *self = (StringWriterObject *)obj;
    Py_ssize_t str_len = PyUnicode_GET_LENGTH(value);
    if (str_len == 0) {
        return CPY_NONE;
    }

    int src_kind = PyUnicode_KIND(value);
    void *src_data = PyUnicode_DATA(value);
    int self_kind = self->kind;

    // Switch kind if source requires wider characters
    if (src_kind > self_kind) {
        // Use value in the source kind range to trigger proper kind switch
        int32_t codepoint = (src_kind == 2) ? 0x100 : 0x10000;
        if (string_writer_switch_kind(self, codepoint) == CPY_NONE_ERROR) {
            return CPY_NONE_ERROR;
        }
        self_kind = self->kind;
    }

    // Ensure we have enough space
    if (!ensure_string_writer_size(self, str_len)) {
        return CPY_NONE_ERROR;
    }

    // Copy data - ASCII/Latin1 (kind 1) are handled uniformly
    if (self_kind == src_kind) {
        // Same kind, direct copy
        memcpy(self->buf + self->len * self_kind, src_data, str_len * src_kind);
    } else {
        // Different kinds, convert character by character
        for (Py_ssize_t i = 0; i < str_len; i++) {
            Py_UCS4 ch = PyUnicode_READ(src_kind, src_data, i);
            PyUnicode_WRITE(self_kind, self->buf, self->len + i, ch);
        }
    }

    self->len += str_len;
    return CPY_NONE;
}

static PyObject*
StringWriter_write(PyObject *self, PyObject *const *args, size_t nargs) {
    if (unlikely(nargs != 1)) {
        PyErr_Format(PyExc_TypeError,
                     "write() takes exactly 1 argument (%zu given)", nargs);
        return NULL;
    }
    if (!check_string_writer(self)) {
        return NULL;
    }
    PyObject *value = args[0];
    if (unlikely(!PyUnicode_Check(value))) {
        PyErr_SetString(PyExc_TypeError, "value must be a str object");
        return NULL;
    }
    if (unlikely(StringWriter_write_internal(self, value) == CPY_NONE_ERROR)) {
        return NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

// Convert string data to next larger kind (1->2 or 2->4)
static void convert_string_data_in_place(char *buf, Py_ssize_t len,
                                         char old_kind, char new_kind) {
    if (old_kind == 1 && new_kind == 2) {
        // Convert backwards to avoid overwriting
        for (Py_ssize_t i = len - 1; i >= 0; i--) {
            uint8_t val = (uint8_t)buf[i];
            uint16_t expanded = val;
            memcpy(buf + i * 2, &expanded, 2);
        }
    } else if (old_kind == 2 && new_kind == 4) {
        // Convert backwards to avoid overwriting
        for (Py_ssize_t i = len - 1; i >= 0; i--) {
            uint16_t val;
            memcpy(&val, buf + i * 2, 2);
            uint32_t expanded = val;
            memcpy(buf + i * 4, &expanded, 4);
        }
    } else {
        assert(false);
    }
}

static char convert_string_buffer_kind(StringWriterObject *self, char old_kind, char new_kind) {
    // new_kind is always 2 or 4, so the max representable len is PY_SSIZE_T_MAX >> {1,2}.
    // Use a shift instead of a division for cheap overflow check.
    Py_ssize_t max_len = PY_SSIZE_T_MAX >> (new_kind == 4 ? 2 : 1);
    if (unlikely(self->len > max_len)) {
        PyErr_NoMemory();
        return CPY_NONE_ERROR;
    }
    // Current buffer size in bytes
    Py_ssize_t current_buf_size = (self->buf == self->data) ? WRITER_EMBEDDED_BUF_LEN : (self->capacity * old_kind);
    // Needed buffer size in bytes for new kind
    Py_ssize_t needed_size = self->len * new_kind;

    if (current_buf_size >= needed_size) {
        // Convert in place
        convert_string_data_in_place(self->buf, self->len, old_kind, new_kind);
        self->kind = new_kind;
        self->capacity = current_buf_size / new_kind;
    } else {
        // Need to allocate new buffer
        if (!grow_string_buffer_helper(self, self->len, new_kind)) {
            return CPY_NONE_ERROR;
        }
    }
    return CPY_NONE;
}

static char string_writer_switch_kind(StringWriterObject *self, int32_t value) {
    if (self->kind == 1) {
        // Either kind 1 -> 2 or 1 -> 4. First switch to kind 2.
        if (convert_string_buffer_kind(self, 1, 2) == CPY_NONE_ERROR)
            return CPY_NONE_ERROR;
        if ((uint32_t)value > 0xffff) {
            // Call recursively to switch from kind 2 to 4
            return string_writer_switch_kind(self, value);
        }
        return CPY_NONE;
    } else {
        // Must be kind 2 -> 4
        assert(self->kind == 2);
        assert((uint32_t)value > 0xffff);
        return convert_string_buffer_kind(self, 2, 4);
    }
}

// Handle all append cases except for append that stays within kind 1
static char string_append_slow_path(StringWriterObject *self, int32_t value) {
    if (self->kind == 2) {
        if ((uint32_t)value <= 0xffff) {
            // Fast path - kind 2 stays the same
            if (!ensure_string_writer_size(self, 1))
                return CPY_NONE_ERROR;
            // Copy 2-byte character to buffer
            uint16_t val16 = (uint16_t)value;
            memcpy(self->buf + self->len * 2, &val16, 2);
            self->len++;
            return CPY_NONE;
        }
        // Always validate code point range before promotion (but after fast path).
        if (unlikely((uint32_t)value > 0x10FFFF))
            goto fail_range;
        if (string_writer_switch_kind(self, value) == CPY_NONE_ERROR)
            return CPY_NONE_ERROR;
        return string_append_slow_path(self, value);
    } else if (self->kind == 1) {
        // Check precondition -- this must only be used on slow path
        assert((uint32_t)value > 0xff);
        if (unlikely((uint32_t)value > 0x10FFFF))
            goto fail_range;
        if (string_writer_switch_kind(self, value) == CPY_NONE_ERROR)
            return CPY_NONE_ERROR;
        return string_append_slow_path(self, value);
    }
    assert(self->kind == 4);
    if (unlikely((uint32_t)value > 0x10FFFF))
        goto fail_range;
    if (!ensure_string_writer_size(self, 1))
        return CPY_NONE_ERROR;
    // Copy 4-byte character to buffer
    uint32_t val32 = (uint32_t)value;
    memcpy(self->buf + self->len * 4, &val32, 4);
    self->len++;
    return CPY_NONE;

fail_range:
    PyErr_Format(PyExc_ValueError,
                "code point %d is outside valid Unicode range (0-1114111)", value);
    return CPY_NONE_ERROR;
}

static inline char
StringWriter_append_internal(StringWriterObject *self, int32_t value) {
    char kind = self->kind;
    if (kind == 1 && (uint32_t)value < 256) {
        if (!ensure_string_writer_size(self, 1))
            return CPY_NONE_ERROR;
        self->buf[self->len++] = value;
        self->kind = kind;
        return CPY_NONE;
    }
    return string_append_slow_path(self, value);
}

static PyObject*
StringWriter_append(PyObject *self, PyObject *const *args, size_t nargs) {
    if (unlikely(nargs != 1)) {
        PyErr_Format(PyExc_TypeError,
                     "append() takes exactly 1 argument (%zu given)", nargs);
        return NULL;
    }
    if (!check_string_writer(self)) {
        return NULL;
    }
    PyObject *value = args[0];
    int32_t unboxed = CPyLong_AsInt32(value);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) {
        CPy_TypeError("i32", value);
        return NULL;
    }
    if (unlikely(StringWriter_append_internal((StringWriterObject *)self, unboxed) == CPY_NONE_ERROR)) {
        return NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static PyTypeObject *
StringWriter_type_internal(void) {
    return &StringWriterType;  // Return borrowed reference
};

static CPyTagged
StringWriter_len_internal(PyObject *self) {
    StringWriterObject *writer = (StringWriterObject *)self;
    return writer->len << 1;
}

// End of StringWriter

// Helper for write_*_le/be functions - validates args and returns BytesWriter
static inline BytesWriterObject *
parse_write_args(PyObject *const *args, size_t nargs, const char *func_name) {
    if (unlikely(nargs != 2)) {
        PyErr_Format(PyExc_TypeError,
                     "%s() takes exactly 2 arguments (%zu given)", func_name, nargs);
        return NULL;
    }
    PyObject *writer = args[0];
    if (!check_bytes_writer(writer)) {
        return NULL;
    }
    return (BytesWriterObject *)writer;
}

// Helper for read_*_le/be functions - validates args and returns data pointer
// Returns NULL on error, sets *out_index to the validated index on success
static inline const unsigned char *
parse_read_args(PyObject *const *args, size_t nargs, const char *func_name,
                    Py_ssize_t num_bytes, int64_t *out_index) {
    if (unlikely(nargs != 2)) {
        PyErr_Format(PyExc_TypeError,
                     "%s() takes exactly 2 arguments (%zu given)", func_name, nargs);
        return NULL;
    }
    PyObject *bytes_obj = args[0];
    if (unlikely(!PyBytes_Check(bytes_obj))) {
        PyErr_Format(PyExc_TypeError, "%s() argument 1 must be bytes", func_name);
        return NULL;
    }
    int64_t index = CPyLong_AsInt64(args[1]);
    if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) {
        return NULL;
    }
    if (unlikely(index < 0)) {
        PyErr_SetString(PyExc_ValueError, "index must be non-negative");
        return NULL;
    }
    Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj);
    if (unlikely(index > size - num_bytes)) {
        PyErr_Format(PyExc_IndexError,
                     "index %lld out of range for bytes of length %zd",
                     (long long)index, size);
        return NULL;
    }
    *out_index = index;
    return (const unsigned char *)PyBytes_AS_STRING(bytes_obj);
}

static PyObject*
write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_le");
    if (bw == NULL)
        return NULL;
    int16_t unboxed = CPyLong_AsInt16(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 2)))
        return NULL;
    BytesWriter_WriteI16LEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
write_i16_be(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_be");
    if (bw == NULL)
        return NULL;
    int16_t unboxed = CPyLong_AsInt16(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 2)))
        return NULL;
    BytesWriter_WriteI16BEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i16_le", 2, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLong(CPyBytes_ReadI16LEUnsafe(data + index));
}

static PyObject*
read_i16_be(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i16_be", 2, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLong(CPyBytes_ReadI16BEUnsafe(data + index));
}

static PyObject*
write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_le");
    if (bw == NULL)
        return NULL;
    int32_t unboxed = CPyLong_AsInt32(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 4)))
        return NULL;
    BytesWriter_WriteI32LEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
write_i32_be(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_be");
    if (bw == NULL)
        return NULL;
    int32_t unboxed = CPyLong_AsInt32(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 4)))
        return NULL;
    BytesWriter_WriteI32BEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i32_le", 4, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLong(CPyBytes_ReadI32LEUnsafe(data + index));
}

static PyObject*
read_i32_be(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i32_be", 4, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLong(CPyBytes_ReadI32BEUnsafe(data + index));
}

static PyObject*
write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_le");
    if (bw == NULL)
        return NULL;
    int64_t unboxed = CPyLong_AsInt64(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 8)))
        return NULL;
    BytesWriter_WriteI64LEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
write_i64_be(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_be");
    if (bw == NULL)
        return NULL;
    int64_t unboxed = CPyLong_AsInt64(args[1]);
    if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 8)))
        return NULL;
    BytesWriter_WriteI64BEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i64_le", 8, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index));
}

static PyObject*
read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_i64_be", 8, &index);
    if (data == NULL)
        return NULL;
    return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index));
}

static PyObject*
write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_le");
    if (bw == NULL)
        return NULL;
    double unboxed = PyFloat_AsDouble(args[1]);
    if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 4)))
        return NULL;
    BytesWriter_WriteF32LEUnsafe(bw, (float)unboxed);
    Py_RETURN_NONE;
}

static PyObject*
write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_be");
    if (bw == NULL)
        return NULL;
    double unboxed = PyFloat_AsDouble(args[1]);
    if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 4)))
        return NULL;
    BytesWriter_WriteF32BEUnsafe(bw, (float)unboxed);
    Py_RETURN_NONE;
}

static PyObject*
read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_f32_le", 4, &index);
    if (data == NULL)
        return NULL;
    return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index));
}

static PyObject*
read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_f32_be", 4, &index);
    if (data == NULL)
        return NULL;
    return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index));
}

static PyObject*
write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_le");
    if (bw == NULL)
        return NULL;
    double unboxed = PyFloat_AsDouble(args[1]);
    if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 8)))
        return NULL;
    BytesWriter_WriteF64LEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) {
    BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_be");
    if (bw == NULL)
        return NULL;
    double unboxed = PyFloat_AsDouble(args[1]);
    if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
        return NULL;
    if (unlikely(!ensure_bytes_writer_size(bw, 8)))
        return NULL;
    BytesWriter_WriteF64BEUnsafe(bw, unboxed);
    Py_RETURN_NONE;
}

static PyObject*
read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_f64_le", 8, &index);
    if (data == NULL)
        return NULL;
    return PyFloat_FromDouble(CPyBytes_ReadF64LEUnsafe(data + index));
}

static PyObject*
read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) {
    int64_t index;
    const unsigned char *data = parse_read_args(args, nargs, "read_f64_be", 8, &index);
    if (data == NULL)
        return NULL;
    return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index));
}

// Python-level wrappers (`cp_*`) for interpreted callers. The C-side names
// are prefixed `cp_` to avoid colliding with libc's <ctype.h> isspace etc.
// The LibRTStrings_Is* helpers themselves are static inline in librt_strings.h
// so they compile directly into mypyc-emitted code with no capsule
// indirection.

// Parse a Python int as i32 codepoint. Returns 0 on success and writes
// the value to *out; returns -1 on error with a Python exception set.
static int
cp_parse_i32(PyObject *arg, int32_t *out) {
    int overflow;
    long c = PyLong_AsLongAndOverflow(arg, &overflow);
    if (c == -1 && PyErr_Occurred())
        return -1;
    if (overflow != 0 || c < INT32_MIN || c > INT32_MAX) {
        PyErr_SetString(PyExc_OverflowError,
                        "codepoint out of i32 range");
        return -1;
    }
    *out = (int32_t)c;
    return 0;
}

#define DEFINE_CP_BOOL_WRAPPER(name, fn)                                    \
    static PyObject*                                                        \
    cp_##name(PyObject *module, PyObject *arg) {                            \
        int32_t c;                                                          \
        if (cp_parse_i32(arg, &c) < 0)                                      \
            return NULL;                                                    \
        return PyBool_FromLong(fn(c));                                      \
    }

DEFINE_CP_BOOL_WRAPPER(isspace, LibRTStrings_IsSpace)
DEFINE_CP_BOOL_WRAPPER(isdigit, LibRTStrings_IsDigit)
DEFINE_CP_BOOL_WRAPPER(isalnum, LibRTStrings_IsAlnum)
DEFINE_CP_BOOL_WRAPPER(isalpha, LibRTStrings_IsAlpha)
DEFINE_CP_BOOL_WRAPPER(isidentifier, LibRTStrings_IsIdentifier)

#define DEFINE_CP_I32_WRAPPER(name, fn)                                     \
    static PyObject*                                                        \
    cp_##name(PyObject *module, PyObject *arg) {                            \
        int32_t c;                                                          \
        if (cp_parse_i32(arg, &c) < 0)                                      \
            return NULL;                                                    \
        return PyLong_FromLong((long) fn(c));                               \
    }

DEFINE_CP_I32_WRAPPER(toupper, LibRTStrings_ToUpper)
DEFINE_CP_I32_WRAPPER(tolower, LibRTStrings_ToLower)

static PyMethodDef librt_strings_module_methods[] = {
    {"write_i16_le", (PyCFunction) write_i16_le, METH_FASTCALL,
     PyDoc_STR("Write a 16-bit signed integer to BytesWriter in little-endian format")
    },
    {"write_i16_be", (PyCFunction) write_i16_be, METH_FASTCALL,
     PyDoc_STR("Write a 16-bit signed integer to BytesWriter in big-endian format")
    },
    {"read_i16_le", (PyCFunction) read_i16_le, METH_FASTCALL,
     PyDoc_STR("Read a 16-bit signed integer from bytes in little-endian format")
    },
    {"read_i16_be", (PyCFunction) read_i16_be, METH_FASTCALL,
     PyDoc_STR("Read a 16-bit signed integer from bytes in big-endian format")
    },
    {"write_i32_le", (PyCFunction) write_i32_le, METH_FASTCALL,
     PyDoc_STR("Write a 32-bit signed integer to BytesWriter in little-endian format")
    },
    {"write_i32_be", (PyCFunction) write_i32_be, METH_FASTCALL,
     PyDoc_STR("Write a 32-bit signed integer to BytesWriter in big-endian format")
    },
    {"read_i32_le", (PyCFunction) read_i32_le, METH_FASTCALL,
     PyDoc_STR("Read a 32-bit signed integer from bytes in little-endian format")
    },
    {"read_i32_be", (PyCFunction) read_i32_be, METH_FASTCALL,
     PyDoc_STR("Read a 32-bit signed integer from bytes in big-endian format")
    },
    {"write_i64_le", (PyCFunction) write_i64_le, METH_FASTCALL,
     PyDoc_STR("Write a 64-bit signed integer to BytesWriter in little-endian format")
    },
    {"write_i64_be", (PyCFunction) write_i64_be, METH_FASTCALL,
     PyDoc_STR("Write a 64-bit signed integer to BytesWriter in big-endian format")
    },
    {"read_i64_le", (PyCFunction) read_i64_le, METH_FASTCALL,
     PyDoc_STR("Read a 64-bit signed integer from bytes in little-endian format")
    },
    {"read_i64_be", (PyCFunction) read_i64_be, METH_FASTCALL,
     PyDoc_STR("Read a 64-bit signed integer from bytes in big-endian format")
    },
    {"write_f32_le", (PyCFunction) write_f32_le, METH_FASTCALL,
     PyDoc_STR("Write a 32-bit float to BytesWriter in little-endian format")
    },
    {"write_f32_be", (PyCFunction) write_f32_be, METH_FASTCALL,
     PyDoc_STR("Write a 32-bit float to BytesWriter in big-endian format")
    },
    {"read_f32_le", (PyCFunction) read_f32_le, METH_FASTCALL,
     PyDoc_STR("Read a 32-bit float from bytes in little-endian format")
    },
    {"read_f32_be", (PyCFunction) read_f32_be, METH_FASTCALL,
     PyDoc_STR("Read a 32-bit float from bytes in big-endian format")
    },
    {"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL,
     PyDoc_STR("Write a 64-bit float to BytesWriter in little-endian format")
    },
    {"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL,
     PyDoc_STR("Write a 64-bit float to BytesWriter in big-endian format")
    },
    {"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL,
     PyDoc_STR("Read a 64-bit float from bytes in little-endian format")
    },
    {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL,
     PyDoc_STR("Read a 64-bit float from bytes in big-endian format")
    },
    {"isspace", cp_isspace, METH_O,
     PyDoc_STR("Test whether a codepoint (i32) is Unicode whitespace.")
    },
    {"isdigit", cp_isdigit, METH_O,
     PyDoc_STR("Test whether a codepoint (i32) is a Unicode digit.")
    },
    {"isalnum", cp_isalnum, METH_O,
     PyDoc_STR("Test whether a codepoint (i32) is alphanumeric.")
    },
    {"isalpha", cp_isalpha, METH_O,
     PyDoc_STR("Test whether a codepoint (i32) is a Unicode letter.")
    },
    {"isidentifier", cp_isidentifier, METH_O,
     PyDoc_STR("Test whether a codepoint (i32) is a valid identifier start (XID_Start).")
    },
    {"toupper", cp_toupper, METH_O,
     PyDoc_STR("Single-codepoint uppercase mapping for a codepoint (i32). Returns the input unchanged if the Unicode uppercase expands to multiple codepoints (e.g. U+00DF uppercases to \"SS\"); use str.upper() for full Unicode case conversion.")
    },
    {"tolower", cp_tolower, METH_O,
     PyDoc_STR("Single-codepoint lowercase mapping for a codepoint (i32). Returns the input unchanged if the Unicode lowercase expands to multiple codepoints; use str.lower() for full Unicode case conversion.")
    },
    {NULL, NULL, 0, NULL}
};

static int
strings_abi_version(void) {
    return LIBRT_STRINGS_ABI_VERSION;
}

static int
strings_api_version(void) {
    return LIBRT_STRINGS_API_VERSION;
}

static int
librt_strings_module_exec(PyObject *m)
{
    if (PyType_Ready(&BytesWriterType) < 0) {
        return -1;
    }
    if (PyType_Ready(&StringWriterType) < 0) {
        return -1;
    }
    if (PyModule_AddObjectRef(m, "BytesWriter", (PyObject *) &BytesWriterType) < 0) {
        return -1;
    }
    if (PyModule_AddObjectRef(m, "StringWriter", (PyObject *) &StringWriterType) < 0) {
        return -1;
    }

    // Export mypy internal C API, be careful with the order!
    static void *librt_strings_api[LIBRT_STRINGS_API_LEN] = {
        (void *)strings_abi_version,
        (void *)strings_api_version,
        (void *)BytesWriter_internal,
        (void *)BytesWriter_getvalue_internal,
        (void *)BytesWriter_append_internal,
        (void *)_grow_buffer,
        (void *)BytesWriter_type_internal,
        (void *)BytesWriter_truncate_internal,
        (void *)StringWriter_internal,
        (void *)StringWriter_getvalue_internal,
        (void *)string_append_slow_path,
        (void *)StringWriter_type_internal,
        (void *)StringWriter_write_internal,
        (void *)grow_string_buffer,
    };
    PyObject *c_api_object = PyCapsule_New((void *)librt_strings_api, "librt.strings._C_API", NULL);
    if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
        return -1;
    }
    return 0;
}

static PyModuleDef_Slot librt_strings_module_slots[] = {
    {Py_mod_exec, librt_strings_module_exec},
#ifdef Py_MOD_GIL_NOT_USED
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
    {0, NULL}
};

static PyModuleDef librt_strings_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "strings",
    .m_doc = "Utilities for working with str and bytes objects",
    .m_size = 0,
    .m_methods = librt_strings_module_methods,
    .m_slots = librt_strings_module_slots,
};

PyMODINIT_FUNC
PyInit_strings(void)
{
    intern_strings();
    return PyModuleDef_Init(&librt_strings_module);
}
