// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!

@file:Suppress("NAME_SHADOWING")

package uniffi.cera_ffi

// Common helper code.
//
// Ideally this would live in a separate .kt file where it can be unittested etc
// in isolation, and perhaps even published as a re-useable package.
//
// However, it's important that the details of how this helper code works (e.g. the
// way that different builtin types are passed across the FFI) exactly match what's
// expected by the Rust code on the other side of the interface. In practice right
// now that means coming from the exact some version of `uniffi` that was used to
// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
// helpers directly inline like we're doing here.

import com.sun.jna.Callback
import com.sun.jna.IntegerType
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.Structure
import com.sun.jna.ptr.*
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import kotlin.coroutines.resume

// This is a helper for safely working with byte buffers returned from the Rust code.
// A rust-owned buffer is represented by its capacity, its current length, and a
// pointer to the underlying data.

/**
 * @suppress
 */
@Structure.FieldOrder("capacity", "len", "data")
open class RustBuffer : Structure() {
    // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values.
    // When dealing with these fields, make sure to call `toULong()`.
    @JvmField var capacity: Long = 0

    @JvmField var len: Long = 0

    @JvmField var data: Pointer? = null

    class ByValue :
        RustBuffer(),
        Structure.ByValue

    class ByReference :
        RustBuffer(),
        Structure.ByReference

    internal fun setValue(other: RustBuffer) {
        capacity = other.capacity
        len = other.len
        data = other.data
    }

    companion object {
        internal fun alloc(size: ULong = 0UL) =
            uniffiRustCall { status ->
                // Note: need to convert the size to a `Long` value to make this work with JVM.
                UniffiLib.ffi_cera_ffi_rustbuffer_alloc(size.toLong(), status)
            }.also {
                if (it.data == null) {
                    throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=$size)")
                }
            }

        internal fun create(
            capacity: ULong,
            len: ULong,
            data: Pointer?,
        ): RustBuffer.ByValue {
            var buf = RustBuffer.ByValue()
            buf.capacity = capacity.toLong()
            buf.len = len.toLong()
            buf.data = data
            return buf
        }

        internal fun free(buf: RustBuffer.ByValue) =
            uniffiRustCall { status ->
                UniffiLib.ffi_cera_ffi_rustbuffer_free(buf, status)
            }
    }

    @Suppress("TooGenericExceptionThrown")
    fun asByteBuffer() =
        this.data?.getByteBuffer(0, this.len)?.also {
            it.order(ByteOrder.BIG_ENDIAN)
        }
}

// This is a helper for safely passing byte references into the rust code.
// It's not actually used at the moment, because there aren't many things that you
// can take a direct pointer to in the JVM, and if we're going to copy something
// then we might as well copy it into a `RustBuffer`. But it's here for API
// completeness.

@Structure.FieldOrder("len", "data")
internal open class ForeignBytes : Structure() {
    @JvmField var len: Int = 0

    @JvmField var data: Pointer? = null

    class ByValue :
        ForeignBytes(),
        Structure.ByValue
}

/**
 * The FfiConverter interface handles converter types to and from the FFI
 *
 * All implementing objects should be public to support external types.  When a
 * type is external we need to import it's FfiConverter.
 *
 * @suppress
 */
public interface FfiConverter<KotlinType, FfiType> {
    // Convert an FFI type to a Kotlin type
    fun lift(value: FfiType): KotlinType

    // Convert an Kotlin type to an FFI type
    fun lower(value: KotlinType): FfiType

    // Read a Kotlin type from a `ByteBuffer`
    fun read(buf: ByteBuffer): KotlinType

    // Calculate bytes to allocate when creating a `RustBuffer`
    //
    // This must return at least as many bytes as the write() function will
    // write. It can return more bytes than needed, for example when writing
    // Strings we can't know the exact bytes needed until we the UTF-8
    // encoding, so we pessimistically allocate the largest size possible (3
    // bytes per codepoint).  Allocating extra bytes is not really a big deal
    // because the `RustBuffer` is short-lived.
    fun allocationSize(value: KotlinType): ULong

    // Write a Kotlin type to a `ByteBuffer`
    fun write(
        value: KotlinType,
        buf: ByteBuffer,
    )

    // Lower a value into a `RustBuffer`
    //
    // This method lowers a value into a `RustBuffer` rather than the normal
    // FfiType.  It's used by the callback interface code.  Callback interface
    // returns are always serialized into a `RustBuffer` regardless of their
    // normal FFI type.
    fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
        val rbuf = RustBuffer.alloc(allocationSize(value))
        try {
            val bbuf =
                rbuf.data!!.getByteBuffer(0, rbuf.capacity).also {
                    it.order(ByteOrder.BIG_ENDIAN)
                }
            write(value, bbuf)
            rbuf.writeField("len", bbuf.position().toLong())
            return rbuf
        } catch (e: Throwable) {
            RustBuffer.free(rbuf)
            throw e
        }
    }

    // Lift a value from a `RustBuffer`.
    //
    // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
    // It's currently only used by the `FfiConverterRustBuffer` class below.
    fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
        val byteBuf = rbuf.asByteBuffer()!!
        try {
            val item = read(byteBuf)
            if (byteBuf.hasRemaining()) {
                throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
            }
            return item
        } finally {
            RustBuffer.free(rbuf)
        }
    }
}

/**
 * FfiConverter that uses `RustBuffer` as the FfiType
 *
 * @suppress
 */
public interface FfiConverterRustBuffer<KotlinType> : FfiConverter<KotlinType, RustBuffer.ByValue> {
    override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)

    override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
}
// A handful of classes and functions to support the generated data structures.
// This would be a good candidate for isolating in its own ffi-support lib.

internal const val UNIFFI_CALL_SUCCESS = 0.toByte()
internal const val UNIFFI_CALL_ERROR = 1.toByte()
internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte()

@Structure.FieldOrder("code", "error_buf")
internal open class UniffiRustCallStatus : Structure() {
    @JvmField var code: Byte = 0

    @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()

    class ByValue :
        UniffiRustCallStatus(),
        Structure.ByValue

    fun isSuccess(): Boolean = code == UNIFFI_CALL_SUCCESS

    fun isError(): Boolean = code == UNIFFI_CALL_ERROR

    fun isPanic(): Boolean = code == UNIFFI_CALL_UNEXPECTED_ERROR

    companion object {
        fun create(
            code: Byte,
            errorBuf: RustBuffer.ByValue,
        ): UniffiRustCallStatus.ByValue {
            val callStatus = UniffiRustCallStatus.ByValue()
            callStatus.code = code
            callStatus.error_buf = errorBuf
            return callStatus
        }
    }
}

class InternalException(
    message: String,
) : kotlin.Exception(message)

/**
 * Each top-level error class has a companion object that can lift the error from the call status's rust buffer
 *
 * @suppress
 */
interface UniffiRustCallStatusErrorHandler<E> {
    fun lift(error_buf: RustBuffer.ByValue): E
}

// Helpers for calling Rust
// In practice we usually need to be synchronized to call this safely, so it doesn't
// synchronize itself

// Call a rust function that returns a Result<>.  Pass in the Error class companion that corresponds to the Err
private inline fun <U, E : kotlin.Exception> uniffiRustCallWithError(
    errorHandler: UniffiRustCallStatusErrorHandler<E>,
    callback: (UniffiRustCallStatus) -> U,
): U {
    var status = UniffiRustCallStatus()
    val return_value = callback(status)
    uniffiCheckCallStatus(errorHandler, status)
    return return_value
}

// Check UniffiRustCallStatus and throw an error if the call wasn't successful
private fun <E : kotlin.Exception> uniffiCheckCallStatus(
    errorHandler: UniffiRustCallStatusErrorHandler<E>,
    status: UniffiRustCallStatus,
) {
    if (status.isSuccess()) {
        return
    } else if (status.isError()) {
        throw errorHandler.lift(status.error_buf)
    } else if (status.isPanic()) {
        // when the rust code sees a panic, it tries to construct a rustbuffer
        // with the message.  but if that code panics, then it just sends back
        // an empty buffer.
        if (status.error_buf.len > 0) {
            throw InternalException(FfiConverterString.lift(status.error_buf))
        } else {
            throw InternalException("Rust panic")
        }
    } else {
        throw InternalException("Unknown rust call status: $status.code")
    }
}

/**
 * UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
 *
 * @suppress
 */
object UniffiNullRustCallStatusErrorHandler : UniffiRustCallStatusErrorHandler<InternalException> {
    override fun lift(error_buf: RustBuffer.ByValue): InternalException {
        RustBuffer.free(error_buf)
        return InternalException("Unexpected CALL_ERROR")
    }
}

// Call a rust function that returns a plain value
private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U =
    uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback)

internal inline fun <T> uniffiTraitInterfaceCall(
    callStatus: UniffiRustCallStatus,
    makeCall: () -> T,
    writeReturn: (T) -> Unit,
) {
    try {
        writeReturn(makeCall())
    } catch (e: kotlin.Exception) {
        val err =
            try {
                e.stackTraceToString()
            } catch (_: Throwable) {
                ""
            }
        callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
        callStatus.error_buf = FfiConverterString.lower(err)
    }
}

internal inline fun <T, reified E : Throwable> uniffiTraitInterfaceCallWithError(
    callStatus: UniffiRustCallStatus,
    makeCall: () -> T,
    writeReturn: (T) -> Unit,
    lowerError: (E) -> RustBuffer.ByValue,
) {
    try {
        writeReturn(makeCall())
    } catch (e: kotlin.Exception) {
        if (e is E) {
            callStatus.code = UNIFFI_CALL_ERROR
            callStatus.error_buf = lowerError(e)
        } else {
            val err =
                try {
                    e.stackTraceToString()
                } catch (_: Throwable) {
                    ""
                }
            callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
            callStatus.error_buf = FfiConverterString.lower(err)
        }
    }
}

// Initial value and increment amount for handles.
// These ensure that Kotlin-generated handles always have the lowest bit set
private const val UNIFFI_HANDLEMAP_INITIAL = 1.toLong()
private const val UNIFFI_HANDLEMAP_DELTA = 2.toLong()

// Map handles to objects
//
// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code.
internal class UniffiHandleMap<T : Any> {
    private val map = ConcurrentHashMap<Long, T>()

    // Start
    private val counter =
        java.util.concurrent.atomic
            .AtomicLong(UNIFFI_HANDLEMAP_INITIAL)

    val size: Int
        get() = map.size

    // Insert a new object into the handle map and get a handle for it
    fun insert(obj: T): Long {
        val handle = counter.getAndAdd(UNIFFI_HANDLEMAP_DELTA)
        map.put(handle, obj)
        return handle
    }

    // Clone a handle, creating a new one
    fun clone(handle: Long): Long {
        val obj = map.get(handle) ?: throw InternalException("UniffiHandleMap.clone: Invalid handle")
        return insert(obj)
    }

    // Get an object from the handle map
    fun get(handle: Long): T = map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle")

    // Remove an entry from the handlemap and get the Kotlin object back
    fun remove(handle: Long): T = map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle")
}

// Contains loading, initialization code,
// and the FFI Function declarations in a com.sun.jna.Library.
@Synchronized
private fun findLibraryName(componentName: String): String {
    val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
    if (libOverride != null) {
        return libOverride
    }
    return "cera_ffi"
}

// Define FFI callback types
internal interface UniffiRustFutureContinuationCallback : com.sun.jna.Callback {
    fun callback(
        `data`: Long,
        `pollResult`: Byte,
    )
}

internal interface UniffiForeignFutureDroppedCallback : com.sun.jna.Callback {
    fun callback(`handle`: Long)
}

internal interface UniffiCallbackInterfaceFree : com.sun.jna.Callback {
    fun callback(`handle`: Long)
}

internal interface UniffiCallbackInterfaceClone : com.sun.jna.Callback {
    fun callback(`handle`: Long): Long
}

@Structure.FieldOrder("handle", "free")
internal open class UniffiForeignFutureDroppedCallbackStruct(
    @JvmField internal var `handle`: Long = 0.toLong(),
    @JvmField internal var `free`: UniffiForeignFutureDroppedCallback? = null,
) : Structure() {
    class UniffiByValue(
        `handle`: Long = 0.toLong(),
        `free`: UniffiForeignFutureDroppedCallback? = null,
    ) : UniffiForeignFutureDroppedCallbackStruct(`handle`, `free`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureDroppedCallbackStruct) {
        `handle` = other.`handle`
        `free` = other.`free`
    }
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultU8(
    @JvmField internal var `returnValue`: Byte = 0.toByte(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Byte = 0.toByte(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultU8(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultU8) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteU8 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultU8.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultI8(
    @JvmField internal var `returnValue`: Byte = 0.toByte(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Byte = 0.toByte(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultI8(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultI8) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteI8 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultI8.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultU16(
    @JvmField internal var `returnValue`: Short = 0.toShort(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Short = 0.toShort(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultU16(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultU16) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteU16 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultU16.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultI16(
    @JvmField internal var `returnValue`: Short = 0.toShort(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Short = 0.toShort(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultI16(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultI16) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteI16 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultI16.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultU32(
    @JvmField internal var `returnValue`: Int = 0,
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Int = 0,
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultU32(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultU32) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteU32 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultU32.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultI32(
    @JvmField internal var `returnValue`: Int = 0,
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Int = 0,
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultI32(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultI32) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteI32 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultI32.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultU64(
    @JvmField internal var `returnValue`: Long = 0.toLong(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Long = 0.toLong(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultU64(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultU64) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteU64 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultU64.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultI64(
    @JvmField internal var `returnValue`: Long = 0.toLong(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Long = 0.toLong(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultI64(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultI64) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteI64 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultI64.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultF32(
    @JvmField internal var `returnValue`: Float = 0.0f,
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Float = 0.0f,
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultF32(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultF32) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteF32 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultF32.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultF64(
    @JvmField internal var `returnValue`: Double = 0.0,
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: Double = 0.0,
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultF64(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultF64) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteF64 : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultF64.UniffiByValue,
    )
}

@Structure.FieldOrder("returnValue", "callStatus")
internal open class UniffiForeignFutureResultRustBuffer(
    @JvmField internal var `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(),
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `returnValue`: RustBuffer.ByValue = RustBuffer.ByValue(),
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultRustBuffer(`returnValue`, `callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultRustBuffer) {
        `returnValue` = other.`returnValue`
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteRustBuffer : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultRustBuffer.UniffiByValue,
    )
}

@Structure.FieldOrder("callStatus")
internal open class UniffiForeignFutureResultVoid(
    @JvmField internal var `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
) : Structure() {
    class UniffiByValue(
        `callStatus`: UniffiRustCallStatus.ByValue = UniffiRustCallStatus.ByValue(),
    ) : UniffiForeignFutureResultVoid(`callStatus`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiForeignFutureResultVoid) {
        `callStatus` = other.`callStatus`
    }
}

internal interface UniffiForeignFutureCompleteVoid : com.sun.jna.Callback {
    fun callback(
        `callbackData`: Long,
        `result`: UniffiForeignFutureResultVoid.UniffiByValue,
    )
}

internal interface UniffiCallbackInterfaceDownloadProgressSinkMethod0 : com.sun.jna.Callback {
    fun callback(
        `uniffiHandle`: Long,
        `url`: RustBuffer.ByValue,
        `bytesDownloaded`: Long,
        `totalBytes`: RustBuffer.ByValue,
        `uniffiOutReturn`: Pointer,
        uniffiCallStatus: UniffiRustCallStatus,
    )
}

internal interface UniffiCallbackInterfaceModalitySinkMethod0 : com.sun.jna.Callback {
    fun callback(
        `uniffiHandle`: Long,
        `tokens`: RustBuffer.ByValue,
        `uniffiOutReturn`: Pointer,
        uniffiCallStatus: UniffiRustCallStatus,
    )
}

internal interface UniffiCallbackInterfaceModalitySinkMethod1 : com.sun.jna.Callback {
    fun callback(
        `uniffiHandle`: Long,
        `pcm`: RustBuffer.ByValue,
        `sampleRate`: Int,
        `uniffiOutReturn`: Pointer,
        uniffiCallStatus: UniffiRustCallStatus,
    )
}

internal interface UniffiCallbackInterfaceModalitySinkMethod2 : com.sun.jna.Callback {
    fun callback(
        `uniffiHandle`: Long,
        `reason`: RustBuffer.ByValue,
        `uniffiOutReturn`: Pointer,
        uniffiCallStatus: UniffiRustCallStatus,
    )
}

@Structure.FieldOrder("uniffiFree", "uniffiClone", "onProgress")
internal open class UniffiVTableCallbackInterfaceDownloadProgressSink(
    @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null,
    @JvmField internal var `uniffiClone`: UniffiCallbackInterfaceClone? = null,
    @JvmField internal var `onProgress`: UniffiCallbackInterfaceDownloadProgressSinkMethod0? = null,
) : Structure() {
    class UniffiByValue(
        `uniffiFree`: UniffiCallbackInterfaceFree? = null,
        `uniffiClone`: UniffiCallbackInterfaceClone? = null,
        `onProgress`: UniffiCallbackInterfaceDownloadProgressSinkMethod0? = null,
    ) : UniffiVTableCallbackInterfaceDownloadProgressSink(`uniffiFree`, `uniffiClone`, `onProgress`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceDownloadProgressSink) {
        `uniffiFree` = other.`uniffiFree`
        `uniffiClone` = other.`uniffiClone`
        `onProgress` = other.`onProgress`
    }
}

@Structure.FieldOrder("uniffiFree", "uniffiClone", "onTextTokens", "onAudioFrames", "onDone")
internal open class UniffiVTableCallbackInterfaceModalitySink(
    @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null,
    @JvmField internal var `uniffiClone`: UniffiCallbackInterfaceClone? = null,
    @JvmField internal var `onTextTokens`: UniffiCallbackInterfaceModalitySinkMethod0? = null,
    @JvmField internal var `onAudioFrames`: UniffiCallbackInterfaceModalitySinkMethod1? = null,
    @JvmField internal var `onDone`: UniffiCallbackInterfaceModalitySinkMethod2? = null,
) : Structure() {
    class UniffiByValue(
        `uniffiFree`: UniffiCallbackInterfaceFree? = null,
        `uniffiClone`: UniffiCallbackInterfaceClone? = null,
        `onTextTokens`: UniffiCallbackInterfaceModalitySinkMethod0? = null,
        `onAudioFrames`: UniffiCallbackInterfaceModalitySinkMethod1? = null,
        `onDone`: UniffiCallbackInterfaceModalitySinkMethod2? = null,
    ) : UniffiVTableCallbackInterfaceModalitySink(`uniffiFree`, `uniffiClone`, `onTextTokens`, `onAudioFrames`, `onDone`),
        Structure.ByValue

    internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceModalitySink) {
        `uniffiFree` = other.`uniffiFree`
        `uniffiClone` = other.`uniffiClone`
        `onTextTokens` = other.`onTextTokens`
        `onAudioFrames` = other.`onAudioFrames`
        `onDone` = other.`onDone`
    }
}

// A JNA Library to expose the extern-C FFI definitions.
// This is an implementation detail which will be called internally by the public API.

// For large crates we prevent `MethodTooLargeException` (see #2340)
// N.B. the name of the extension is very misleading, since it is
// rather `InterfaceTooLargeException`, caused by too many methods
// in the interface for large crates.
//
// By splitting the otherwise huge interface into two parts
// * UniffiLib (this)
// * IntegrityCheckingUniffiLib
// And all checksum methods are put into `IntegrityCheckingUniffiLib`
// we allow for ~2x as many methods in the UniffiLib interface.
//
// Note: above all written when we used JNA's `loadIndirect` etc.
// We now use JNA's "direct mapping" - unclear if same considerations apply exactly.
internal object IntegrityCheckingUniffiLib {
    init {
        Native.register(IntegrityCheckingUniffiLib::class.java, findLibraryName(componentName = "cera_ffi"))
        uniffiCheckContractApiVersion(this)
        uniffiCheckApiChecksums(this)
    }

    external fun uniffi_cera_ffi_checksum_func_cera_ffi_version(): Int

    external fun uniffi_cera_ffi_checksum_func_cpu_backend_report(): Int

    external fun uniffi_cera_ffi_checksum_method_bundlerepo_cache_size(): Int

    external fun uniffi_cera_ffi_checksum_method_bundlerepo_clear_cache(): Int

    external fun uniffi_cera_ffi_checksum_method_bundlerepo_store_dir(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_apply_chat_template(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_bos_token(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_capabilities(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_context_size(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_decode_tokens(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_encode_text(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_eos_token(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_has_chat_template(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_is_special_token(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_metadata(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_new_session(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_special_token_id(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_transcribe(): Int

    external fun uniffi_cera_ffi_checksum_method_ceraengine_vocab_size(): Int

    external fun uniffi_cera_ffi_checksum_method_downloadprogresssink_on_progress(): Int

    external fun uniffi_cera_ffi_checksum_method_loraadapters_target_count(): Int

    external fun uniffi_cera_ffi_checksum_method_modalitysink_on_text_tokens(): Int

    external fun uniffi_cera_ffi_checksum_method_modalitysink_on_audio_frames(): Int

    external fun uniffi_cera_ffi_checksum_method_modalitysink_on_done(): Int

    external fun uniffi_cera_ffi_checksum_method_session_append_audio(): Int

    external fun uniffi_cera_ffi_checksum_method_session_append_image(): Int

    external fun uniffi_cera_ffi_checksum_method_session_append_text(): Int

    external fun uniffi_cera_ffi_checksum_method_session_append_tokens(): Int

    external fun uniffi_cera_ffi_checksum_method_session_attach_lora(): Int

    external fun uniffi_cera_ffi_checksum_method_session_cancel(): Int

    external fun uniffi_cera_ffi_checksum_method_session_capabilities(): Int

    external fun uniffi_cera_ffi_checksum_method_session_clear_cancel(): Int

    external fun uniffi_cera_ffi_checksum_method_session_generate(): Int

    external fun uniffi_cera_ffi_checksum_method_session_generate_async(): Int

    external fun uniffi_cera_ffi_checksum_method_session_generate_streaming(): Int

    external fun uniffi_cera_ffi_checksum_method_session_generate_streaming_async(): Int

    external fun uniffi_cera_ffi_checksum_method_session_has_lora(): Int

    external fun uniffi_cera_ffi_checksum_method_session_hidden_size(): Int

    external fun uniffi_cera_ffi_checksum_method_session_hidden_states_for_text(): Int

    external fun uniffi_cera_ffi_checksum_method_session_hidden_states_for_tokens(): Int

    external fun uniffi_cera_ffi_checksum_method_session_hidden_states_mean_pooled(): Int

    external fun uniffi_cera_ffi_checksum_method_session_position(): Int

    external fun uniffi_cera_ffi_checksum_method_session_remove_lora(): Int

    external fun uniffi_cera_ffi_checksum_method_session_reset(): Int

    external fun uniffi_cera_ffi_checksum_method_session_set_image_max_long_size(): Int

    external fun uniffi_cera_ffi_checksum_constructor_bundlerepo_new(): Int

    external fun uniffi_cera_ffi_checksum_constructor_bundlerepo_with_progress(): Int

    external fun uniffi_cera_ffi_checksum_constructor_ceraengine_from_bundle_id(): Int

    external fun uniffi_cera_ffi_checksum_constructor_ceraengine_from_bundle_id_async(): Int

    external fun uniffi_cera_ffi_checksum_constructor_ceraengine_from_path(): Int

    external fun uniffi_cera_ffi_checksum_constructor_loraadapters_from_gguf(): Int

    external fun uniffi_cera_ffi_checksum_constructor_loraadapters_from_safetensors(): Int

    external fun ffi_cera_ffi_uniffi_contract_version(): Int
}

internal object UniffiLib {
    // The Cleaner for the whole library
    internal val CLEANER: UniffiCleaner by lazy {
        UniffiCleaner.create()
    }

    init {
        Native.register(UniffiLib::class.java, findLibraryName(componentName = "cera_ffi"))
        uniffiCallbackInterfaceDownloadProgressSink.register(this)
        uniffiCallbackInterfaceModalitySink.register(this)
    }

    external fun uniffi_cera_ffi_fn_clone_bundlerepo(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_bundlerepo(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_constructor_bundlerepo_new(
        `storeDir`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_constructor_bundlerepo_with_progress(
        `storeDir`: RustBuffer.ByValue,
        `progress`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_bundlerepo_cache_size(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_bundlerepo_clear_cache(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_bundlerepo_store_dir(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_clone_ceraengine(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_ceraengine(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_constructor_ceraengine_from_bundle_id(
        `bundleId`: RustBuffer.ByValue,
        `quant`: RustBuffer.ByValue,
        `config`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_constructor_ceraengine_from_bundle_id_async(
        `bundleId`: RustBuffer.ByValue,
        `quant`: RustBuffer.ByValue,
        `config`: RustBuffer.ByValue,
    ): Long

    external fun uniffi_cera_ffi_fn_constructor_ceraengine_from_path(
        `path`: RustBuffer.ByValue,
        `config`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_ceraengine_apply_chat_template(
        `ptr`: Long,
        `messages`: RustBuffer.ByValue,
        `addGenerationPrompt`: Byte,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_bos_token(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_capabilities(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_context_size(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_ceraengine_decode_tokens(
        `ptr`: Long,
        `tokens`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_encode_text(
        `ptr`: Long,
        `text`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_eos_token(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_has_chat_template(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Byte

    external fun uniffi_cera_ffi_fn_method_ceraengine_is_special_token(
        `ptr`: Long,
        `id`: Int,
        uniffi_out_err: UniffiRustCallStatus,
    ): Byte

    external fun uniffi_cera_ffi_fn_method_ceraengine_metadata(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_new_session(
        `ptr`: Long,
        `config`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_ceraengine_special_token_id(
        `ptr`: Long,
        `name`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_transcribe(
        `ptr`: Long,
        `pcm`: RustBuffer.ByValue,
        `sampleRate`: Int,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_ceraengine_vocab_size(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun uniffi_cera_ffi_fn_clone_downloadprogresssink(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_downloadprogresssink(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_init_callback_vtable_downloadprogresssink(
        `vtable`: UniffiVTableCallbackInterfaceDownloadProgressSink,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_downloadprogresssink_on_progress(
        `ptr`: Long,
        `url`: RustBuffer.ByValue,
        `bytesDownloaded`: Long,
        `totalBytes`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_clone_loraadapters(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_loraadapters(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_constructor_loraadapters_from_gguf(
        `path`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_constructor_loraadapters_from_safetensors(
        `path`: RustBuffer.ByValue,
        `alpha`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_method_loraadapters_target_count(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun uniffi_cera_ffi_fn_clone_modalitysink(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_modalitysink(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_init_callback_vtable_modalitysink(`vtable`: UniffiVTableCallbackInterfaceModalitySink): Unit

    external fun uniffi_cera_ffi_fn_method_modalitysink_on_text_tokens(
        `ptr`: Long,
        `tokens`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_modalitysink_on_audio_frames(
        `ptr`: Long,
        `pcm`: RustBuffer.ByValue,
        `sampleRate`: Int,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_modalitysink_on_done(
        `ptr`: Long,
        `reason`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_clone_session(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun uniffi_cera_ffi_fn_free_session(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_append_audio(
        `ptr`: Long,
        `samples`: RustBuffer.ByValue,
        `sampleRate`: Int,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_append_image(
        `ptr`: Long,
        `bytes`: RustBuffer.ByValue,
        `maxLongSize`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_append_text(
        `ptr`: Long,
        `text`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_append_tokens(
        `ptr`: Long,
        `tokens`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_attach_lora(
        `ptr`: Long,
        `adapters`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_cancel(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_capabilities(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_clear_cancel(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_generate(
        `ptr`: Long,
        `opts`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_generate_async(
        `ptr`: Long,
        `opts`: RustBuffer.ByValue,
    ): Long

    external fun uniffi_cera_ffi_fn_method_session_generate_streaming(
        `ptr`: Long,
        `opts`: RustBuffer.ByValue,
        `sink`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_generate_streaming_async(
        `ptr`: Long,
        `opts`: RustBuffer.ByValue,
        `sink`: Long,
    ): Long

    external fun uniffi_cera_ffi_fn_method_session_has_lora(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Byte

    external fun uniffi_cera_ffi_fn_method_session_hidden_size(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun uniffi_cera_ffi_fn_method_session_hidden_states_for_text(
        `ptr`: Long,
        `text`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_hidden_states_for_tokens(
        `ptr`: Long,
        `tokens`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_hidden_states_mean_pooled(
        `ptr`: Long,
        `tokens`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_method_session_position(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun uniffi_cera_ffi_fn_method_session_remove_lora(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_reset(
        `ptr`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_method_session_set_image_max_long_size(
        `ptr`: Long,
        `maxLongSize`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun uniffi_cera_ffi_fn_func_cera_ffi_version(uniffi_out_err: UniffiRustCallStatus): RustBuffer.ByValue

    external fun uniffi_cera_ffi_fn_func_cpu_backend_report(uniffi_out_err: UniffiRustCallStatus): RustBuffer.ByValue

    external fun ffi_cera_ffi_rustbuffer_alloc(
        `size`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun ffi_cera_ffi_rustbuffer_from_bytes(
        `bytes`: ForeignBytes.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun ffi_cera_ffi_rustbuffer_free(
        `buf`: RustBuffer.ByValue,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit

    external fun ffi_cera_ffi_rustbuffer_reserve(
        `buf`: RustBuffer.ByValue,
        `additional`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun ffi_cera_ffi_rust_future_poll_u8(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_u8(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_u8(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_u8(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun ffi_cera_ffi_rust_future_poll_i8(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_i8(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_i8(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_i8(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Byte

    external fun ffi_cera_ffi_rust_future_poll_u16(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_u16(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_u16(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_u16(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun ffi_cera_ffi_rust_future_poll_i16(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_i16(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_i16(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_i16(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Short

    external fun ffi_cera_ffi_rust_future_poll_u32(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_u32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_u32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_u32(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun ffi_cera_ffi_rust_future_poll_i32(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_i32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_i32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_i32(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Int

    external fun ffi_cera_ffi_rust_future_poll_u64(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_u64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_u64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_u64(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun ffi_cera_ffi_rust_future_poll_i64(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_i64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_i64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_i64(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Long

    external fun ffi_cera_ffi_rust_future_poll_f32(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_f32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_f32(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_f32(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Float

    external fun ffi_cera_ffi_rust_future_poll_f64(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_f64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_f64(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_f64(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Double

    external fun ffi_cera_ffi_rust_future_poll_rust_buffer(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_rust_buffer(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_rust_buffer(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_rust_buffer(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): RustBuffer.ByValue

    external fun ffi_cera_ffi_rust_future_poll_void(
        `handle`: Long,
        `callback`: UniffiRustFutureContinuationCallback,
        `callbackData`: Long,
    ): Unit

    external fun ffi_cera_ffi_rust_future_cancel_void(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_free_void(`handle`: Long): Unit

    external fun ffi_cera_ffi_rust_future_complete_void(
        `handle`: Long,
        uniffi_out_err: UniffiRustCallStatus,
    ): Unit
}

private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) {
    // Get the bindings contract version from our ComponentInterface
    val bindings_contract_version = 30
    // Get the scaffolding contract version by calling the into the dylib
    val scaffolding_contract_version = lib.ffi_cera_ffi_uniffi_contract_version()
    if (bindings_contract_version != scaffolding_contract_version) {
        throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project")
    }
}

@Suppress("UNUSED_PARAMETER")
private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
    if (lib.uniffi_cera_ffi_checksum_func_cera_ffi_version() != 39330) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_func_cpu_backend_report() != 61086) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_bundlerepo_cache_size() != 29364) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_bundlerepo_clear_cache() != 11512) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_bundlerepo_store_dir() != 40876) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_apply_chat_template() != 38712) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_bos_token() != 30744) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_capabilities() != 65060) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_context_size() != 47091) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_decode_tokens() != 27407) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_encode_text() != 52220) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_eos_token() != 21294) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_has_chat_template() != 63563) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_is_special_token() != 7395) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_metadata() != 46262) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_new_session() != 54382) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_special_token_id() != 35790) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_transcribe() != 9680) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_ceraengine_vocab_size() != 13487) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_downloadprogresssink_on_progress() != 33688) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_loraadapters_target_count() != 23137) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_modalitysink_on_text_tokens() != 50332) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_modalitysink_on_audio_frames() != 54889) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_modalitysink_on_done() != 2908) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_append_audio() != 44552) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_append_image() != 13190) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_append_text() != 13301) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_append_tokens() != 1227) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_attach_lora() != 28982) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_cancel() != 7555) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_capabilities() != 8147) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_clear_cancel() != 11168) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_generate() != 57005) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_generate_async() != 58489) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_generate_streaming() != 43707) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_generate_streaming_async() != 12198) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_has_lora() != 13931) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_hidden_size() != 46607) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_hidden_states_for_text() != 54184) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_hidden_states_for_tokens() != 65100) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_hidden_states_mean_pooled() != 61246) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_position() != 13264) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_remove_lora() != 29534) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_reset() != 48041) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_method_session_set_image_max_long_size() != 36283) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_bundlerepo_new() != 15544) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_bundlerepo_with_progress() != 24631) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_ceraengine_from_bundle_id() != 60717) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_ceraengine_from_bundle_id_async() != 14088) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_ceraengine_from_path() != 64420) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_loraadapters_from_gguf() != 57598) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
    if (lib.uniffi_cera_ffi_checksum_constructor_loraadapters_from_safetensors() != 11183) {
        throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
    }
}

/**
 * @suppress
 */
public fun uniffiEnsureInitialized() {
    IntegrityCheckingUniffiLib
    // UniffiLib() initialized as objects are used, but we still need to explicitly
    // reference it so initialization across crates works as expected.
    UniffiLib
}

// Async support
// Async return type handlers

internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
internal const val UNIFFI_RUST_FUTURE_POLL_WAKE = 1.toByte()

internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()

// FFI type for Rust future continuations
internal object uniffiRustFutureContinuationCallbackImpl : UniffiRustFutureContinuationCallback {
    override fun callback(
        data: Long,
        pollResult: Byte,
    ) {
        uniffiContinuationHandleMap.remove(data).resume(pollResult)
    }
}

internal suspend fun <T, F, E : kotlin.Exception> uniffiRustCallAsync(
    rustFuture: Long,
    pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit,
    completeFunc: (Long, UniffiRustCallStatus) -> F,
    freeFunc: (Long) -> Unit,
    liftFunc: (F) -> T,
    errorHandler: UniffiRustCallStatusErrorHandler<E>,
): T {
    try {
        do {
            val pollResult =
                suspendCancellableCoroutine<Byte> { continuation ->
                    pollFunc(
                        rustFuture,
                        uniffiRustFutureContinuationCallbackImpl,
                        uniffiContinuationHandleMap.insert(continuation),
                    )
                }
        } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY)

        return liftFunc(
            uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }),
        )
    } finally {
        freeFunc(rustFuture)
    }
}

// Public interface members begin here.

// Interface implemented by anything that can contain an object reference.
//
// Such types expose a `destroy()` method that must be called to cleanly
// dispose of the contained objects. Failure to call this method may result
// in memory leaks.
//
// The easiest way to ensure this method is called is to use the `.use`
// helper method to execute a block and destroy the object at the end.
interface Disposable {
    fun destroy()

    companion object {
        fun destroy(vararg args: Any?) {
            for (arg in args) {
                when (arg) {
                    is Disposable -> {
                        arg.destroy()
                    }

                    is ArrayList<*> -> {
                        for (idx in arg.indices) {
                            val element = arg[idx]
                            if (element is Disposable) {
                                element.destroy()
                            }
                        }
                    }

                    is Map<*, *> -> {
                        for (element in arg.values) {
                            if (element is Disposable) {
                                element.destroy()
                            }
                        }
                    }

                    is Iterable<*> -> {
                        for (element in arg) {
                            if (element is Disposable) {
                                element.destroy()
                            }
                        }
                    }
                }
            }
        }
    }
}

/**
 * @suppress
 */
inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
    try {
        block(this)
    } finally {
        try {
            // N.B. our implementation is on the nullable type `Disposable?`.
            this?.destroy()
        } catch (e: Throwable) {
            // swallow
        }
    }

/**
 * Placeholder object used to signal that we're constructing an interface with a FFI handle.
 *
 * This is the first argument for interface constructors that input a raw handle. It exists is that
 * so we can avoid signature conflicts when an interface has a regular constructor than inputs a
 * Long.
 *
 * @suppress
 * */
object UniffiWithHandle

/**
 * Used to instantiate an interface without an actual pointer, for fakes in tests, mostly.
 *
 * @suppress
 * */
object NoHandle // Magic number for the Rust proxy to call using the same mechanism as every other method,

// to free the callback once it's dropped by Rust.
internal const val IDX_CALLBACK_FREE = 0

// Callback return codes
internal const val UNIFFI_CALLBACK_SUCCESS = 0
internal const val UNIFFI_CALLBACK_ERROR = 1
internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2

/**
 * @suppress
 */
public abstract class FfiConverterCallbackInterface<CallbackInterface : Any> : FfiConverter<CallbackInterface, Long> {
    internal val handleMap = UniffiHandleMap<CallbackInterface>()

    internal fun drop(handle: Long) {
        handleMap.remove(handle)
    }

    override fun lift(value: Long): CallbackInterface = handleMap.get(value)

    override fun read(buf: ByteBuffer) = lift(buf.getLong())

    override fun lower(value: CallbackInterface) = handleMap.insert(value)

    override fun allocationSize(value: CallbackInterface) = 8UL

    override fun write(
        value: CallbackInterface,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

/**
 * The cleaner interface for Object finalization code to run.
 * This is the entry point to any implementation that we're using.
 *
 * The cleaner registers objects and returns cleanables, so now we are
 * defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
 * different implmentations available at compile time.
 *
 * @suppress
 */
interface UniffiCleaner {
    interface Cleanable {
        fun clean()
    }

    fun register(
        value: Any,
        cleanUpTask: Runnable,
    ): UniffiCleaner.Cleanable

    companion object
}

// The fallback Jna cleaner, which is available for both Android, and the JVM.
private class UniffiJnaCleaner : UniffiCleaner {
    private val cleaner =
        com.sun.jna.internal.Cleaner
            .getCleaner()

    override fun register(
        value: Any,
        cleanUpTask: Runnable,
    ): UniffiCleaner.Cleanable = UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
}

private class UniffiJnaCleanable(
    private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
) : UniffiCleaner.Cleanable {
    override fun clean() = cleanable.clean()
}

// We decide at uniffi binding generation time whether we were
// using Android or not.
// There are further runtime checks to chose the correct implementation
// of the cleaner.
private fun UniffiCleaner.Companion.create(): UniffiCleaner =
    try {
        // For safety's sake: if the library hasn't been run in android_cleaner = true
        // mode, but is being run on Android, then we still need to think about
        // Android API versions.
        // So we check if java.lang.ref.Cleaner is there, and use that…
        java.lang.Class.forName("java.lang.ref.Cleaner")
        JavaLangRefCleaner()
    } catch (e: ClassNotFoundException) {
        // … otherwise, fallback to the JNA cleaner.
        UniffiJnaCleaner()
    }

private class JavaLangRefCleaner : UniffiCleaner {
    val cleaner =
        java.lang.ref.Cleaner
            .create()

    override fun register(
        value: Any,
        cleanUpTask: Runnable,
    ): UniffiCleaner.Cleanable = JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
}

private class JavaLangRefCleanable(
    val cleanable: java.lang.ref.Cleaner.Cleanable,
) : UniffiCleaner.Cleanable {
    override fun clean() = cleanable.clean()
}

/**
 * @suppress
 */
public object FfiConverterUInt : FfiConverter<UInt, Int> {
    override fun lift(value: Int): UInt = value.toUInt()

    override fun read(buf: ByteBuffer): UInt = lift(buf.getInt())

    override fun lower(value: UInt): Int = value.toInt()

    override fun allocationSize(value: UInt) = 4UL

    override fun write(
        value: UInt,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.toInt())
    }
}

/**
 * @suppress
 */
public object FfiConverterULong : FfiConverter<ULong, Long> {
    override fun lift(value: Long): ULong = value.toULong()

    override fun read(buf: ByteBuffer): ULong = lift(buf.getLong())

    override fun lower(value: ULong): Long = value.toLong()

    override fun allocationSize(value: ULong) = 8UL

    override fun write(
        value: ULong,
        buf: ByteBuffer,
    ) {
        buf.putLong(value.toLong())
    }
}

/**
 * @suppress
 */
public object FfiConverterFloat : FfiConverter<Float, Float> {
    override fun lift(value: Float): Float = value

    override fun read(buf: ByteBuffer): Float = buf.getFloat()

    override fun lower(value: Float): Float = value

    override fun allocationSize(value: Float) = 4UL

    override fun write(
        value: Float,
        buf: ByteBuffer,
    ) {
        buf.putFloat(value)
    }
}

/**
 * @suppress
 */
public object FfiConverterBoolean : FfiConverter<Boolean, Byte> {
    override fun lift(value: Byte): Boolean = value.toInt() != 0

    override fun read(buf: ByteBuffer): Boolean = lift(buf.get())

    override fun lower(value: Boolean): Byte = if (value) 1.toByte() else 0.toByte()

    override fun allocationSize(value: Boolean) = 1UL

    override fun write(
        value: Boolean,
        buf: ByteBuffer,
    ) {
        buf.put(lower(value))
    }
}

/**
 * @suppress
 */
public object FfiConverterString : FfiConverter<String, RustBuffer.ByValue> {
    // Note: we don't inherit from FfiConverterRustBuffer, because we use a
    // special encoding when lowering/lifting.  We can use `RustBuffer.len` to
    // store our length and avoid writing it out to the buffer.
    override fun lift(value: RustBuffer.ByValue): String {
        try {
            val byteArr = ByteArray(value.len.toInt())
            value.asByteBuffer()!!.get(byteArr)
            return byteArr.toString(Charsets.UTF_8)
        } finally {
            RustBuffer.free(value)
        }
    }

    override fun read(buf: ByteBuffer): String {
        val len = buf.getInt()
        val byteArr = ByteArray(len)
        buf.get(byteArr)
        return byteArr.toString(Charsets.UTF_8)
    }

    fun toUtf8(value: String): ByteBuffer {
        // Make sure we don't have invalid UTF-16, check for lone surrogates.
        return Charsets.UTF_8.newEncoder().run {
            onMalformedInput(CodingErrorAction.REPORT)
            encode(CharBuffer.wrap(value))
        }
    }

    override fun lower(value: String): RustBuffer.ByValue {
        val byteBuf = toUtf8(value)
        // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
        // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
        val rbuf = RustBuffer.alloc(byteBuf.limit().toULong())
        rbuf.asByteBuffer()!!.put(byteBuf)
        return rbuf
    }

    // We aren't sure exactly how many bytes our string will be once it's UTF-8
    // encoded.  Allocate 3 bytes per UTF-16 code unit which will always be
    // enough.
    override fun allocationSize(value: String): ULong {
        val sizeForLength = 4UL
        val sizeForString = value.length.toULong() * 3UL
        return sizeForLength + sizeForString
    }

    override fun write(
        value: String,
        buf: ByteBuffer,
    ) {
        val byteBuf = toUtf8(value)
        buf.putInt(byteBuf.limit())
        buf.put(byteBuf)
    }
}

/**
 * @suppress
 */
public object FfiConverterByteArray : FfiConverterRustBuffer<ByteArray> {
    override fun read(buf: ByteBuffer): ByteArray {
        val len = buf.getInt()
        val byteArr = ByteArray(len)
        buf.get(byteArr)
        return byteArr
    }

    override fun allocationSize(value: ByteArray): ULong = 4UL + value.size.toULong()

    override fun write(
        value: ByteArray,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.size)
        buf.put(value)
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * Remote model-bundle downloader + on-disk cache. Wraps
 * [`cera::bundle::BundleRepo`]; construct once per application with
 * a persistent `store_dir` and reuse across engine loads so the
 * HTTP client pool + downloaded-file cache are shared.
 *
 * On Android the `store_dir` should typically be
 * `Context.getFilesDir()` (persistent), not `getCacheDir()` (OS-
 * purgeable under storage pressure). On iOS / macOS, the app's
 * Application Support or a dedicated subdirectory under Documents
 * is a reasonable baseline.
 *
 * Cache layout mirrors the remote URL structure under
 * `<store_dir>/huggingface.co/<full path>`, so inspecting the
 * on-disk state with a file browser is straightforward and multiple
 * cera-powered apps on the same device can share the same cache
 * directory without conflicting.
 */
public interface BundleRepoInterface {
    /**
     * Total bytes currently held in the cache. Returns `0` if the
     * `store_dir` doesn't exist yet (no downloads have run).
     * O(n) over the cache contents; for a multi-GB cache it's a
     * real walk, not a constant-time query — UIs surfacing the
     * value should run it off the main thread (e.g. via
     * `withContext(Dispatchers.IO)` on Kotlin or
     * `Task.detached` on Swift).
     *
     * Mobile apps use this to drive a "Storage: X MB used" line in
     * settings or to gate a "Clear cache" button on actual
     * non-zero usage.
     */
    fun `cacheSize`(): kotlin.ULong

    /**
     * Wipe every file the repo has cached, leaving `store_dir`
     * itself in place so subsequent downloads land in the same
     * path. Idempotent — calling on an empty repo or non-existent
     * `store_dir` is a no-op success.
     *
     * Mobile apps trigger this from a "Clear downloaded models"
     * settings action. Caller is responsible for serializing
     * against in-flight downloads — typically trivial since the
     * action is user-driven.
     */
    fun `clearCache`()

    /**
     * The directory this repo caches bundles under. Matches what was
     * passed to [`BundleRepo::new`] / [`BundleRepo::with_progress`],
     * useful for log / telemetry.
     */
    fun `storeDir`(): kotlin.String

    companion object
}

/**
 * Remote model-bundle downloader + on-disk cache. Wraps
 * [`cera::bundle::BundleRepo`]; construct once per application with
 * a persistent `store_dir` and reuse across engine loads so the
 * HTTP client pool + downloaded-file cache are shared.
 *
 * On Android the `store_dir` should typically be
 * `Context.getFilesDir()` (persistent), not `getCacheDir()` (OS-
 * purgeable under storage pressure). On iOS / macOS, the app's
 * Application Support or a dedicated subdirectory under Documents
 * is a reasonable baseline.
 *
 * Cache layout mirrors the remote URL structure under
 * `<store_dir>/huggingface.co/<full path>`, so inspecting the
 * on-disk state with a file browser is straightforward and multiple
 * cera-powered apps on the same device can share the same cache
 * directory without conflicting.
 */
open class BundleRepo :
    Disposable,
    AutoCloseable,
    BundleRepoInterface {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    /**
     * Create a new repo rooted at `store_dir`. The directory doesn't
     * need to exist yet — it's created on the first download. Pass
     * the same path to subsequent runs to reuse the cached bundles.
     */
    constructor(`storeDir`: kotlin.String) :
        this(
            UniffiWithHandle,
            uniffiRustCall { _status ->
                UniffiLib.uniffi_cera_ffi_fn_constructor_bundlerepo_new(FfiConverterString.lower(`storeDir`), _status)
            },
        )

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_bundlerepo(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_bundlerepo(handle, status)
        }
    }

    /**
     * Total bytes currently held in the cache. Returns `0` if the
     * `store_dir` doesn't exist yet (no downloads have run).
     * O(n) over the cache contents; for a multi-GB cache it's a
     * real walk, not a constant-time query — UIs surfacing the
     * value should run it off the main thread (e.g. via
     * `withContext(Dispatchers.IO)` on Kotlin or
     * `Task.detached` on Swift).
     *
     * Mobile apps use this to drive a "Storage: X MB used" line in
     * settings or to gate a "Clear cache" button on actual
     * non-zero usage.
     */
    @Throws(FfiException::class)
    override fun `cacheSize`(): kotlin.ULong =
        FfiConverterULong.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_bundlerepo_cache_size(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Wipe every file the repo has cached, leaving `store_dir`
     * itself in place so subsequent downloads land in the same
     * path. Idempotent — calling on an empty repo or non-existent
     * `store_dir` is a no-op success.
     *
     * Mobile apps trigger this from a "Clear downloaded models"
     * settings action. Caller is responsible for serializing
     * against in-flight downloads — typically trivial since the
     * action is user-driven.
     */
    @Throws(FfiException::class)
    override fun `clearCache`() =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_bundlerepo_clear_cache(
                    it,
                    _status,
                )
            }
        }

    /**
     * The directory this repo caches bundles under. Matches what was
     * passed to [`BundleRepo::new`] / [`BundleRepo::with_progress`],
     * useful for log / telemetry.
     */
    override fun `storeDir`(): kotlin.String =
        FfiConverterString.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_bundlerepo_store_dir(
                        it,
                        _status,
                    )
                }
            },
        )

    companion object {
        /**
         * Create a new repo rooted at `store_dir` with a foreign
         * [`DownloadProgressSink`] attached. The sink fires periodically
         * during cache-miss downloads (every ~256 KB written + once at
         * end-of-stream). Cache-hit resolves don't fire any callbacks.
         * The same sink receives events for every file the repo
         * downloads — distinguish per-file progress by the `url`
         * argument on each callback.
         *
         * Construction-time attachment (rather than per-call) matches
         * how mobile apps drive a single download-progress UI across
         * multiple files in one logical bundle (manifest + GGUF + …):
         * one repo, one sink, one progress bar. If you need to tear
         * down the sink mid-app-lifecycle, drop the repo + construct a
         * new one — Arc-based, so all in-flight calls finish on the
         * old sink and new calls go to the new one.
         */
        fun `withProgress`(
            `storeDir`: kotlin.String,
            `progress`: DownloadProgressSink,
        ): BundleRepo =
            FfiConverterTypeBundleRepo.lift(
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_constructor_bundlerepo_with_progress(
                        FfiConverterString.lower(`storeDir`),
                        FfiConverterTypeDownloadProgressSink.lower(`progress`),
                        _status,
                    )
                },
            )
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeBundleRepo : FfiConverter<BundleRepo, Long> {
    override fun lower(value: BundleRepo): Long = value.uniffiCloneHandle()

    override fun lift(value: Long): BundleRepo = BundleRepo(UniffiWithHandle, value)

    override fun read(buf: ByteBuffer): BundleRepo = lift(buf.getLong())

    override fun allocationSize(value: BundleRepo) = 8UL

    override fun write(
        value: BundleRepo,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * Owning handle to a loaded model. Mirrors [`cera::CeraEngine`];
 * `#[uniffi::Object]` requires `Arc<Self>` wrapping which matches how
 * the underlying engine is already used internally.
 */
public interface CeraEngineInterface {
    /**
     * Render the model's chat template against a sequence of
     * `ChatMessage`s. `add_generation_prompt = true` appends the
     * model's "now it's the assistant's turn" suffix (typical when
     * driving an interactive chat); `false` produces a transcript
     * the model can keep continuing.
     *
     * Returns [`FfiError::Backend`] if the model has no chat
     * template (check [`CeraEngine::has_chat_template`] first) or
     * if the template fails to render against the supplied messages.
     */
    fun `applyChatTemplate`(
        `messages`: List<ChatMessage>,
        `addGenerationPrompt`: kotlin.Boolean,
    ): kotlin.String

    /**
     * Beginning-of-sequence token ID, if the model has one.
     * LLaMA-family models typically do; some don't. Honor
     * [`ModelMetadata::add_bos_token`] when deciding whether to
     * prepend it manually to a prompt.
     */
    fun `bosToken`(): kotlin.UInt?

    /**
     * What this model accepts as input / emits as output. Derived at
     * load time from the manifest's `inference_type`.
     */
    fun `capabilities`(): ModalityCapabilities

    /**
     * Resolved context-window size (KV cache cap) the engine was
     * configured with. Mirrors the `context_size` field of the
     * [`EngineConfig`] passed to `from_path` / `from_bundle_id`,
     * with the `0` → `model.max_seq_len` defaulting already
     * applied so callers always see a meaningful number rather
     * than the internal `usize::MAX` sentinel.
     *
     * Note this is the **engine-level** requested cap, not a
     * per-session ceiling. cera core clamps the model's
     * `max_seq_len` at load time to `min(requested_context,
     * gguf_max_seq_len)` (see `cera/src/model/lfm2.rs`), so
     * [`Self::metadata`]`.max_seq_len` is already the effective
     * ceiling for any session built from this engine — `context_size`
     * is informational ("what cap did this engine load with?")
     * rather than a value callers should `min(...)` against.
     */
    fun `contextSize`(): kotlin.ULong

    /**
     * Decode token IDs back to text. Out-of-vocab IDs are silently
     * skipped (omitted from the decoded output) — `BpeTokenizer::decode`
     * only appends bytes for IDs it has in `vocab.get(id)`. No
     * substitution glyph, no error. Callers that want to detect
     * invalid IDs should validate against `vocab_size()` first.
     */
    fun `decodeTokens`(`tokens`: List<kotlin.UInt>): kotlin.String

    /**
     * Encode `text` into token IDs using the model's BPE tokenizer.
     * Empty input returns an empty vec.
     */
    fun `encodeText`(`text`: kotlin.String): List<kotlin.UInt>

    /**
     * End-of-sequence / end-of-text token ID, if the model has one.
     * Used as a default stop-token by the sampler; callers can also
     * pass it explicitly in [`GenerateOpts::stop_tokens`].
     */
    fun `eosToken`(): kotlin.UInt?

    /**
     * `true` if the model's tokenizer carries a chat template (a
     * minijinja string from GGUF metadata). Foreign callers should
     * check this before calling [`CeraEngine::apply_chat_template`].
     */
    fun `hasChatTemplate`(): kotlin.Boolean

    /**
     * `true` when `id` is registered as a control or user-defined
     * special token in the model's GGUF metadata
     * (`tokenizer.ggml.token_type` types `3` / `4`). Useful for
     * output filtering — e.g. dropping `<|im_end|>` from streamed
     * tokens before rendering them to a UI — and for token-class
     * classification in analysis tools.
     *
     * Out-of-range IDs (>= vocab size) and regular vocab tokens
     * both return `false`. Companion to [`Self::special_token_id`]
     * which goes the other direction (name → ID).
     */
    fun `isSpecialToken`(`id`: kotlin.UInt): kotlin.Boolean

    /**
     * Short summary of the loaded model (architecture, vocab size,
     * max context, etc.). Returns a `Clone` of the stored metadata.
     */
    fun `metadata`(): ModelMetadata

    /**
     * Open a new [`Session`] sharing this engine's model + tokenizer
     * by `Arc` clone. The returned session outlives `&self`; the
     * engine keeps the shared state live for every session it hands
     * out. Cheap — no model load, just config + state allocation.
     */
    fun `newSession`(`config`: SessionConfig): Session

    /**
     * Look up a special token by name (e.g. `<|im_start|>`,
     * `<|im_end|>`, `<|tool_call|>`). Returns `None` if the token
     * isn't defined in the tokenizer's vocab.
     */
    fun `specialTokenId`(`name`: kotlin.String): kotlin.UInt?

    /**
     * Transcribe mono `f32` PCM audio (normalized to roughly `[-1.0, 1.0]`) to text using the
     * model's trained `"Perform ASR."` chat mode. `sample_rate` must match the audio encoder's
     * expected rate (resample beforehand if needed). Requires an audio-capable bundle; a text-only
     * model returns an [`FfiError`] for unsupported modality.
     *
     * Blocking: runs a full prefill + greedy decode. Foreign async runtimes should wrap the call in
     * `spawn_blocking` / its equivalent.
     */
    fun `transcribe`(
        `pcm`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    ): kotlin.String

    /**
     * Total vocabulary size — the number of distinct token IDs the
     * model can emit. Sourced from the model's config (matches
     * [`ModelMetadata::vocab_size`]) rather than the tokenizer's
     * own count: in healthy models they match, but the model's
     * config is the authoritative range for valid logit indices.
     */
    fun `vocabSize`(): kotlin.UInt

    companion object
}

/**
 * Owning handle to a loaded model. Mirrors [`cera::CeraEngine`];
 * `#[uniffi::Object]` requires `Arc<Self>` wrapping which matches how
 * the underlying engine is already used internally.
 */
open class CeraEngine :
    Disposable,
    AutoCloseable,
    CeraEngineInterface {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_ceraengine(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_ceraengine(handle, status)
        }
    }

    /**
     * Render the model's chat template against a sequence of
     * `ChatMessage`s. `add_generation_prompt = true` appends the
     * model's "now it's the assistant's turn" suffix (typical when
     * driving an interactive chat); `false` produces a transcript
     * the model can keep continuing.
     *
     * Returns [`FfiError::Backend`] if the model has no chat
     * template (check [`CeraEngine::has_chat_template`] first) or
     * if the template fails to render against the supplied messages.
     */
    @Throws(FfiException::class)
    override fun `applyChatTemplate`(
        `messages`: List<ChatMessage>,
        `addGenerationPrompt`: kotlin.Boolean,
    ): kotlin.String =
        FfiConverterString.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_apply_chat_template(
                        it,
                        FfiConverterSequenceTypeChatMessage.lower(`messages`),
                        FfiConverterBoolean.lower(`addGenerationPrompt`),
                        _status,
                    )
                }
            },
        )

    /**
     * Beginning-of-sequence token ID, if the model has one.
     * LLaMA-family models typically do; some don't. Honor
     * [`ModelMetadata::add_bos_token`] when deciding whether to
     * prepend it manually to a prompt.
     */
    override fun `bosToken`(): kotlin.UInt? =
        FfiConverterOptionalUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_bos_token(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * What this model accepts as input / emits as output. Derived at
     * load time from the manifest's `inference_type`.
     */
    override fun `capabilities`(): ModalityCapabilities =
        FfiConverterTypeModalityCapabilities.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_capabilities(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Resolved context-window size (KV cache cap) the engine was
     * configured with. Mirrors the `context_size` field of the
     * [`EngineConfig`] passed to `from_path` / `from_bundle_id`,
     * with the `0` → `model.max_seq_len` defaulting already
     * applied so callers always see a meaningful number rather
     * than the internal `usize::MAX` sentinel.
     *
     * Note this is the **engine-level** requested cap, not a
     * per-session ceiling. cera core clamps the model's
     * `max_seq_len` at load time to `min(requested_context,
     * gguf_max_seq_len)` (see `cera/src/model/lfm2.rs`), so
     * [`Self::metadata`]`.max_seq_len` is already the effective
     * ceiling for any session built from this engine — `context_size`
     * is informational ("what cap did this engine load with?")
     * rather than a value callers should `min(...)` against.
     */
    override fun `contextSize`(): kotlin.ULong =
        FfiConverterULong.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_context_size(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Decode token IDs back to text. Out-of-vocab IDs are silently
     * skipped (omitted from the decoded output) — `BpeTokenizer::decode`
     * only appends bytes for IDs it has in `vocab.get(id)`. No
     * substitution glyph, no error. Callers that want to detect
     * invalid IDs should validate against `vocab_size()` first.
     */
    override fun `decodeTokens`(`tokens`: List<kotlin.UInt>): kotlin.String =
        FfiConverterString.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_decode_tokens(
                        it,
                        FfiConverterSequenceUInt.lower(`tokens`),
                        _status,
                    )
                }
            },
        )

    /**
     * Encode `text` into token IDs using the model's BPE tokenizer.
     * Empty input returns an empty vec.
     */
    override fun `encodeText`(`text`: kotlin.String): List<kotlin.UInt> =
        FfiConverterSequenceUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_encode_text(
                        it,
                        FfiConverterString.lower(`text`),
                        _status,
                    )
                }
            },
        )

    /**
     * End-of-sequence / end-of-text token ID, if the model has one.
     * Used as a default stop-token by the sampler; callers can also
     * pass it explicitly in [`GenerateOpts::stop_tokens`].
     */
    override fun `eosToken`(): kotlin.UInt? =
        FfiConverterOptionalUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_eos_token(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * `true` if the model's tokenizer carries a chat template (a
     * minijinja string from GGUF metadata). Foreign callers should
     * check this before calling [`CeraEngine::apply_chat_template`].
     */
    override fun `hasChatTemplate`(): kotlin.Boolean =
        FfiConverterBoolean.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_has_chat_template(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * `true` when `id` is registered as a control or user-defined
     * special token in the model's GGUF metadata
     * (`tokenizer.ggml.token_type` types `3` / `4`). Useful for
     * output filtering — e.g. dropping `<|im_end|>` from streamed
     * tokens before rendering them to a UI — and for token-class
     * classification in analysis tools.
     *
     * Out-of-range IDs (>= vocab size) and regular vocab tokens
     * both return `false`. Companion to [`Self::special_token_id`]
     * which goes the other direction (name → ID).
     */
    override fun `isSpecialToken`(`id`: kotlin.UInt): kotlin.Boolean =
        FfiConverterBoolean.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_is_special_token(
                        it,
                        FfiConverterUInt.lower(`id`),
                        _status,
                    )
                }
            },
        )

    /**
     * Short summary of the loaded model (architecture, vocab size,
     * max context, etc.). Returns a `Clone` of the stored metadata.
     */
    override fun `metadata`(): ModelMetadata =
        FfiConverterTypeModelMetadata.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_metadata(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Open a new [`Session`] sharing this engine's model + tokenizer
     * by `Arc` clone. The returned session outlives `&self`; the
     * engine keeps the shared state live for every session it hands
     * out. Cheap — no model load, just config + state allocation.
     */
    override fun `newSession`(`config`: SessionConfig): Session =
        FfiConverterTypeSession.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_new_session(
                        it,
                        FfiConverterTypeSessionConfig.lower(`config`),
                        _status,
                    )
                }
            },
        )

    /**
     * Look up a special token by name (e.g. `<|im_start|>`,
     * `<|im_end|>`, `<|tool_call|>`). Returns `None` if the token
     * isn't defined in the tokenizer's vocab.
     */
    override fun `specialTokenId`(`name`: kotlin.String): kotlin.UInt? =
        FfiConverterOptionalUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_special_token_id(
                        it,
                        FfiConverterString.lower(`name`),
                        _status,
                    )
                }
            },
        )

    /**
     * Transcribe mono `f32` PCM audio (normalized to roughly `[-1.0, 1.0]`) to text using the
     * model's trained `"Perform ASR."` chat mode. `sample_rate` must match the audio encoder's
     * expected rate (resample beforehand if needed). Requires an audio-capable bundle; a text-only
     * model returns an [`FfiError`] for unsupported modality.
     *
     * Blocking: runs a full prefill + greedy decode. Foreign async runtimes should wrap the call in
     * `spawn_blocking` / its equivalent.
     */
    @Throws(FfiException::class)
    override fun `transcribe`(
        `pcm`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    ): kotlin.String =
        FfiConverterString.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_transcribe(
                        it,
                        FfiConverterSequenceFloat.lower(`pcm`),
                        FfiConverterUInt.lower(`sampleRate`),
                        _status,
                    )
                }
            },
        )

    /**
     * Total vocabulary size — the number of distinct token IDs the
     * model can emit. Sourced from the model's config (matches
     * [`ModelMetadata::vocab_size`]) rather than the tokenizer's
     * own count: in healthy models they match, but the model's
     * config is the authoritative range for valid logit indices.
     */
    override fun `vocabSize`(): kotlin.UInt =
        FfiConverterUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_ceraengine_vocab_size(
                        it,
                        _status,
                    )
                }
            },
        )

    companion object {
        /**
         * Load a model by LeapBundles ID + quantization selector, e.g.
         * `from_bundle_id("LFM2-1.2B-GGUF", "Q4_0", config)`. Resolves
         * to the matching `<bundle_id>/<quant>.json` manifest under
         * `huggingface.co/LiquidAI/LeapBundles` and downloads whatever
         * isn't already in `config.bundle_repo`'s on-disk cache.
         *
         * `config.bundle_repo` must be set; otherwise this returns an
         * [`FfiError::Backend`] telling the caller to construct a
         * [`BundleRepo`] and attach it. Idempotent across calls — the
         * repo's cache deduplicates subsequent downloads.
         *
         * Blocking: this call fetches over the network on first run +
         * opens / parses the GGUF. Foreign async runtimes should wrap
         * the call in `spawn_blocking` / its equivalent. (An async
         * counterpart matching `generate_async` could be added later;
         * not in this PR.)
         */
        @Throws(FfiException::class)
        fun `fromBundleId`(
            `bundleId`: kotlin.String,
            `quant`: kotlin.String,
            `config`: EngineConfig,
        ): CeraEngine =
            FfiConverterTypeCeraEngine.lift(
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_constructor_ceraengine_from_bundle_id(
                        FfiConverterString.lower(`bundleId`),
                        FfiConverterString.lower(`quant`),
                        FfiConverterTypeEngineConfig.lower(`config`),
                        _status,
                    )
                },
            )

        /**
         * Async variant of [`CeraEngine::from_bundle_id`] — offloads the
         * manifest + GGUF download and the engine construction onto a
         * tokio blocking worker so the caller's async context isn't
         * stalled. Foreign async runtimes (Kotlin coroutines, Swift
         * `async`, Python `asyncio`) `.await` it directly.
         *
         * `config.bundle_repo` must be set (same constraint as the sync
         * twin); construct a [`BundleRepo`] rooted at a persistent cache
         * directory and attach it to the config before calling.
         *
         * Cancellation semantics (weaker than [`Session::generate_async`]):
         * dropping the returned future drops the `AbortOnDrop` guard,
         * which calls `AbortHandle::abort` on the spawned task. That
         * cancels the task if it's still queued on tokio's blocking
         * pool, so a not-yet-started download never runs. But if the
         * task has started, abort is a no-op — the download is a
         * `reqwest::blocking` call with no cooperative cancel point,
         * and cera's engine-construction code (tokenizer build, model
         * load, KV alloc) also isn't interruptible. In that case the
         * task runs to completion and the engine is constructed then
         * dropped; the downloaded bundle stays cached, so the caller's
         * next attempt starts from that cache hit. Bandwidth isn't
         * wasted, it's just shifted.
         *
         * `JoinError` from a panicking blocking closure surfaces as
         * [`FfiError::Backend`] with a diagnostic prefix, same as
         * [`Session::generate_async`].
         */
        @Throws(FfiException::class)
        @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
        suspend fun `fromBundleIdAsync`(
            `bundleId`: kotlin.String,
            `quant`: kotlin.String,
            `config`: EngineConfig,
        ): CeraEngine =
            uniffiRustCallAsync(
                UniffiLib.uniffi_cera_ffi_fn_constructor_ceraengine_from_bundle_id_async(
                    FfiConverterString.lower(`bundleId`),
                    FfiConverterString.lower(`quant`),
                    FfiConverterTypeEngineConfig.lower(`config`),
                ),
                { future, callback, continuation -> UniffiLib.ffi_cera_ffi_rust_future_poll_u64(future, callback, continuation) },
                { future, continuation -> UniffiLib.ffi_cera_ffi_rust_future_complete_u64(future, continuation) },
                { future -> UniffiLib.ffi_cera_ffi_rust_future_free_u64(future) },
                // lift function
                { FfiConverterTypeCeraEngine.lift(it) },
                // Error FFI converter
                FfiException.ErrorHandler,
            )

        /**
         * Load a model from a local filesystem path. Accepts the same
         * inputs as the native [`cera::CeraEngine::from_path`]: a bare
         * `.gguf`, a LeapBundles `.json` manifest, or a directory
         * containing exactly one `.json` manifest.
         *
         * If the manifest carries `http(s)://` URLs for its files,
         * `config.bundle_repo` must be set — otherwise those URLs fail
         * to resolve. For a pure-local workflow (bundle already on
         * disk) leave `bundle_repo = None`.
         */
        @Throws(FfiException::class)
        fun `fromPath`(
            `path`: kotlin.String,
            `config`: EngineConfig,
        ): CeraEngine =
            FfiConverterTypeCeraEngine.lift(
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_constructor_ceraengine_from_path(
                        FfiConverterString.lower(`path`),
                        FfiConverterTypeEngineConfig.lower(`config`),
                        _status,
                    )
                },
            )
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeCeraEngine : FfiConverter<CeraEngine, Long> {
    override fun lower(value: CeraEngine): Long = value.uniffiCloneHandle()

    override fun lift(value: Long): CeraEngine = CeraEngine(UniffiWithHandle, value)

    override fun read(buf: ByteBuffer): CeraEngine = lift(buf.getLong())

    override fun allocationSize(value: CeraEngine) = 8UL

    override fun write(
        value: CeraEngine,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * Foreign-trait callback for download progress events from
 * [`BundleRepo::with_progress`]. Implementers (Kotlin class, Swift
 * class, Python subclass) drive a progress UI from these events.
 *
 * All methods are required from foreign implementations (UniFFI
 * 0.31 foreign traits don't carry Rust default-impl fallbacks).
 *
 * Threading: `on_progress` is invoked from the thread driving the
 * download. For sync `from_bundle_id` that's the caller's thread;
 * for `from_bundle_id_async` it's a tokio blocking worker. If your
 * progress UI requires marshalling onto a UI thread (`@MainActor`,
 * `runOnUiThread`, etc.), the implementer is responsible for the
 * dispatch.
 */
public interface DownloadProgressSink {
    /**
     * Called periodically during a download. `bytes_downloaded` is
     * monotonic across the same call's stream; `total_bytes` is the
     * `Content-Length` reported by the server (may be `None` for
     * chunked-transfer responses or when HEAD didn't surface a
     * length). Same `url` value across all calls for one download
     * — pattern-match on it to drive a per-file UI within a
     * multi-file bundle download.
     *
     * Throttled by `cera-core` to ~256 KB granularity + one final
     * callback at end-of-stream so the consumer always sees the
     * final byte count.
     */
    fun `onProgress`(
        `url`: kotlin.String,
        `bytesDownloaded`: kotlin.ULong,
        `totalBytes`: kotlin.ULong?,
    )

    companion object
}

/**
 * Foreign-trait callback for download progress events from
 * [`BundleRepo::with_progress`]. Implementers (Kotlin class, Swift
 * class, Python subclass) drive a progress UI from these events.
 *
 * All methods are required from foreign implementations (UniFFI
 * 0.31 foreign traits don't carry Rust default-impl fallbacks).
 *
 * Threading: `on_progress` is invoked from the thread driving the
 * download. For sync `from_bundle_id` that's the caller's thread;
 * for `from_bundle_id_async` it's a tokio blocking worker. If your
 * progress UI requires marshalling onto a UI thread (`@MainActor`,
 * `runOnUiThread`, etc.), the implementer is responsible for the
 * dispatch.
 */
open class DownloadProgressSinkImpl :
    Disposable,
    AutoCloseable,
    DownloadProgressSink {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_downloadprogresssink(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_downloadprogresssink(handle, status)
        }
    }

    /**
     * Called periodically during a download. `bytes_downloaded` is
     * monotonic across the same call's stream; `total_bytes` is the
     * `Content-Length` reported by the server (may be `None` for
     * chunked-transfer responses or when HEAD didn't surface a
     * length). Same `url` value across all calls for one download
     * — pattern-match on it to drive a per-file UI within a
     * multi-file bundle download.
     *
     * Throttled by `cera-core` to ~256 KB granularity + one final
     * callback at end-of-stream so the consumer always sees the
     * final byte count.
     */
    override fun `onProgress`(
        `url`: kotlin.String,
        `bytesDownloaded`: kotlin.ULong,
        `totalBytes`: kotlin.ULong?,
    ) = callWithHandle {
        uniffiRustCall { _status ->
            UniffiLib.uniffi_cera_ffi_fn_method_downloadprogresssink_on_progress(
                it,
                FfiConverterString.lower(`url`),
                FfiConverterULong.lower(`bytesDownloaded`),
                FfiConverterOptionalULong.lower(`totalBytes`),
                _status,
            )
        }
    }

    /**
     * @suppress
     */
    companion object
}

// Put the implementation in an object so we don't pollute the top-level namespace
internal object uniffiCallbackInterfaceDownloadProgressSink {
    internal object `onProgress` : UniffiCallbackInterfaceDownloadProgressSinkMethod0 {
        override fun callback(
            `uniffiHandle`: Long,
            `url`: RustBuffer.ByValue,
            `bytesDownloaded`: Long,
            `totalBytes`: RustBuffer.ByValue,
            `uniffiOutReturn`: Pointer,
            uniffiCallStatus: UniffiRustCallStatus,
        ) {
            val uniffiObj = FfiConverterTypeDownloadProgressSink.handleMap.get(uniffiHandle)
            val makeCall = {  uniffiObj.`onProgress`(
                FfiConverterString.lift(`url`),
                FfiConverterULong.lift(`bytesDownloaded`),
                FfiConverterOptionalULong.lift(`totalBytes`),
            )
            }
            val writeReturn = { _: Unit -> Unit }
            uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
        }
    }

    internal object uniffiFree : UniffiCallbackInterfaceFree {
        override fun callback(handle: Long) {
            FfiConverterTypeDownloadProgressSink.handleMap.remove(handle)
        }
    }

    internal object uniffiClone : UniffiCallbackInterfaceClone {
        override fun callback(handle: Long): Long = FfiConverterTypeDownloadProgressSink.handleMap.clone(handle)
    }

    internal var vtable =
        UniffiVTableCallbackInterfaceDownloadProgressSink.UniffiByValue(
            uniffiFree,
            uniffiClone,
            `onProgress`,
        )

    // Registers the foreign callback with the Rust side.
    // This method is generated for each callback interface.
    internal fun register(lib: UniffiLib) {
        lib.uniffi_cera_ffi_fn_init_callback_vtable_downloadprogresssink(vtable)
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeDownloadProgressSink : FfiConverter<DownloadProgressSink, Long> {
    internal val handleMap = UniffiHandleMap<DownloadProgressSink>()

    override fun lower(value: DownloadProgressSink): Long {
        if (value is DownloadProgressSinkImpl) {
            // Rust-implemented object.  Clone the handle and return it
            return value.uniffiCloneHandle()
        } else {
            // Kotlin object, generate a new vtable handle and return that.
            return handleMap.insert(value)
        }
    }

    override fun lift(value: Long): DownloadProgressSink {
        if ((value and 1.toLong()) == 0.toLong()) {
            // Rust-generated handle, construct a new class that uses the handle to implement the
            // interface
            return DownloadProgressSinkImpl(UniffiWithHandle, value)
        } else {
            // Kotlin-generated handle, get the object from the handle map
            return handleMap.remove(value)
        }
    }

    override fun read(buf: ByteBuffer): DownloadProgressSink = lift(buf.getLong())

    override fun allocationSize(value: DownloadProgressSink) = 8UL

    override fun write(
        value: DownloadProgressSink,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * A loaded LoRA adapter, ready to attach to a [`Session`] via
 * [`Session::attach_lora`]. Load it once and share the handle across sessions —
 * it's reference-counted internally, so attaching to multiple sessions doesn't
 * re-parse or re-allocate the factors.
 */
public interface LoraAdaptersInterface {
    /**
     * Number of `(layer, target)` low-rank deltas the adapter carries — for
     * diagnostics / logging.
     */
    fun `targetCount`(): kotlin.UInt

    companion object
}

/**
 * A loaded LoRA adapter, ready to attach to a [`Session`] via
 * [`Session::attach_lora`]. Load it once and share the handle across sessions —
 * it's reference-counted internally, so attaching to multiple sessions doesn't
 * re-parse or re-allocate the factors.
 */
open class LoraAdapters :
    Disposable,
    AutoCloseable,
    LoraAdaptersInterface {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_loraadapters(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_loraadapters(handle, status)
        }
    }

    /**
     * Number of `(layer, target)` low-rank deltas the adapter carries — for
     * diagnostics / logging.
     */
    override fun `targetCount`(): kotlin.UInt =
        FfiConverterUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_loraadapters_target_count(
                        it,
                        _status,
                    )
                }
            },
        )

    companion object {
        /**
         * Load a llama.cpp-format GGUF adapter (`convert_lora_to_gguf` output) from
         * a local path. `alpha` is read from the adapter's `adapter.lora.alpha`
         * metadata (missing ⇒ scale = 1).
         */
        @Throws(FfiException::class)
        fun `fromGguf`(`path`: kotlin.String): LoraAdapters =
            FfiConverterTypeLoraAdapters.lift(
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_constructor_loraadapters_from_gguf(FfiConverterString.lower(`path`), _status)
                },
            )

        /**
         * Load a PEFT `.safetensors` adapter from a local path. PEFT stores `alpha`
         * in a sibling `adapter_config.json`, so pass it explicitly here (`None` ⇒
         * scale = 1, i.e. `alpha == rank`).
         */
        @Throws(FfiException::class)
        fun `fromSafetensors`(
            `path`: kotlin.String,
            `alpha`: kotlin.Float?,
        ): LoraAdapters =
            FfiConverterTypeLoraAdapters.lift(
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_constructor_loraadapters_from_safetensors(
                        FfiConverterString.lower(`path`),
                        FfiConverterOptionalFloat.lower(`alpha`),
                        _status,
                    )
                },
            )
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeLoraAdapters : FfiConverter<LoraAdapters, Long> {
    override fun lower(value: LoraAdapters): Long = value.uniffiCloneHandle()

    override fun lift(value: Long): LoraAdapters = LoraAdapters(UniffiWithHandle, value)

    override fun read(buf: ByteBuffer): LoraAdapters = lift(buf.getLong())

    override fun allocationSize(value: LoraAdapters) = 8UL

    override fun write(
        value: LoraAdapters,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * Streaming sink for decode output. Foreign callers implement this
 * trait (Kotlin class, Swift class, Python subclass) and pass an
 * `Arc<dyn ModalitySink>` to [`Session::generate_streaming`] to
 * receive tokens + audio frames + the finish reason as they happen.
 *
 * All methods are required from foreign implementations (UniFFI 0.28
 * foreign traits don't carry Rust's default-impl fallbacks). Callers
 * that don't care about a modality can provide an empty body.
 *
 * Threading: every method is invoked on the same Rust thread running
 * `generate` — the decode thread. If the foreign runtime requires
 * marshalling onto a different thread (e.g. Swift's `@MainActor`) it
 * is the implementer's responsibility to dispatch the call there.
 */
public interface ModalitySink {
    /**
     * Called with each chunk of generated token IDs. Ownership of the
     * `Vec<u32>` is transferred to the callback, so implementations
     * may retain or store it directly if needed — no clone required.
     */
    fun `onTextTokens`(`tokens`: List<kotlin.UInt>)

    /**
     * Called with each chunk of generated PCM audio samples. Not
     * called for text-only models; LFM2-Audio-class models emit here.
     * The `sample_rate` is the model's native output rate (typically
     * 24000 for LFM2-Audio) and is stable across the whole generate
     * call.
     */
    fun `onAudioFrames`(
        `pcm`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    )

    /**
     * Called exactly once per [`Session::generate_streaming`] call,
     * as the last thing before the wrapper returns. Fires for both
     * success (`MaxTokens`, `Stop`, `Cancelled`, `ContextFull`) and
     * failure paths: on error the wrapper synthesizes a
     * [`FinishReason::Error`] so foreign consumers have a reliable
     * end-of-stream signal regardless of how the call exits.
     */
    fun `onDone`(`reason`: FinishReason)

    companion object
}

/**
 * Streaming sink for decode output. Foreign callers implement this
 * trait (Kotlin class, Swift class, Python subclass) and pass an
 * `Arc<dyn ModalitySink>` to [`Session::generate_streaming`] to
 * receive tokens + audio frames + the finish reason as they happen.
 *
 * All methods are required from foreign implementations (UniFFI 0.28
 * foreign traits don't carry Rust's default-impl fallbacks). Callers
 * that don't care about a modality can provide an empty body.
 *
 * Threading: every method is invoked on the same Rust thread running
 * `generate` — the decode thread. If the foreign runtime requires
 * marshalling onto a different thread (e.g. Swift's `@MainActor`) it
 * is the implementer's responsibility to dispatch the call there.
 */
open class ModalitySinkImpl :
    Disposable,
    AutoCloseable,
    ModalitySink {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_modalitysink(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_modalitysink(handle, status)
        }
    }

    /**
     * Called with each chunk of generated token IDs. Ownership of the
     * `Vec<u32>` is transferred to the callback, so implementations
     * may retain or store it directly if needed — no clone required.
     */
    override fun `onTextTokens`(`tokens`: List<kotlin.UInt>) =
        callWithHandle {
            uniffiRustCall { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_modalitysink_on_text_tokens(
                    it,
                    FfiConverterSequenceUInt.lower(`tokens`),
                    _status,
                )
            }
        }

    /**
     * Called with each chunk of generated PCM audio samples. Not
     * called for text-only models; LFM2-Audio-class models emit here.
     * The `sample_rate` is the model's native output rate (typically
     * 24000 for LFM2-Audio) and is stable across the whole generate
     * call.
     */
    override fun `onAudioFrames`(
        `pcm`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    ) = callWithHandle {
        uniffiRustCall { _status ->
            UniffiLib.uniffi_cera_ffi_fn_method_modalitysink_on_audio_frames(
                it,
                FfiConverterSequenceFloat.lower(`pcm`),
                FfiConverterUInt.lower(`sampleRate`),
                _status,
            )
        }
    }

    /**
     * Called exactly once per [`Session::generate_streaming`] call,
     * as the last thing before the wrapper returns. Fires for both
     * success (`MaxTokens`, `Stop`, `Cancelled`, `ContextFull`) and
     * failure paths: on error the wrapper synthesizes a
     * [`FinishReason::Error`] so foreign consumers have a reliable
     * end-of-stream signal regardless of how the call exits.
     */
    override fun `onDone`(`reason`: FinishReason) =
        callWithHandle {
            uniffiRustCall { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_modalitysink_on_done(
                    it,
                    FfiConverterTypeFinishReason.lower(`reason`),
                    _status,
                )
            }
        }

    /**
     * @suppress
     */
    companion object
}

// Put the implementation in an object so we don't pollute the top-level namespace
internal object uniffiCallbackInterfaceModalitySink {
    internal object `onTextTokens` : UniffiCallbackInterfaceModalitySinkMethod0 {
        override fun callback(
            `uniffiHandle`: Long,
            `tokens`: RustBuffer.ByValue,
            `uniffiOutReturn`: Pointer,
            uniffiCallStatus: UniffiRustCallStatus,
        ) {
            val uniffiObj = FfiConverterTypeModalitySink.handleMap.get(uniffiHandle)
            val makeCall = {  uniffiObj.`onTextTokens`(
                FfiConverterSequenceUInt.lift(`tokens`),
            )
            }
            val writeReturn = { _: Unit -> Unit }
            uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
        }
    }

    internal object `onAudioFrames` : UniffiCallbackInterfaceModalitySinkMethod1 {
        override fun callback(
            `uniffiHandle`: Long,
            `pcm`: RustBuffer.ByValue,
            `sampleRate`: Int,
            `uniffiOutReturn`: Pointer,
            uniffiCallStatus: UniffiRustCallStatus,
        ) {
            val uniffiObj = FfiConverterTypeModalitySink.handleMap.get(uniffiHandle)
            val makeCall = {  uniffiObj.`onAudioFrames`(
                FfiConverterSequenceFloat.lift(`pcm`),
                FfiConverterUInt.lift(`sampleRate`),
            )
            }
            val writeReturn = { _: Unit -> Unit }
            uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
        }
    }

    internal object `onDone` : UniffiCallbackInterfaceModalitySinkMethod2 {
        override fun callback(
            `uniffiHandle`: Long,
            `reason`: RustBuffer.ByValue,
            `uniffiOutReturn`: Pointer,
            uniffiCallStatus: UniffiRustCallStatus,
        ) {
            val uniffiObj = FfiConverterTypeModalitySink.handleMap.get(uniffiHandle)
            val makeCall = {  uniffiObj.`onDone`(
                FfiConverterTypeFinishReason.lift(`reason`),
            )
            }
            val writeReturn = { _: Unit -> Unit }
            uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
        }
    }

    internal object uniffiFree : UniffiCallbackInterfaceFree {
        override fun callback(handle: Long) {
            FfiConverterTypeModalitySink.handleMap.remove(handle)
        }
    }

    internal object uniffiClone : UniffiCallbackInterfaceClone {
        override fun callback(handle: Long): Long = FfiConverterTypeModalitySink.handleMap.clone(handle)
    }

    internal var vtable =
        UniffiVTableCallbackInterfaceModalitySink.UniffiByValue(
            uniffiFree,
            uniffiClone,
            `onTextTokens`,
            `onAudioFrames`,
            `onDone`,
        )

    // Registers the foreign callback with the Rust side.
    // This method is generated for each callback interface.
    internal fun register(lib: UniffiLib) {
        lib.uniffi_cera_ffi_fn_init_callback_vtable_modalitysink(vtable)
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeModalitySink : FfiConverter<ModalitySink, Long> {
    internal val handleMap = UniffiHandleMap<ModalitySink>()

    override fun lower(value: ModalitySink): Long {
        if (value is ModalitySinkImpl) {
            // Rust-implemented object.  Clone the handle and return it
            return value.uniffiCloneHandle()
        } else {
            // Kotlin object, generate a new vtable handle and return that.
            return handleMap.insert(value)
        }
    }

    override fun lift(value: Long): ModalitySink {
        if ((value and 1.toLong()) == 0.toLong()) {
            // Rust-generated handle, construct a new class that uses the handle to implement the
            // interface
            return ModalitySinkImpl(UniffiWithHandle, value)
        } else {
            // Kotlin-generated handle, get the object from the handle map
            return handleMap.remove(value)
        }
    }

    override fun read(buf: ByteBuffer): ModalitySink = lift(buf.getLong())

    override fun allocationSize(value: ModalitySink) = 8UL

    override fun write(
        value: ModalitySink,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

// This template implements a class for working with a Rust struct via a handle
// to the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
//   * Each instance holds an opaque handle to the underlying Rust struct.
//     Method calls need to read this handle from the object's state and pass it in to
//     the Rust FFI.
//
//   * When an instance is no longer needed, its handle should be passed to a
//     special destructor function provided by the Rust FFI, which will drop the
//     underlying Rust struct.
//
//   * Given an instance, calling code is expected to call the special
//     `destroy` method in order to free it after use, either by calling it explicitly
//     or by using a higher-level helper like the `use` method. Failing to do so risks
//     leaking the underlying Rust struct.
//
//   * We can't assume that calling code will do the right thing, and must be prepared
//     to handle Kotlin method calls executing concurrently with or even after a call to
//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
//   * We must never allow Rust code to operate on the underlying Rust struct after
//     the destructor has been called, and must never call the destructor more than once.
//     Doing so may trigger memory unsafety.
//
//   * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
//     is implemented to call the destructor when the Kotlin object becomes unreachable.
//     This is done in a background thread. This is not a panacea, and client code should be aware that
//      1. the thread may starve if some there are objects that have poorly performing
//     `drop` methods or do significant work in their `drop` methods.
//      2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
//         or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the handle, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
//    * Thread A starts a method call, reads the value of the handle, but is interrupted
//      before it can pass the handle over the FFI to Rust.
//    * Thread B calls `destroy` and frees the underlying Rust struct.
//    * Thread A resumes, passing the already-read handle value to Rust and triggering
//      a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
//      The initial value for the flag is false.
//
//    * At the start of each method call, we atomically check the counter.
//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
//    * At the end of each method call, we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
//    * When `destroy` is called, we atomically flip the flag from false to true.
//      If the flag was already true we silently fail.
//      Otherwise we atomically decrement and check the counter.
//      If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//

/**
 * Stateful inference handle. Wraps [`cera::Session`] behind a
 * `Mutex` so UniFFI's `Arc<Session>` shape works with methods that
 * need `&mut self` on the inner session (prefill, generate, reset).
 *
 * Call [`CeraEngine::new_session`] to open a session; the engine's
 * `Arc<Model>` and `Arc<BpeTokenizer>` are cloned into the new
 * session so it outlives the engine handle across FFI calls.
 */
public interface SessionInterface {
    /**
     * Append PCM audio samples (mono `f32`, normalized to roughly
     * `[-1.0, 1.0]`) at `sample_rate` Hz. The audio is encoded via
     * the bundle's mmproj (`AudioEncoderWeights`) and prefilled
     * into the LLM as soft tokens — see
     * [`cera::Session::append_audio`] for the underlying flow.
     *
     * `CeraEngine::new_session` auto-attaches the encoder when the
     * loaded bundle's `inference_type == LlamaCppLfm2AudioV1` and
     * has `multimodal_projector` set in the manifest, so FFI
     * consumers don't need any separate "load encoder" call.
     * Bundles where the manifest omits `multimodal_projector`
     * silently end up with no encoder attached (no log). Bundles
     * where the file is named but fails to open or parse log a
     * `tracing::warn!` at `CeraEngine` construction. Both cases
     * surface here as a "no audio encoder attached" `Backend`
     * error.
     *
     * `sample_rate` must be 16000 — resampling is out of scope.
     * Callers should resample externally before passing samples in.
     *
     * **Marshaling cost**: UniFFI maps `Vec<f32>` to `List<Float>`
     * in Kotlin and `[Float]` in Swift. The Kotlin side boxes each
     * `Float` to `java.lang.Float`, a ~4× memory overhead vs the
     * underlying `f32` wire bytes — negligible for O(seconds ×
     * sample-rate) chunks but worth knowing if you're streaming
     * continuous audio in tight loops.
     *
     * Errors:
     * - `EmptyInput` either when `samples` is empty (fast-fail,
     * enforced here for parity with `append_text` /
     * `append_tokens`) **or** when the audio is too short to
     * produce any encoder frames (e.g. shorter than one
     * center-padded STFT window).
     * - `UnsupportedModality` if the loaded model's
     * [`ModalityCapabilities::audio_in`] is `false`.
     * - `Backend(...)` for sample-rate mismatch, encoder/LLM
     * `hidden_size` mismatch, or missing encoder. The latter
     * includes both "manifest didn't list a mmproj" (no warn
     * logged) and "mmproj listed but failed to open/parse"
     * (warn logged at `CeraEngine::from_path`).
     * - `ContextOverflow` / `Cancelled` propagate from the
     * underlying prefill.
     */
    fun `appendAudio`(
        `samples`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    )

    /**
     * Append an encoded image (PNG / JPEG bytes, auto-detected) to the
     * context. The image is decoded, resized, normalized, and run
     * through the bundle's vision mmproj (`VisionEncoderWeights`), then
     * prefilled into the LLM as soft tokens — see
     * [`cera::Session::append_image`] for the underlying flow.
     *
     * `CeraEngine::new_session` auto-attaches the vision encoder when
     * the loaded bundle's `inference_type` is a VL type with
     * `multimodal_projector` set in the manifest, so FFI consumers
     * don't need a separate "load encoder" call. Bundles whose
     * manifest omits the mmproj end up with no encoder attached (no
     * log); bundles where it's named but fails to open/parse log a
     * `tracing::warn!` at `CeraEngine` construction. Both surface here
     * as a "no vision encoder attached" `Backend` error.
     *
     * `max_long_size` controls the per-call cap on the longest side of
     * the *encoded* image, with three cases distinguished so the
     * session default stays reachable through FFI:
     * - `None` — defer to the session default set via
     * [`Self::set_image_max_long_size`] (no cap if none was set).
     * - `Some(0)` — explicitly force *no cap* for this call, ignoring
     * the session default.
     * - `Some(n)` (`n > 0`) — cap this call at `n`, overriding the
     * session default.
     *
     * When a cap applies, the resize target is shrunk
     * (aspect-preserving) so its longer side is at most `n` pixels,
     * floored at one aligned patch block (so a very small `n` can still
     * round up to that minimum) — a quality/cost knob (smaller = fewer
     * image tokens, faster, less detail). It only shrinks (never
     * upscales) and takes precedence over the model's
     * minimum-resolution floor. The cap bounds the *encode*, not the
     * *decode* (a huge source image is still decoded, bounded by
     * internal limits).
     *
     * **Placement matters.** Prefer driving multimodal turns through
     * the chat template; calling this at the wrong stream position
     * (outside the model's image-marker envelope) leaves the LLM
     * unable to interpret the embeddings as visual content. See
     * [`cera::Session::append_image`] for the marker recipe.
     *
     * Errors (capability is checked before emptiness, matching core):
     * - `UnsupportedModality` if the loaded model's
     * [`ModalityCapabilities::image_in`] is `false`.
     * - `EmptyInput` when `bytes` is empty (on a VL session).
     * - `Backend(...)` for image decode failure, missing vision
     * encoder, or encoder/LLM `projection_dim` ≠ `hidden_size`
     * mismatch.
     * - `ContextOverflow` / `Cancelled` propagate from the
     * underlying prefill.
     */
    fun `appendImage`(
        `bytes`: kotlin.ByteArray,
        `maxLongSize`: kotlin.UInt?,
    )

    /**
     * Append raw text to the context, running a prefill over just
     * the new tokens. `EmptyInput` error if `text` is empty.
     */
    fun `appendText`(`text`: kotlin.String)

    /**
     * Append pre-tokenized IDs. Useful when the caller has its own
     * tokenizer + chat-template pipeline.
     */
    fun `appendTokens`(`tokens`: List<kotlin.UInt>)

    /**
     * Attach a [`LoraAdapters`] to this session (generated as `attachLora` in
     * Swift/Kotlin — this is the engine's equivalent of a `setLoraAdapters`
     * call). It's applied to every subsequent forward pass — generation **and**
     * hidden-states extraction — until removed or replaced (hot-swap), and is
     * preserved across [`Self::reset`]. Returns [`FfiError::LoraParse`] if the
     * adapter's dimensions don't match the loaded model. Only affects tokens
     * processed after the call (doesn't retroactively re-adapt cached KV).
     */
    fun `attachLora`(`adapters`: LoraAdapters)

    /**
     * Signal in-flight `generate()` to exit with
     * `FinishReason::Cancelled` at the next between-token check.
     * Safe from any thread. No-op if no `generate()` is running.
     */
    fun `cancel`()

    /**
     * Capabilities reported by the loaded model. Cheap — reads a
     * cached copy, no lock.
     */
    fun `capabilities`(): ModalityCapabilities

    /**
     * Clear the cancel flag without dropping any session state.
     * Use this after observing a cancellation signal — either
     * [`FfiError::Cancelled`] from `append_text` / `append_tokens`
     * / `append_audio` (mid-prefill cancellation surfaces
     * typed), or `finish_reason = "Cancelled"` on the
     * [`GenerateOutput`] returned from `generate` (cancellation
     * during decode is reported as an `Ok` with that finish
     * reason rather than an `Err`) — when you want to resume
     * work on the same session without losing the accumulated
     * KV cache.
     *
     * Compared to [`Self::reset`]:
     * - `clear_cancel`: keeps KV state + position + sampler
     * intact; only flips the cancel atomic back to `false`.
     * Use for "interrupted but continuing" flows.
     * - `reset`: drops KV cache + position + last logits +
     * re-seeds sampler. Use for "clear conversation" flows.
     *
     * Atomic-backed; no mutex acquire, infallible, safe from
     * any thread (mirrors the shape of [`Self::cancel`]).
     */
    fun `clearCancel`()

    /**
     * Run autoregressive decode and return all emitted tokens +
     * a summary. Synchronous — the call blocks until the decode
     * loop exits (`max_tokens`, EOS, `cancel()`, or error).
     *
     * For streaming (per-chunk delivery) and async, see the PR 4 /
     * PR 5 follow-ups in `cera-ffi/README.md`.
     */
    fun `generate`(`opts`: GenerateOpts): GenerateOutput

    /**
     * Async variant of [`Session::generate`] — runs buffered decode
     * (returning every emitted token + a summary) on a tokio blocking
     * worker so the caller's async context isn't stalled by the
     * synchronous decode loop.
     *
     * Cancellation: dropping the returned future (Kotlin coroutine
     * scope exit, Swift `Task.cancel`, Python `asyncio.Task.cancel`)
     * triggers both an abort of the queued `spawn_blocking` task (so
     * a not-yet-started decode never runs) and a
     * [`Session::cancel`] call (so an in-flight decode exits at its
     * next between-token check with [`FinishReason::Cancelled`]).
     * Either path releases the session mutex; subsequent calls see
     * a clean session. You can also call [`Session::cancel`]
     * directly from any thread to trigger the same in-flight exit
     * without dropping the future. See `AsyncCancelGuard` for the
     * full rationale.
     *
     * On error the wrapper performs the same poisoned-mutex handling
     * as sync [`Session::generate`]. `JoinError` from a panic in the
     * blocking closure surfaces as [`FfiError::Backend`] with a
     * diagnostic prefix.
     */
    suspend fun `generateAsync`(`opts`: GenerateOpts): GenerateOutput

    /**
     * Run autoregressive decode, streaming every token (and audio
     * frame, for audio-capable models) to a foreign [`ModalitySink`]
     * as soon as it's produced. Returns only a [`GenerateSummary`] —
     * token IDs are delivered through `sink.on_text_tokens`, not a
     * return value.
     *
     * Synchronous: the call blocks on the decode thread and each
     * `sink` method runs on that same thread before decoding
     * continues. For async, see PR 5 in `cera-ffi/README.md`.
     *
     * **Callback reentrancy — deadlock hazard.** The session mutex is
     * held for the entire call, and sink callbacks run while that
     * lock is held. Calling back into methods that also take the
     * mutex ([`Session::append_text`], [`Session::append_tokens`],
     * [`Session::generate`], [`Session::generate_streaming`],
     * [`Session::reset`]) from inside a sink method will deadlock.
     * [`Session::cancel`] and [`Session::position`] are atomic-backed
     * and safe to call from the sink or from any other thread.
     *
     * Cancellation: call [`Session::cancel`] from any thread (or from
     * inside a sink callback on this thread) to terminate the loop at
     * the next between-token check; `sink.on_done` fires with
     * [`FinishReason::Cancelled`].
     *
     * End-of-stream guarantee: `sink.on_done` fires exactly once per
     * call, even on error paths. If the underlying decode returns an
     * error before reaching its own `on_done` call (e.g.,
     * `EmptyInput` with no prefill logits), the wrapper synthesizes
     * a terminal `on_done(FinishReason::Error { message })` so
     * foreign consumers have a reliable end-of-stream signal
     * regardless of how the call exits.
     */
    fun `generateStreaming`(
        `opts`: GenerateOpts,
        `sink`: ModalitySink,
    ): GenerateSummary

    /**
     * Async variant of [`Session::generate_streaming`] — delivers
     * tokens and audio frames to the foreign [`ModalitySink`] as the
     * decode loop produces them, from within a blocking worker so
     * the caller's async runtime stays responsive.
     *
     * Sink callbacks run on the blocking worker thread that's
     * executing the decode — **not** on the caller's async thread.
     * The reentrancy hazard documented on
     * [`Session::generate_streaming`] still applies: sink callbacks
     * that call back into `append_text` / `generate*` / `reset` from
     * inside the session will deadlock on the session mutex.
     * [`Session::cancel`] and [`Session::position`] remain atomic-
     * backed and safe to invoke from any thread (including from
     * inside a callback).
     *
     * Cancellation: dropping the returned future fires the same
     * abort + [`Session::cancel`] pair as [`Session::generate_async`]
     * (see `AsyncCancelGuard`). For an in-flight decode, the loop
     * exits with [`FinishReason::Cancelled`] and the sink's `on_done`
     * fires on the blocking worker before the task completes —
     * foreign consumers get the terminal signal even though they've
     * already stopped awaiting. For a queued-but-not-started decode,
     * abort cancels the task without ever running the closure; no
     * sink callbacks fire for that case (the decode never began).
     */
    suspend fun `generateStreamingAsync`(
        `opts`: GenerateOpts,
        `sink`: ModalitySink,
    ): GenerateSummary

    /**
     * Whether a LoRA adapter is currently attached to this session.
     */
    fun `hasLora`(): kotlin.Boolean

    /**
     * Model hidden dimension `D`. Reshape a raw `[T*D]` byte buffer from
     * [`Self::hidden_states_for_tokens`] into `[T][D]` with this. Lock-free
     * (cached at construction), so — like `position()` — it's safe to call from
     * a `generate_streaming` sink callback.
     */
    fun `hiddenSize`(): kotlin.UInt

    /**
     * Like [`Self::hidden_states_for_tokens`] but tokenizes `text` first
     * (Swift `hiddenStates(for:)`). Returns the same LE-f32 byte layout.
     */
    fun `hiddenStatesForText`(`text`: kotlin.String): kotlin.ByteArray

    /**
     * Per-token last-layer hidden states (post-final-RMSNorm — the llama.cpp
     * `--pooling none` / `llama_get_embeddings_ith` vector) for `tokens`,
     * returned as **little-endian f32 bytes**: `n_tokens * hidden_size * 4`
     * bytes, row-major, token `t` channel `c` at `(t*D + c) * 4`.
     *
     * Bytes (UniFFI `Data` in Swift, `ByteArray` in Kotlin) rather than
     * `List<Float>` to avoid Kotlin's per-element boxing on the potentially
     * large `[T*D]` payload. Swift decodes via `Data.withUnsafeBytes`; reflects
     * the active LoRA once that lands. Side-effect-free: does not disturb the
     * session's generation KV.
     *
     * Like `append_*` / `generate`, this holds the session mutex for the
     * duration of the compute, so it must NOT be called re-entrantly from
     * within a `generate_streaming` sink callback (would self-deadlock).
     *
     * Errors: `EmptyInput` on empty input; `UnsupportedModality` if the backend
     * doesn't implement hidden-state extraction; `InvalidToken` if any id is
     * `>= vocab_size`.
     */
    fun `hiddenStatesForTokens`(`tokens`: List<kotlin.UInt>): kotlin.ByteArray

    /**
     * Mean-pooled hidden state — a single `[hidden_size]` vector (the common
     * classifier path: pool in Rust, ship `D` floats not `T*D`). Returned as
     * `[Float]` / `List<Float>`; only `D` elements, so boxing is negligible.
     */
    fun `hiddenStatesMeanPooled`(`tokens`: List<kotlin.UInt>): List<kotlin.Float>

    /**
     * Current KV position — how many tokens live in the cache.
     * Atomic-backed; safe to call from a different thread while
     * `generate()` is in flight.
     */
    fun `position`(): kotlin.UInt

    /**
     * Remove any attached LoRA adapter, returning to base-model inference.
     */
    fun `removeLora`()

    /**
     * Drop cached state + resample the seed. After `reset()` the
     * session behaves like a freshly-opened one (same
     * model/tokenizer/config, no accumulated context).
     *
     * Returns `Result` so a poisoned-mutex case surfaces as an error
     * instead of panicking across the FFI boundary.
     */
    fun `reset`()

    /**
     * Set a session-default cap on the longest side of an appended
     * image, in pixels (`None` = no cap). Unlike the per-call
     * `max_long_size` argument to [`Self::append_image`], this default
     * is honored by every image-append path the session drives —
     * including chat-template flows — so a host can configure the
     * image-encode budget once. See [`Self::append_image`] for the cap
     * semantics (shrinks the encoded target, never upscales, takes
     * precedence over the model's minimum-resolution floor).
     */
    fun `setImageMaxLongSize`(`maxLongSize`: kotlin.UInt?)

    companion object
}

/**
 * Stateful inference handle. Wraps [`cera::Session`] behind a
 * `Mutex` so UniFFI's `Arc<Session>` shape works with methods that
 * need `&mut self` on the inner session (prefill, generate, reset).
 *
 * Call [`CeraEngine::new_session`] to open a session; the engine's
 * `Arc<Model>` and `Arc<BpeTokenizer>` are cloned into the new
 * session so it outlives the engine handle across FFI calls.
 */
open class Session :
    Disposable,
    AutoCloseable,
    SessionInterface {
    /**
     * @suppress
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(withHandle: UniffiWithHandle, handle: Long) {
        this.handle = handle
        this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(handle))
    }

    /**
     * @suppress
     *
     * This constructor can be used to instantiate a fake object. Only used for tests. Any
     * attempt to actually use an object constructed this way will fail as there is no
     * connected Rust object.
     */
    @Suppress("UNUSED_PARAMETER")
    constructor(noHandle: NoHandle) {
        this.handle = 0
        this.cleanable = null
    }

    protected val handle: Long
    protected val cleanable: UniffiCleaner.Cleanable?

    private val wasDestroyed = AtomicBoolean(false)
    private val callCounter = AtomicLong(1)

    override fun destroy() {
        // Only allow a single call to this method.
        // TODO: maybe we should log a warning if called more than once?
        if (this.wasDestroyed.compareAndSet(false, true)) {
            // This decrement always matches the initial count of 1 given at creation time.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    @Synchronized
    override fun close() {
        this.destroy()
    }

    internal inline fun <R> callWithHandle(block: (handle: Long) -> R): R {
        // Check and increment the call counter, to keep the object alive.
        // This needs a compare-and-set retry loop in case of concurrent updates.
        do {
            val c = this.callCounter.get()
            if (c == 0L) {
                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
            }
            if (c == Long.MAX_VALUE) {
                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
            }
        } while (!this.callCounter.compareAndSet(c, c + 1L))
        // Now we can safely do the method call without the handle being freed concurrently.
        try {
            return block(this.uniffiCloneHandle())
        } finally {
            // This decrement always matches the increment we performed above.
            if (this.callCounter.decrementAndGet() == 0L) {
                cleanable?.clean()
            }
        }
    }

    // Use a static inner class instead of a closure so as not to accidentally
    // capture `this` as part of the cleanable's action.
    private class UniffiCleanAction(
        private val handle: Long,
    ) : Runnable {
        override fun run() {
            if (handle == 0.toLong()) {
                // Fake object created with `NoHandle`, don't try to free.
                return
            }
            uniffiRustCall { status ->
                UniffiLib.uniffi_cera_ffi_fn_free_session(handle, status)
            }
        }
    }

    /**
     * @suppress
     */
    fun uniffiCloneHandle(): Long {
        if (handle == 0.toLong()) {
            throw InternalException("uniffiCloneHandle() called on NoHandle object")
        }
        return uniffiRustCall { status ->
            UniffiLib.uniffi_cera_ffi_fn_clone_session(handle, status)
        }
    }

    /**
     * Append PCM audio samples (mono `f32`, normalized to roughly
     * `[-1.0, 1.0]`) at `sample_rate` Hz. The audio is encoded via
     * the bundle's mmproj (`AudioEncoderWeights`) and prefilled
     * into the LLM as soft tokens — see
     * [`cera::Session::append_audio`] for the underlying flow.
     *
     * `CeraEngine::new_session` auto-attaches the encoder when the
     * loaded bundle's `inference_type == LlamaCppLfm2AudioV1` and
     * has `multimodal_projector` set in the manifest, so FFI
     * consumers don't need any separate "load encoder" call.
     * Bundles where the manifest omits `multimodal_projector`
     * silently end up with no encoder attached (no log). Bundles
     * where the file is named but fails to open or parse log a
     * `tracing::warn!` at `CeraEngine` construction. Both cases
     * surface here as a "no audio encoder attached" `Backend`
     * error.
     *
     * `sample_rate` must be 16000 — resampling is out of scope.
     * Callers should resample externally before passing samples in.
     *
     * **Marshaling cost**: UniFFI maps `Vec<f32>` to `List<Float>`
     * in Kotlin and `[Float]` in Swift. The Kotlin side boxes each
     * `Float` to `java.lang.Float`, a ~4× memory overhead vs the
     * underlying `f32` wire bytes — negligible for O(seconds ×
     * sample-rate) chunks but worth knowing if you're streaming
     * continuous audio in tight loops.
     *
     * Errors:
     * - `EmptyInput` either when `samples` is empty (fast-fail,
     * enforced here for parity with `append_text` /
     * `append_tokens`) **or** when the audio is too short to
     * produce any encoder frames (e.g. shorter than one
     * center-padded STFT window).
     * - `UnsupportedModality` if the loaded model's
     * [`ModalityCapabilities::audio_in`] is `false`.
     * - `Backend(...)` for sample-rate mismatch, encoder/LLM
     * `hidden_size` mismatch, or missing encoder. The latter
     * includes both "manifest didn't list a mmproj" (no warn
     * logged) and "mmproj listed but failed to open/parse"
     * (warn logged at `CeraEngine::from_path`).
     * - `ContextOverflow` / `Cancelled` propagate from the
     * underlying prefill.
     */
    @Throws(FfiException::class)
    override fun `appendAudio`(
        `samples`: List<kotlin.Float>,
        `sampleRate`: kotlin.UInt,
    ) = callWithHandle {
        uniffiRustCallWithError(FfiException) { _status ->
            UniffiLib.uniffi_cera_ffi_fn_method_session_append_audio(
                it,
                FfiConverterSequenceFloat.lower(`samples`),
                FfiConverterUInt.lower(`sampleRate`),
                _status,
            )
        }
    }

    /**
     * Append an encoded image (PNG / JPEG bytes, auto-detected) to the
     * context. The image is decoded, resized, normalized, and run
     * through the bundle's vision mmproj (`VisionEncoderWeights`), then
     * prefilled into the LLM as soft tokens — see
     * [`cera::Session::append_image`] for the underlying flow.
     *
     * `CeraEngine::new_session` auto-attaches the vision encoder when
     * the loaded bundle's `inference_type` is a VL type with
     * `multimodal_projector` set in the manifest, so FFI consumers
     * don't need a separate "load encoder" call. Bundles whose
     * manifest omits the mmproj end up with no encoder attached (no
     * log); bundles where it's named but fails to open/parse log a
     * `tracing::warn!` at `CeraEngine` construction. Both surface here
     * as a "no vision encoder attached" `Backend` error.
     *
     * `max_long_size` controls the per-call cap on the longest side of
     * the *encoded* image, with three cases distinguished so the
     * session default stays reachable through FFI:
     * - `None` — defer to the session default set via
     * [`Self::set_image_max_long_size`] (no cap if none was set).
     * - `Some(0)` — explicitly force *no cap* for this call, ignoring
     * the session default.
     * - `Some(n)` (`n > 0`) — cap this call at `n`, overriding the
     * session default.
     *
     * When a cap applies, the resize target is shrunk
     * (aspect-preserving) so its longer side is at most `n` pixels,
     * floored at one aligned patch block (so a very small `n` can still
     * round up to that minimum) — a quality/cost knob (smaller = fewer
     * image tokens, faster, less detail). It only shrinks (never
     * upscales) and takes precedence over the model's
     * minimum-resolution floor. The cap bounds the *encode*, not the
     * *decode* (a huge source image is still decoded, bounded by
     * internal limits).
     *
     * **Placement matters.** Prefer driving multimodal turns through
     * the chat template; calling this at the wrong stream position
     * (outside the model's image-marker envelope) leaves the LLM
     * unable to interpret the embeddings as visual content. See
     * [`cera::Session::append_image`] for the marker recipe.
     *
     * Errors (capability is checked before emptiness, matching core):
     * - `UnsupportedModality` if the loaded model's
     * [`ModalityCapabilities::image_in`] is `false`.
     * - `EmptyInput` when `bytes` is empty (on a VL session).
     * - `Backend(...)` for image decode failure, missing vision
     * encoder, or encoder/LLM `projection_dim` ≠ `hidden_size`
     * mismatch.
     * - `ContextOverflow` / `Cancelled` propagate from the
     * underlying prefill.
     */
    @Throws(FfiException::class)
    override fun `appendImage`(
        `bytes`: kotlin.ByteArray,
        `maxLongSize`: kotlin.UInt?,
    ) = callWithHandle {
        uniffiRustCallWithError(FfiException) { _status ->
            UniffiLib.uniffi_cera_ffi_fn_method_session_append_image(
                it,
                FfiConverterByteArray.lower(`bytes`),
                FfiConverterOptionalUInt.lower(`maxLongSize`),
                _status,
            )
        }
    }

    /**
     * Append raw text to the context, running a prefill over just
     * the new tokens. `EmptyInput` error if `text` is empty.
     */
    @Throws(FfiException::class)
    override fun `appendText`(`text`: kotlin.String) =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_append_text(
                    it,
                    FfiConverterString.lower(`text`),
                    _status,
                )
            }
        }

    /**
     * Append pre-tokenized IDs. Useful when the caller has its own
     * tokenizer + chat-template pipeline.
     */
    @Throws(FfiException::class)
    override fun `appendTokens`(`tokens`: List<kotlin.UInt>) =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_append_tokens(
                    it,
                    FfiConverterSequenceUInt.lower(`tokens`),
                    _status,
                )
            }
        }

    /**
     * Attach a [`LoraAdapters`] to this session (generated as `attachLora` in
     * Swift/Kotlin — this is the engine's equivalent of a `setLoraAdapters`
     * call). It's applied to every subsequent forward pass — generation **and**
     * hidden-states extraction — until removed or replaced (hot-swap), and is
     * preserved across [`Self::reset`]. Returns [`FfiError::LoraParse`] if the
     * adapter's dimensions don't match the loaded model. Only affects tokens
     * processed after the call (doesn't retroactively re-adapt cached KV).
     */
    @Throws(FfiException::class)
    override fun `attachLora`(`adapters`: LoraAdapters) =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_attach_lora(
                    it,
                    FfiConverterTypeLoraAdapters.lower(`adapters`),
                    _status,
                )
            }
        }

    /**
     * Signal in-flight `generate()` to exit with
     * `FinishReason::Cancelled` at the next between-token check.
     * Safe from any thread. No-op if no `generate()` is running.
     */
    override fun `cancel`() =
        callWithHandle {
            uniffiRustCall { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_cancel(
                    it,
                    _status,
                )
            }
        }

    /**
     * Capabilities reported by the loaded model. Cheap — reads a
     * cached copy, no lock.
     */
    override fun `capabilities`(): ModalityCapabilities =
        FfiConverterTypeModalityCapabilities.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_capabilities(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Clear the cancel flag without dropping any session state.
     * Use this after observing a cancellation signal — either
     * [`FfiError::Cancelled`] from `append_text` / `append_tokens`
     * / `append_audio` (mid-prefill cancellation surfaces
     * typed), or `finish_reason = "Cancelled"` on the
     * [`GenerateOutput`] returned from `generate` (cancellation
     * during decode is reported as an `Ok` with that finish
     * reason rather than an `Err`) — when you want to resume
     * work on the same session without losing the accumulated
     * KV cache.
     *
     * Compared to [`Self::reset`]:
     * - `clear_cancel`: keeps KV state + position + sampler
     * intact; only flips the cancel atomic back to `false`.
     * Use for "interrupted but continuing" flows.
     * - `reset`: drops KV cache + position + last logits +
     * re-seeds sampler. Use for "clear conversation" flows.
     *
     * Atomic-backed; no mutex acquire, infallible, safe from
     * any thread (mirrors the shape of [`Self::cancel`]).
     */
    override fun `clearCancel`() =
        callWithHandle {
            uniffiRustCall { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_clear_cancel(
                    it,
                    _status,
                )
            }
        }

    /**
     * Run autoregressive decode and return all emitted tokens +
     * a summary. Synchronous — the call blocks until the decode
     * loop exits (`max_tokens`, EOS, `cancel()`, or error).
     *
     * For streaming (per-chunk delivery) and async, see the PR 4 /
     * PR 5 follow-ups in `cera-ffi/README.md`.
     */
    @Throws(FfiException::class)
    override fun `generate`(`opts`: GenerateOpts): GenerateOutput =
        FfiConverterTypeGenerateOutput.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_generate(
                        it,
                        FfiConverterTypeGenerateOpts.lower(`opts`),
                        _status,
                    )
                }
            },
        )

    /**
     * Async variant of [`Session::generate`] — runs buffered decode
     * (returning every emitted token + a summary) on a tokio blocking
     * worker so the caller's async context isn't stalled by the
     * synchronous decode loop.
     *
     * Cancellation: dropping the returned future (Kotlin coroutine
     * scope exit, Swift `Task.cancel`, Python `asyncio.Task.cancel`)
     * triggers both an abort of the queued `spawn_blocking` task (so
     * a not-yet-started decode never runs) and a
     * [`Session::cancel`] call (so an in-flight decode exits at its
     * next between-token check with [`FinishReason::Cancelled`]).
     * Either path releases the session mutex; subsequent calls see
     * a clean session. You can also call [`Session::cancel`]
     * directly from any thread to trigger the same in-flight exit
     * without dropping the future. See `AsyncCancelGuard` for the
     * full rationale.
     *
     * On error the wrapper performs the same poisoned-mutex handling
     * as sync [`Session::generate`]. `JoinError` from a panic in the
     * blocking closure surfaces as [`FfiError::Backend`] with a
     * diagnostic prefix.
     */
    @Throws(FfiException::class)
    @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
    override suspend fun `generateAsync`(`opts`: GenerateOpts): GenerateOutput =
        uniffiRustCallAsync(
            callWithHandle { uniffiHandle ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_generate_async(
                    uniffiHandle,
                    FfiConverterTypeGenerateOpts.lower(`opts`),
                )
            },
            { future, callback, continuation -> UniffiLib.ffi_cera_ffi_rust_future_poll_rust_buffer(future, callback, continuation) },
            { future, continuation -> UniffiLib.ffi_cera_ffi_rust_future_complete_rust_buffer(future, continuation) },
            { future -> UniffiLib.ffi_cera_ffi_rust_future_free_rust_buffer(future) },
            // lift function
            { FfiConverterTypeGenerateOutput.lift(it) },
            // Error FFI converter
            FfiException.ErrorHandler,
        )

    /**
     * Run autoregressive decode, streaming every token (and audio
     * frame, for audio-capable models) to a foreign [`ModalitySink`]
     * as soon as it's produced. Returns only a [`GenerateSummary`] —
     * token IDs are delivered through `sink.on_text_tokens`, not a
     * return value.
     *
     * Synchronous: the call blocks on the decode thread and each
     * `sink` method runs on that same thread before decoding
     * continues. For async, see PR 5 in `cera-ffi/README.md`.
     *
     * **Callback reentrancy — deadlock hazard.** The session mutex is
     * held for the entire call, and sink callbacks run while that
     * lock is held. Calling back into methods that also take the
     * mutex ([`Session::append_text`], [`Session::append_tokens`],
     * [`Session::generate`], [`Session::generate_streaming`],
     * [`Session::reset`]) from inside a sink method will deadlock.
     * [`Session::cancel`] and [`Session::position`] are atomic-backed
     * and safe to call from the sink or from any other thread.
     *
     * Cancellation: call [`Session::cancel`] from any thread (or from
     * inside a sink callback on this thread) to terminate the loop at
     * the next between-token check; `sink.on_done` fires with
     * [`FinishReason::Cancelled`].
     *
     * End-of-stream guarantee: `sink.on_done` fires exactly once per
     * call, even on error paths. If the underlying decode returns an
     * error before reaching its own `on_done` call (e.g.,
     * `EmptyInput` with no prefill logits), the wrapper synthesizes
     * a terminal `on_done(FinishReason::Error { message })` so
     * foreign consumers have a reliable end-of-stream signal
     * regardless of how the call exits.
     */
    @Throws(FfiException::class)
    override fun `generateStreaming`(
        `opts`: GenerateOpts,
        `sink`: ModalitySink,
    ): GenerateSummary =
        FfiConverterTypeGenerateSummary.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_generate_streaming(
                        it,
                        FfiConverterTypeGenerateOpts.lower(`opts`),
                        FfiConverterTypeModalitySink.lower(`sink`),
                        _status,
                    )
                }
            },
        )

    /**
     * Async variant of [`Session::generate_streaming`] — delivers
     * tokens and audio frames to the foreign [`ModalitySink`] as the
     * decode loop produces them, from within a blocking worker so
     * the caller's async runtime stays responsive.
     *
     * Sink callbacks run on the blocking worker thread that's
     * executing the decode — **not** on the caller's async thread.
     * The reentrancy hazard documented on
     * [`Session::generate_streaming`] still applies: sink callbacks
     * that call back into `append_text` / `generate*` / `reset` from
     * inside the session will deadlock on the session mutex.
     * [`Session::cancel`] and [`Session::position`] remain atomic-
     * backed and safe to invoke from any thread (including from
     * inside a callback).
     *
     * Cancellation: dropping the returned future fires the same
     * abort + [`Session::cancel`] pair as [`Session::generate_async`]
     * (see `AsyncCancelGuard`). For an in-flight decode, the loop
     * exits with [`FinishReason::Cancelled`] and the sink's `on_done`
     * fires on the blocking worker before the task completes —
     * foreign consumers get the terminal signal even though they've
     * already stopped awaiting. For a queued-but-not-started decode,
     * abort cancels the task without ever running the closure; no
     * sink callbacks fire for that case (the decode never began).
     */
    @Throws(FfiException::class)
    @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
    override suspend fun `generateStreamingAsync`(
        `opts`: GenerateOpts,
        `sink`: ModalitySink,
    ): GenerateSummary =
        uniffiRustCallAsync(
            callWithHandle { uniffiHandle ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_generate_streaming_async(
                    uniffiHandle,
                    FfiConverterTypeGenerateOpts.lower(`opts`),
                    FfiConverterTypeModalitySink.lower(`sink`),
                )
            },
            { future, callback, continuation -> UniffiLib.ffi_cera_ffi_rust_future_poll_rust_buffer(future, callback, continuation) },
            { future, continuation -> UniffiLib.ffi_cera_ffi_rust_future_complete_rust_buffer(future, continuation) },
            { future -> UniffiLib.ffi_cera_ffi_rust_future_free_rust_buffer(future) },
            // lift function
            { FfiConverterTypeGenerateSummary.lift(it) },
            // Error FFI converter
            FfiException.ErrorHandler,
        )

    /**
     * Whether a LoRA adapter is currently attached to this session.
     */
    @Throws(FfiException::class)
    override fun `hasLora`(): kotlin.Boolean =
        FfiConverterBoolean.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_has_lora(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Model hidden dimension `D`. Reshape a raw `[T*D]` byte buffer from
     * [`Self::hidden_states_for_tokens`] into `[T][D]` with this. Lock-free
     * (cached at construction), so — like `position()` — it's safe to call from
     * a `generate_streaming` sink callback.
     */
    override fun `hiddenSize`(): kotlin.UInt =
        FfiConverterUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_hidden_size(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Like [`Self::hidden_states_for_tokens`] but tokenizes `text` first
     * (Swift `hiddenStates(for:)`). Returns the same LE-f32 byte layout.
     */
    @Throws(FfiException::class)
    override fun `hiddenStatesForText`(`text`: kotlin.String): kotlin.ByteArray =
        FfiConverterByteArray.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_hidden_states_for_text(
                        it,
                        FfiConverterString.lower(`text`),
                        _status,
                    )
                }
            },
        )

    /**
     * Per-token last-layer hidden states (post-final-RMSNorm — the llama.cpp
     * `--pooling none` / `llama_get_embeddings_ith` vector) for `tokens`,
     * returned as **little-endian f32 bytes**: `n_tokens * hidden_size * 4`
     * bytes, row-major, token `t` channel `c` at `(t*D + c) * 4`.
     *
     * Bytes (UniFFI `Data` in Swift, `ByteArray` in Kotlin) rather than
     * `List<Float>` to avoid Kotlin's per-element boxing on the potentially
     * large `[T*D]` payload. Swift decodes via `Data.withUnsafeBytes`; reflects
     * the active LoRA once that lands. Side-effect-free: does not disturb the
     * session's generation KV.
     *
     * Like `append_*` / `generate`, this holds the session mutex for the
     * duration of the compute, so it must NOT be called re-entrantly from
     * within a `generate_streaming` sink callback (would self-deadlock).
     *
     * Errors: `EmptyInput` on empty input; `UnsupportedModality` if the backend
     * doesn't implement hidden-state extraction; `InvalidToken` if any id is
     * `>= vocab_size`.
     */
    @Throws(FfiException::class)
    override fun `hiddenStatesForTokens`(`tokens`: List<kotlin.UInt>): kotlin.ByteArray =
        FfiConverterByteArray.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_hidden_states_for_tokens(
                        it,
                        FfiConverterSequenceUInt.lower(`tokens`),
                        _status,
                    )
                }
            },
        )

    /**
     * Mean-pooled hidden state — a single `[hidden_size]` vector (the common
     * classifier path: pool in Rust, ship `D` floats not `T*D`). Returned as
     * `[Float]` / `List<Float>`; only `D` elements, so boxing is negligible.
     */
    @Throws(FfiException::class)
    override fun `hiddenStatesMeanPooled`(`tokens`: List<kotlin.UInt>): List<kotlin.Float> =
        FfiConverterSequenceFloat.lift(
            callWithHandle {
                uniffiRustCallWithError(FfiException) { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_hidden_states_mean_pooled(
                        it,
                        FfiConverterSequenceUInt.lower(`tokens`),
                        _status,
                    )
                }
            },
        )

    /**
     * Current KV position — how many tokens live in the cache.
     * Atomic-backed; safe to call from a different thread while
     * `generate()` is in flight.
     */
    override fun `position`(): kotlin.UInt =
        FfiConverterUInt.lift(
            callWithHandle {
                uniffiRustCall { _status ->
                    UniffiLib.uniffi_cera_ffi_fn_method_session_position(
                        it,
                        _status,
                    )
                }
            },
        )

    /**
     * Remove any attached LoRA adapter, returning to base-model inference.
     */
    @Throws(FfiException::class)
    override fun `removeLora`() =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_remove_lora(
                    it,
                    _status,
                )
            }
        }

    /**
     * Drop cached state + resample the seed. After `reset()` the
     * session behaves like a freshly-opened one (same
     * model/tokenizer/config, no accumulated context).
     *
     * Returns `Result` so a poisoned-mutex case surfaces as an error
     * instead of panicking across the FFI boundary.
     */
    @Throws(FfiException::class)
    override fun `reset`() =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_reset(
                    it,
                    _status,
                )
            }
        }

    /**
     * Set a session-default cap on the longest side of an appended
     * image, in pixels (`None` = no cap). Unlike the per-call
     * `max_long_size` argument to [`Self::append_image`], this default
     * is honored by every image-append path the session drives —
     * including chat-template flows — so a host can configure the
     * image-encode budget once. See [`Self::append_image`] for the cap
     * semantics (shrinks the encoded target, never upscales, takes
     * precedence over the model's minimum-resolution floor).
     */
    @Throws(FfiException::class)
    override fun `setImageMaxLongSize`(`maxLongSize`: kotlin.UInt?) =
        callWithHandle {
            uniffiRustCallWithError(FfiException) { _status ->
                UniffiLib.uniffi_cera_ffi_fn_method_session_set_image_max_long_size(
                    it,
                    FfiConverterOptionalUInt.lower(`maxLongSize`),
                    _status,
                )
            }
        }

    /**
     * @suppress
     */
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeSession : FfiConverter<Session, Long> {
    override fun lower(value: Session): Long = value.uniffiCloneHandle()

    override fun lift(value: Long): Session = Session(UniffiWithHandle, value)

    override fun read(buf: ByteBuffer): Session = lift(buf.getLong())

    override fun allocationSize(value: Session) = 8UL

    override fun write(
        value: Session,
        buf: ByteBuffer,
    ) {
        buf.putLong(lower(value))
    }
}

/**
 * One message in a chat-template conversation. Mirrors
 * [`cera::tokenizer::ChatMessage`]. Pass a `Vec<ChatMessage>` to
 * [`CeraEngine::apply_chat_template`] to render the model's
 * chat-template (Jinja2 from GGUF metadata) into a prompt string
 * ready to feed into [`Session::append_text`].
 *
 * `role` follows the OpenAI / chat-template convention — typically
 * one of `"system"`, `"user"`, `"assistant"`, occasionally
 * `"tool"`. cera-ffi doesn't validate the role string; whatever is
 * passed flows directly into the Jinja template. Whether an
 * unknown role errors or silently no-ops depends on the template's
 * own logic — many templates have an explicit error path for
 * unrecognized roles, but it's template-dependent rather than
 * enforced by [`CeraEngine::apply_chat_template`].
 */
data class ChatMessage(
    var `role`: kotlin.String,
    var `content`: kotlin.String,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeChatMessage : FfiConverterRustBuffer<ChatMessage> {
    override fun read(buf: ByteBuffer): ChatMessage =
        ChatMessage(
            FfiConverterString.read(buf),
            FfiConverterString.read(buf),
        )

    override fun allocationSize(value: ChatMessage) =
        (
            FfiConverterString.allocationSize(value.`role`) +
                FfiConverterString.allocationSize(value.`content`)
        )

    override fun write(
        value: ChatMessage,
        buf: ByteBuffer,
    ) {
        FfiConverterString.write(value.`role`, buf)
        FfiConverterString.write(value.`content`, buf)
    }
}

/**
 * Per-engine configuration at load time. Mirrors [`cera::EngineConfig`]
 * with `u64` fields (UniFFI doesn't marshal `usize`).
 */
data class EngineConfig(
    /**
     * KV-cache capacity in tokens. Capped by the model's own
     * `max_seq_len`. Pass `0` to use the model's full declared
     * `max_seq_len` (translated to `usize::MAX` internally, then
     * capped by the loader).
     */
    var `contextSize`: kotlin.ULong,
    var `backend`: BackendPreference,
    /**
     * Bundle repository for resolving `http(s)://` URLs in manifests
     * (or for [`CeraEngine::from_bundle_id`]). `None` means "remote
     * URLs will fail with an error"; set this to a [`BundleRepo`]
     * rooted at a persistent cache directory to enable remote
     * downloads. Construct the repo once + reuse it across engine
     * loads so its HTTP client pool + on-disk cache are shared.
     */
    var `bundleRepo`: BundleRepo?,
) : Disposable {
    @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
    override fun destroy() {
        Disposable.destroy(
            this.`contextSize`,
            this.`backend`,
            this.`bundleRepo`,
        )
    }

    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeEngineConfig : FfiConverterRustBuffer<EngineConfig> {
    override fun read(buf: ByteBuffer): EngineConfig =
        EngineConfig(
            FfiConverterULong.read(buf),
            FfiConverterTypeBackendPreference.read(buf),
            FfiConverterOptionalTypeBundleRepo.read(buf),
        )

    override fun allocationSize(value: EngineConfig) =
        (
            FfiConverterULong.allocationSize(value.`contextSize`) +
                FfiConverterTypeBackendPreference.allocationSize(value.`backend`) +
                FfiConverterOptionalTypeBundleRepo.allocationSize(value.`bundleRepo`)
        )

    override fun write(
        value: EngineConfig,
        buf: ByteBuffer,
    ) {
        FfiConverterULong.write(value.`contextSize`, buf)
        FfiConverterTypeBackendPreference.write(value.`backend`, buf)
        FfiConverterOptionalTypeBundleRepo.write(value.`bundleRepo`, buf)
    }
}

/**
 * Per-call decode options. Mirrors [`cera::GenerateOpts`].
 *
 * `flush_every_tokens` / `flush_every_ms` are accepted but have no
 * effect under the synchronous [`Session::generate`] — they're
 * meaningful once streaming (foreign-trait `ModalitySink`) lands
 * in a follow-up PR. Including them in the record now keeps the FFI
 * surface stable across that transition.
 */
data class GenerateOpts(
    var `maxTokens`: kotlin.UInt,
    var `temperature`: kotlin.Float,
    var `topP`: kotlin.Float,
    var `topK`: kotlin.UInt,
    /**
     * Min-p (relative) nucleus cutoff: drop tokens below `min_p * p_max`. `0.0`
     * disables it. Honored in the stochastic path.
     */
    var `minP`: kotlin.Float,
    /**
     * Repetition penalty over tokens generated this call. `1.0` disables it.
     * Honored in the stochastic path (greedy/argmax decoding is unaffected).
     */
    var `repetitionPenalty`: kotlin.Float,
    /**
     * Early-stop IDs (EOS / instruction markers / end-of-turn).
     */
    var `stopTokens`: List<kotlin.UInt>,
    /**
     * Optional GBNF grammar **source text** constraining the output (e.g. a
     * JSON grammar). When absent (the default), decoding is unconstrained. The
     * grammar is compiled on the Rust side when generation starts; a malformed
     * grammar is reported as a `GrammarParse` error.
     */
    var `grammar`: kotlin.String?,
    /**
     * Ignored under synchronous generate; reserved for streaming.
     */
    var `flushEveryTokens`: kotlin.UInt,
    /**
     * Ignored under synchronous generate; reserved for streaming.
     */
    var `flushEveryMs`: kotlin.UInt,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeGenerateOpts : FfiConverterRustBuffer<GenerateOpts> {
    override fun read(buf: ByteBuffer): GenerateOpts =
        GenerateOpts(
            FfiConverterUInt.read(buf),
            FfiConverterFloat.read(buf),
            FfiConverterFloat.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterFloat.read(buf),
            FfiConverterFloat.read(buf),
            FfiConverterSequenceUInt.read(buf),
            FfiConverterOptionalString.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterUInt.read(buf),
        )

    override fun allocationSize(value: GenerateOpts) =
        (
            FfiConverterUInt.allocationSize(value.`maxTokens`) +
                FfiConverterFloat.allocationSize(value.`temperature`) +
                FfiConverterFloat.allocationSize(value.`topP`) +
                FfiConverterUInt.allocationSize(value.`topK`) +
                FfiConverterFloat.allocationSize(value.`minP`) +
                FfiConverterFloat.allocationSize(value.`repetitionPenalty`) +
                FfiConverterSequenceUInt.allocationSize(value.`stopTokens`) +
                FfiConverterOptionalString.allocationSize(value.`grammar`) +
                FfiConverterUInt.allocationSize(value.`flushEveryTokens`) +
                FfiConverterUInt.allocationSize(value.`flushEveryMs`)
        )

    override fun write(
        value: GenerateOpts,
        buf: ByteBuffer,
    ) {
        FfiConverterUInt.write(value.`maxTokens`, buf)
        FfiConverterFloat.write(value.`temperature`, buf)
        FfiConverterFloat.write(value.`topP`, buf)
        FfiConverterUInt.write(value.`topK`, buf)
        FfiConverterFloat.write(value.`minP`, buf)
        FfiConverterFloat.write(value.`repetitionPenalty`, buf)
        FfiConverterSequenceUInt.write(value.`stopTokens`, buf)
        FfiConverterOptionalString.write(value.`grammar`, buf)
        FfiConverterUInt.write(value.`flushEveryTokens`, buf)
        FfiConverterUInt.write(value.`flushEveryMs`, buf)
    }
}

/**
 * Bundle of everything a synchronous `generate` call produces:
 * the generated token IDs plus the decode summary. The two are
 * returned together so callers don't have to manage a separate
 * callback channel; streaming (per-chunk delivery) lands in PR 4.
 */
data class GenerateOutput(
    /**
     * Generated token IDs, in order, not including any prompt
     * tokens. Decode with [`cera::tokenizer::BpeTokenizer`] on the
     * Rust side or (once exposed) through a tokenizer handle on the
     * FFI side.
     */
    var `tokens`: List<kotlin.UInt>,
    var `summary`: GenerateSummary,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeGenerateOutput : FfiConverterRustBuffer<GenerateOutput> {
    override fun read(buf: ByteBuffer): GenerateOutput =
        GenerateOutput(
            FfiConverterSequenceUInt.read(buf),
            FfiConverterTypeGenerateSummary.read(buf),
        )

    override fun allocationSize(value: GenerateOutput) =
        (
            FfiConverterSequenceUInt.allocationSize(value.`tokens`) +
                FfiConverterTypeGenerateSummary.allocationSize(value.`summary`)
        )

    override fun write(
        value: GenerateOutput,
        buf: ByteBuffer,
    ) {
        FfiConverterSequenceUInt.write(value.`tokens`, buf)
        FfiConverterTypeGenerateSummary.write(value.`summary`, buf)
    }
}

/**
 * Decode-run metadata. Mirrors [`cera::GenerateSummary`].
 */
data class GenerateSummary(
    var `tokensGenerated`: kotlin.UInt,
    var `promptEvalTokens`: kotlin.UInt,
    var `promptEvalMs`: kotlin.UInt,
    var `decodeMs`: kotlin.UInt,
    var `finishReason`: FinishReason,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeGenerateSummary : FfiConverterRustBuffer<GenerateSummary> {
    override fun read(buf: ByteBuffer): GenerateSummary =
        GenerateSummary(
            FfiConverterUInt.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterTypeFinishReason.read(buf),
        )

    override fun allocationSize(value: GenerateSummary) =
        (
            FfiConverterUInt.allocationSize(value.`tokensGenerated`) +
                FfiConverterUInt.allocationSize(value.`promptEvalTokens`) +
                FfiConverterUInt.allocationSize(value.`promptEvalMs`) +
                FfiConverterUInt.allocationSize(value.`decodeMs`) +
                FfiConverterTypeFinishReason.allocationSize(value.`finishReason`)
        )

    override fun write(
        value: GenerateSummary,
        buf: ByteBuffer,
    ) {
        FfiConverterUInt.write(value.`tokensGenerated`, buf)
        FfiConverterUInt.write(value.`promptEvalTokens`, buf)
        FfiConverterUInt.write(value.`promptEvalMs`, buf)
        FfiConverterUInt.write(value.`decodeMs`, buf)
        FfiConverterTypeFinishReason.write(value.`finishReason`, buf)
    }
}

/**
 * Modality support flags for a loaded model. Mirrors
 * [`cera::ModalityCapabilities`].
 */
data class ModalityCapabilities(
    var `textIn`: kotlin.Boolean,
    var `textOut`: kotlin.Boolean,
    var `imageIn`: kotlin.Boolean,
    var `audioIn`: kotlin.Boolean,
    var `audioOut`: kotlin.Boolean,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeModalityCapabilities : FfiConverterRustBuffer<ModalityCapabilities> {
    override fun read(buf: ByteBuffer): ModalityCapabilities =
        ModalityCapabilities(
            FfiConverterBoolean.read(buf),
            FfiConverterBoolean.read(buf),
            FfiConverterBoolean.read(buf),
            FfiConverterBoolean.read(buf),
            FfiConverterBoolean.read(buf),
        )

    override fun allocationSize(value: ModalityCapabilities) =
        (
            FfiConverterBoolean.allocationSize(value.`textIn`) +
                FfiConverterBoolean.allocationSize(value.`textOut`) +
                FfiConverterBoolean.allocationSize(value.`imageIn`) +
                FfiConverterBoolean.allocationSize(value.`audioIn`) +
                FfiConverterBoolean.allocationSize(value.`audioOut`)
        )

    override fun write(
        value: ModalityCapabilities,
        buf: ByteBuffer,
    ) {
        FfiConverterBoolean.write(value.`textIn`, buf)
        FfiConverterBoolean.write(value.`textOut`, buf)
        FfiConverterBoolean.write(value.`imageIn`, buf)
        FfiConverterBoolean.write(value.`audioIn`, buf)
        FfiConverterBoolean.write(value.`audioOut`, buf)
    }
}

/**
 * Short summary of a loaded model. Mirrors [`cera::ModelMetadata`].
 */
data class ModelMetadata(
    var `architecture`: kotlin.String,
    var `maxSeqLen`: kotlin.UInt,
    var `vocabSize`: kotlin.UInt,
    var `hasChatTemplate`: kotlin.Boolean,
    var `quantization`: kotlin.String,
    /**
     * Mirror of GGUF `tokenizer.ggml.add_bos_token`. Consumers that
     * want to insert a BOS at the head of a raw prompt should honor it.
     */
    var `addBosToken`: kotlin.Boolean,
    /**
     * SIMD backend tier the runtime resolved for this host (e.g.
     * `"neon+dotprod"`, `"avx2"`, `"scalar"`). A host property, not
     * model-specific — surfaced here so consumers fetching metadata also
     * get backend diagnostics for telemetry / bug reports. For the full
     * feature list, see [`cpu_backend_report`].
     */
    var `cpuBackend`: kotlin.String,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeModelMetadata : FfiConverterRustBuffer<ModelMetadata> {
    override fun read(buf: ByteBuffer): ModelMetadata =
        ModelMetadata(
            FfiConverterString.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterBoolean.read(buf),
            FfiConverterString.read(buf),
            FfiConverterBoolean.read(buf),
            FfiConverterString.read(buf),
        )

    override fun allocationSize(value: ModelMetadata) =
        (
            FfiConverterString.allocationSize(value.`architecture`) +
                FfiConverterUInt.allocationSize(value.`maxSeqLen`) +
                FfiConverterUInt.allocationSize(value.`vocabSize`) +
                FfiConverterBoolean.allocationSize(value.`hasChatTemplate`) +
                FfiConverterString.allocationSize(value.`quantization`) +
                FfiConverterBoolean.allocationSize(value.`addBosToken`) +
                FfiConverterString.allocationSize(value.`cpuBackend`)
        )

    override fun write(
        value: ModelMetadata,
        buf: ByteBuffer,
    ) {
        FfiConverterString.write(value.`architecture`, buf)
        FfiConverterUInt.write(value.`maxSeqLen`, buf)
        FfiConverterUInt.write(value.`vocabSize`, buf)
        FfiConverterBoolean.write(value.`hasChatTemplate`, buf)
        FfiConverterString.write(value.`quantization`, buf)
        FfiConverterBoolean.write(value.`addBosToken`, buf)
        FfiConverterString.write(value.`cpuBackend`, buf)
    }
}

/**
 * Per-session configuration. Mirrors [`cera::SessionConfig`].
 */
data class SessionConfig(
    /**
     * Cap on total tokens held in KV. `None` → model's default
     * `max_seq_len`.
     */
    var `maxSeqLen`: kotlin.UInt?,
    /**
     * KV cache compression mode.
     */
    var `kvCompression`: KvCompression,
    /**
     * Pinned-prefix length for Phase-1.5 context shift on overflow.
     * `0` disables shift; overflow returns `ContextOverflow` error.
     */
    var `nKeep`: kotlin.UInt,
    /**
     * Deterministic sampling seed. `None` = fresh entropy per call.
     */
    var `seed`: kotlin.ULong?,
    /**
     * Chunked-prefill ubatch size. `0` = monolithic prefill.
     */
    var `ubatchSize`: kotlin.UInt,
) {
    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeSessionConfig : FfiConverterRustBuffer<SessionConfig> {
    override fun read(buf: ByteBuffer): SessionConfig =
        SessionConfig(
            FfiConverterOptionalUInt.read(buf),
            FfiConverterTypeKvCompression.read(buf),
            FfiConverterUInt.read(buf),
            FfiConverterOptionalULong.read(buf),
            FfiConverterUInt.read(buf),
        )

    override fun allocationSize(value: SessionConfig) =
        (
            FfiConverterOptionalUInt.allocationSize(value.`maxSeqLen`) +
                FfiConverterTypeKvCompression.allocationSize(value.`kvCompression`) +
                FfiConverterUInt.allocationSize(value.`nKeep`) +
                FfiConverterOptionalULong.allocationSize(value.`seed`) +
                FfiConverterUInt.allocationSize(value.`ubatchSize`)
        )

    override fun write(
        value: SessionConfig,
        buf: ByteBuffer,
    ) {
        FfiConverterOptionalUInt.write(value.`maxSeqLen`, buf)
        FfiConverterTypeKvCompression.write(value.`kvCompression`, buf)
        FfiConverterUInt.write(value.`nKeep`, buf)
        FfiConverterOptionalULong.write(value.`seed`, buf)
        FfiConverterUInt.write(value.`ubatchSize`, buf)
    }
}

/**
 * Compute-backend selector. Mirrors [`cera::BackendPreference`];
 * kept as a separate type so the `cera` crate doesn't carry UniFFI
 * annotations.
 */

enum class BackendPreference {
    /**
     * Probe Metal → GPU → CPU at load time.
     */
    AUTO,
    CPU,

    /**
     * `wgpu` (Vulkan / Metal / DX12). Requires the `gpu` feature.
     */
    GPU,

    /**
     * Native Metal. Requires the `metal` feature + macOS.
     */
    METAL,

    ;

    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeBackendPreference : FfiConverterRustBuffer<BackendPreference> {
    override fun read(buf: ByteBuffer) =
        try {
            BackendPreference.values()[buf.getInt() - 1]
        } catch (e: IndexOutOfBoundsException) {
            throw RuntimeException("invalid enum value, something is very wrong!!", e)
        }

    override fun allocationSize(value: BackendPreference) = 4UL

    override fun write(
        value: BackendPreference,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.ordinal + 1)
    }
}

/**
 * Typed error surface for `cera-ffi`. Mirrors [`cera::CeraError`] one-
 * to-one so foreign callers can pattern-match on error class (Kotlin
 * `when`, Swift `switch`, Python `match`) instead of string-sniffing
 * a generic message.
 *
 * `Backend` is **not** a silent fallback for unmapped `cera::CeraError`
 * variants — the `From<CeraError>` impl is exhaustive, so adding a
 * new cera variant breaks compilation here. `Backend` exists solely
 * for FFI-internal errors that have no cera analog: `JoinError` from
 * a panicking `spawn_blocking` task, a poisoned `Session::inner`
 * mutex, 32-bit `u64 → usize` overflow in `EngineConfig::try_from`.
 *
 * Every variant carries the data needed to act on it:
 * `ContextOverflow` exposes `max_seq_len` and `by` so callers can
 * reset or truncate rather than re-reading the message;
 * `UnsupportedInferenceType` exposes the offending value;
 * `Io` preserves the underlying OS error message as a string since
 * `io::Error` isn't UniFFI-marshallable.
 *
 * `#[error(...)]` format strings match `cera::CeraError` exactly for
 * every shared variant, so `Display` output is identical whether the
 * error originates from cera directly or routes through the FFI
 * wrapper. Pinned by `ffi_error_display_matches_cera_error_for_every_shared_variant`.
 */
sealed class FfiException : kotlin.Exception() {
    /**
     * The loaded model doesn't support the modality the caller
     * requested (e.g. `append_audio` on a text-only LLM).
     */
    class UnsupportedModality : FfiException() {
        override val message
            get() = ""
    }

    /**
     * The manifest's `inference_type` is one cera doesn't recognize
     * at this version. Field carries the offending string.
     */
    class UnsupportedInferenceType(
        val `inferenceType`: kotlin.String,
    ) : FfiException() {
        override val message
            get() = "inferenceType=${ `inferenceType` }"
    }

    /**
     * A concurrent `generate*` call is already in flight on this
     * session. Rust side guards with a mutex; this surfaces when the
     * FFI detects contention.
     */
    class Busy : FfiException() {
        override val message
            get() = ""
    }

    /**
     * The caller (or the cancel-on-drop guard) flipped the cancel
     * atomic mid-call. Surfaces from `append_text`, `append_tokens`,
     * and `append_audio` when chunked prefill detects the cancel
     * flag between micro-batches and aborts (see
     * [`cera::Session::append_tokens`] for the chunked-prefill
     * mechanism). Call [`Session::clear_cancel`] to reset the flag
     * so the next call can proceed.
     *
     * `generate` reports cancellation via a different path: the
     * call still returns `Ok` with a [`GenerateOutput`] whose
     * `finish_reason` is set to `Cancelled`. Two paths because
     * chunked prefill has nothing useful to return on cancel (no
     * decoded tokens) while decode has accumulated tokens worth
     * preserving.
     */
    class Cancelled : FfiException() {
        override val message
            get() = ""
    }

    /**
     * The context window is full and the session can't shift to make
     * room (e.g. `n_keep == 0`, TurboQuant caches, or the active
     * model doesn't support rope-shift). `max_seq_len` is the cap
     * that was hit; `by` is the overshoot in tokens.
     */
    class ContextOverflow(
        val `maxSeqLen`: kotlin.UInt,
        val `by`: kotlin.UInt,
    ) : FfiException() {
        override val message
            get() = "maxSeqLen=${ `maxSeqLen` }, by=${ `by` }"
    }

    /**
     * Input buffer was empty (e.g. `append_text("")`, or decode with
     * no prefill state).
     */
    class EmptyInput : FfiException() {
        override val message
            get() = ""
    }

    /**
     * Filesystem / mmap / network error surfaced from cera. The
     * underlying `io::Error` isn't marshallable, so the message is
     * flattened to a string. Callers that need the raw kind should
     * parse the `detail` field or open an issue to request a typed
     * field.
     *
     * Field is named `detail` rather than `message` because UniFFI's
     * 0.31 Kotlin generator emits `class Io(val `message`) : FfiException()`
     * AND `override val message` in the body when the field is literally
     * named `message`, producing a "conflicting declarations" error
     * (the constructor param collides with the inherited
     * `Throwable.message` override). Renaming to `detail` sidesteps
     * the collision.
     *
     * Format string matches `cera::CeraError::Io`'s `"io: {0}"` so
     * foreign `.toString()` / `String(describing:)` gives the same
     * output Rust consumers see.
     */
    class Io(
        val `detail`: kotlin.String,
    ) : FfiException() {
        override val message
            get() = "detail=${ `detail` }"
    }

    /**
     * FFI-internal error with no cera analog: `JoinError` from a
     * panicking `spawn_blocking` task, poisoned `Session::inner`
     * mutex, 32-bit `u64 → usize` overflow in `EngineConfig::try_from`,
     * or `cera::CeraError::Backend` routed through the `From` impl.
     * Format string matches `cera::CeraError::Backend`'s
     * `"backend: {0}"` — FFI-internal constructors that have already
     * formatted a descriptive message (e.g. "generate_async join
     * error: ...") still read cleanly with the `backend:` label.
     *
     * Field is named `detail` rather than `message` for the same
     * `Throwable.message` collision reason as [`FfiError::Io`].
     */
    class Backend(
        val `detail`: kotlin.String,
    ) : FfiException() {
        override val message
            get() = "detail=${ `detail` }"
    }

    /**
     * The GBNF grammar string passed in `GenerateOpts.grammar` failed to
     * compile. Grammar compilation happens in the FFI wrapper (the compiled
     * grammar object can't cross the boundary, so callers pass the source text
     * and it's parsed here). `detail` carries the parser's diagnostic.
     */
    class GrammarParse(
        val `detail`: kotlin.String,
    ) : FfiException() {
        override val message
            get() = "detail=${ `detail` }"
    }

    /**
     * A token id passed to `hidden_states_for_tokens` (or another
     * token-taking method) was `>= vocab_size`. Returned as a typed error
     * rather than tripping the model-layer `assert!` (whose panic would
     * unwind through the held session lock and poison it). Mirrors
     * `cera::CeraError::InvalidToken`.
     */
    class InvalidToken(
        val `id`: kotlin.UInt,
        val `vocabSize`: kotlin.UInt,
    ) : FfiException() {
        override val message
            get() = "id=${ `id` }, vocabSize=${ `vocabSize` }"
    }

    /**
     * A LoRA adapter failed to load ([`LoraAdapters::from_gguf`] /
     * [`LoraAdapters::from_safetensors`]) or was incompatible with the model at
     * attach time (wrong dimensions). `detail` carries the diagnostic.
     */
    class LoraParse(
        val `detail`: kotlin.String,
    ) : FfiException() {
        override val message
            get() = "detail=${ `detail` }"
    }

    companion object ErrorHandler : UniffiRustCallStatusErrorHandler<FfiException> {
        override fun lift(error_buf: RustBuffer.ByValue): FfiException = FfiConverterTypeFfiError.lift(error_buf)
    }
}

/**
 * @suppress
 */
public object FfiConverterTypeFfiError : FfiConverterRustBuffer<FfiException> {
    override fun read(buf: ByteBuffer): FfiException =
        when (buf.getInt()) {
            1 -> {
                FfiException.UnsupportedModality()
            }

            2 -> {
                FfiException.UnsupportedInferenceType(
                    FfiConverterString.read(buf),
                )
            }

            3 -> {
                FfiException.Busy()
            }

            4 -> {
                FfiException.Cancelled()
            }

            5 -> {
                FfiException.ContextOverflow(
                    FfiConverterUInt.read(buf),
                    FfiConverterUInt.read(buf),
                )
            }

            6 -> {
                FfiException.EmptyInput()
            }

            7 -> {
                FfiException.Io(
                    FfiConverterString.read(buf),
                )
            }

            8 -> {
                FfiException.Backend(
                    FfiConverterString.read(buf),
                )
            }

            9 -> {
                FfiException.GrammarParse(
                    FfiConverterString.read(buf),
                )
            }

            10 -> {
                FfiException.InvalidToken(
                    FfiConverterUInt.read(buf),
                    FfiConverterUInt.read(buf),
                )
            }

            11 -> {
                FfiException.LoraParse(
                    FfiConverterString.read(buf),
                )
            }

            else -> {
                throw RuntimeException("invalid error enum value, something is very wrong!!")
            }
        }

    override fun allocationSize(value: FfiException): ULong =
        when (value) {
            is FfiException.UnsupportedModality -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL
            )

            is FfiException.UnsupportedInferenceType -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterString.allocationSize(value.`inferenceType`)
            )

            is FfiException.Busy -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL
            )

            is FfiException.Cancelled -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL
            )

            is FfiException.ContextOverflow -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterUInt.allocationSize(value.`maxSeqLen`) +
                    FfiConverterUInt.allocationSize(value.`by`)
            )

            is FfiException.EmptyInput -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL
            )

            is FfiException.Io -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterString.allocationSize(value.`detail`)
            )

            is FfiException.Backend -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterString.allocationSize(value.`detail`)
            )

            is FfiException.GrammarParse -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterString.allocationSize(value.`detail`)
            )

            is FfiException.InvalidToken -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterUInt.allocationSize(value.`id`) +
                    FfiConverterUInt.allocationSize(value.`vocabSize`)
            )

            is FfiException.LoraParse -> (
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                4UL +
                    FfiConverterString.allocationSize(value.`detail`)
            )
        }

    override fun write(
        value: FfiException,
        buf: ByteBuffer,
    ) {
        when (value) {
            is FfiException.UnsupportedModality -> {
                buf.putInt(1)
                Unit
            }

            is FfiException.UnsupportedInferenceType -> {
                buf.putInt(2)
                FfiConverterString.write(value.`inferenceType`, buf)
                Unit
            }

            is FfiException.Busy -> {
                buf.putInt(3)
                Unit
            }

            is FfiException.Cancelled -> {
                buf.putInt(4)
                Unit
            }

            is FfiException.ContextOverflow -> {
                buf.putInt(5)
                FfiConverterUInt.write(value.`maxSeqLen`, buf)
                FfiConverterUInt.write(value.`by`, buf)
                Unit
            }

            is FfiException.EmptyInput -> {
                buf.putInt(6)
                Unit
            }

            is FfiException.Io -> {
                buf.putInt(7)
                FfiConverterString.write(value.`detail`, buf)
                Unit
            }

            is FfiException.Backend -> {
                buf.putInt(8)
                FfiConverterString.write(value.`detail`, buf)
                Unit
            }

            is FfiException.GrammarParse -> {
                buf.putInt(9)
                FfiConverterString.write(value.`detail`, buf)
                Unit
            }

            is FfiException.InvalidToken -> {
                buf.putInt(10)
                FfiConverterUInt.write(value.`id`, buf)
                FfiConverterUInt.write(value.`vocabSize`, buf)
                Unit
            }

            is FfiException.LoraParse -> {
                buf.putInt(11)
                FfiConverterString.write(value.`detail`, buf)
                Unit
            }
        }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
    }
}

/**
 * Why a decode loop exited. Mirrors [`cera::FinishReason`].
 */
sealed class FinishReason {
    object MaxTokens : FinishReason()

    object Stop : FinishReason()

    object Cancelled : FinishReason()

    object ContextFull : FinishReason()

    /**
     * A grammar constraint left no token allowed at this step — decoding
     * stopped because the grammar dead-ended. Only reachable when
     * `GenerateOpts.grammar` is set.
     */
    object GrammarDeadEnd : FinishReason()

    data class Error(
        val `message`: kotlin.String,
    ) : FinishReason() {
        companion object
    }

    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeFinishReason : FfiConverterRustBuffer<FinishReason> {
    override fun read(buf: ByteBuffer): FinishReason =
        when (buf.getInt()) {
            1 -> {
                FinishReason.MaxTokens
            }

            2 -> {
                FinishReason.Stop
            }

            3 -> {
                FinishReason.Cancelled
            }

            4 -> {
                FinishReason.ContextFull
            }

            5 -> {
                FinishReason.GrammarDeadEnd
            }

            6 -> {
                FinishReason.Error(
                    FfiConverterString.read(buf),
                )
            }

            else -> {
                throw RuntimeException("invalid enum value, something is very wrong!!")
            }
        }

    override fun allocationSize(value: FinishReason) =
        when (value) {
            is FinishReason.MaxTokens -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is FinishReason.Stop -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is FinishReason.Cancelled -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is FinishReason.ContextFull -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is FinishReason.GrammarDeadEnd -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is FinishReason.Error -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL +
                        FfiConverterString.allocationSize(value.`message`)
                )
            }
        }

    override fun write(
        value: FinishReason,
        buf: ByteBuffer,
    ) {
        when (value) {
            is FinishReason.MaxTokens -> {
                buf.putInt(1)
                Unit
            }

            is FinishReason.Stop -> {
                buf.putInt(2)
                Unit
            }

            is FinishReason.Cancelled -> {
                buf.putInt(3)
                Unit
            }

            is FinishReason.ContextFull -> {
                buf.putInt(4)
                Unit
            }

            is FinishReason.GrammarDeadEnd -> {
                buf.putInt(5)
                Unit
            }

            is FinishReason.Error -> {
                buf.putInt(6)
                FfiConverterString.write(value.`message`, buf)
                Unit
            }
        }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
    }
}

/**
 * KV-cache compression mode. Mirrors [`cera::kv_cache::KvCompression`].
 * `TurboQuant` is honored by the CPU backend only; Metal / GPU ignore
 * the setting and use the f32 path.
 */
sealed class KvCompression {
    /**
     * No compression — f32 keys and values (default).
     */
    object None : KvCompression()

    /**
     * TurboQuant compression. Both `keys` + `values` true is the
     * production configuration; toggling them individually is
     * primarily for debugging the drift contribution of each side.
     * `seed` drives the per-layer randomized Hadamard rotations.
     */
    data class TurboQuant(
        val `seed`: kotlin.ULong,
        val `keys`: kotlin.Boolean,
        val `values`: kotlin.Boolean,
    ) : KvCompression() {
        companion object
    }

    companion object
}

/**
 * @suppress
 */
public object FfiConverterTypeKvCompression : FfiConverterRustBuffer<KvCompression> {
    override fun read(buf: ByteBuffer): KvCompression =
        when (buf.getInt()) {
            1 -> {
                KvCompression.None
            }

            2 -> {
                KvCompression.TurboQuant(
                    FfiConverterULong.read(buf),
                    FfiConverterBoolean.read(buf),
                    FfiConverterBoolean.read(buf),
                )
            }

            else -> {
                throw RuntimeException("invalid enum value, something is very wrong!!")
            }
        }

    override fun allocationSize(value: KvCompression) =
        when (value) {
            is KvCompression.None -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL
                )
            }

            is KvCompression.TurboQuant -> {
                // Add the size for the Int that specifies the variant plus the size needed for all fields
                (
                    4UL +
                        FfiConverterULong.allocationSize(value.`seed`) +
                        FfiConverterBoolean.allocationSize(value.`keys`) +
                        FfiConverterBoolean.allocationSize(value.`values`)
                )
            }
        }

    override fun write(
        value: KvCompression,
        buf: ByteBuffer,
    ) {
        when (value) {
            is KvCompression.None -> {
                buf.putInt(1)
                Unit
            }

            is KvCompression.TurboQuant -> {
                buf.putInt(2)
                FfiConverterULong.write(value.`seed`, buf)
                FfiConverterBoolean.write(value.`keys`, buf)
                FfiConverterBoolean.write(value.`values`, buf)
                Unit
            }
        }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
    }
}

/**
 * @suppress
 */
public object FfiConverterOptionalUInt : FfiConverterRustBuffer<kotlin.UInt?> {
    override fun read(buf: ByteBuffer): kotlin.UInt? {
        if (buf.get().toInt() == 0) {
            return null
        }
        return FfiConverterUInt.read(buf)
    }

    override fun allocationSize(value: kotlin.UInt?): ULong {
        if (value == null) {
            return 1UL
        } else {
            return 1UL + FfiConverterUInt.allocationSize(value)
        }
    }

    override fun write(
        value: kotlin.UInt?,
        buf: ByteBuffer,
    ) {
        if (value == null) {
            buf.put(0)
        } else {
            buf.put(1)
            FfiConverterUInt.write(value, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterOptionalULong : FfiConverterRustBuffer<kotlin.ULong?> {
    override fun read(buf: ByteBuffer): kotlin.ULong? {
        if (buf.get().toInt() == 0) {
            return null
        }
        return FfiConverterULong.read(buf)
    }

    override fun allocationSize(value: kotlin.ULong?): ULong {
        if (value == null) {
            return 1UL
        } else {
            return 1UL + FfiConverterULong.allocationSize(value)
        }
    }

    override fun write(
        value: kotlin.ULong?,
        buf: ByteBuffer,
    ) {
        if (value == null) {
            buf.put(0)
        } else {
            buf.put(1)
            FfiConverterULong.write(value, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterOptionalFloat : FfiConverterRustBuffer<kotlin.Float?> {
    override fun read(buf: ByteBuffer): kotlin.Float? {
        if (buf.get().toInt() == 0) {
            return null
        }
        return FfiConverterFloat.read(buf)
    }

    override fun allocationSize(value: kotlin.Float?): ULong {
        if (value == null) {
            return 1UL
        } else {
            return 1UL + FfiConverterFloat.allocationSize(value)
        }
    }

    override fun write(
        value: kotlin.Float?,
        buf: ByteBuffer,
    ) {
        if (value == null) {
            buf.put(0)
        } else {
            buf.put(1)
            FfiConverterFloat.write(value, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterOptionalString : FfiConverterRustBuffer<kotlin.String?> {
    override fun read(buf: ByteBuffer): kotlin.String? {
        if (buf.get().toInt() == 0) {
            return null
        }
        return FfiConverterString.read(buf)
    }

    override fun allocationSize(value: kotlin.String?): ULong {
        if (value == null) {
            return 1UL
        } else {
            return 1UL + FfiConverterString.allocationSize(value)
        }
    }

    override fun write(
        value: kotlin.String?,
        buf: ByteBuffer,
    ) {
        if (value == null) {
            buf.put(0)
        } else {
            buf.put(1)
            FfiConverterString.write(value, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterOptionalTypeBundleRepo : FfiConverterRustBuffer<BundleRepo?> {
    override fun read(buf: ByteBuffer): BundleRepo? {
        if (buf.get().toInt() == 0) {
            return null
        }
        return FfiConverterTypeBundleRepo.read(buf)
    }

    override fun allocationSize(value: BundleRepo?): ULong {
        if (value == null) {
            return 1UL
        } else {
            return 1UL + FfiConverterTypeBundleRepo.allocationSize(value)
        }
    }

    override fun write(
        value: BundleRepo?,
        buf: ByteBuffer,
    ) {
        if (value == null) {
            buf.put(0)
        } else {
            buf.put(1)
            FfiConverterTypeBundleRepo.write(value, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterSequenceUInt : FfiConverterRustBuffer<List<kotlin.UInt>> {
    override fun read(buf: ByteBuffer): List<kotlin.UInt> {
        val len = buf.getInt()
        return List<kotlin.UInt>(len) {
            FfiConverterUInt.read(buf)
        }
    }

    override fun allocationSize(value: List<kotlin.UInt>): ULong {
        val sizeForLength = 4UL
        val sizeForItems = value.map { FfiConverterUInt.allocationSize(it) }.sum()
        return sizeForLength + sizeForItems
    }

    override fun write(
        value: List<kotlin.UInt>,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.size)
        value.iterator().forEach {
            FfiConverterUInt.write(it, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterSequenceFloat : FfiConverterRustBuffer<List<kotlin.Float>> {
    override fun read(buf: ByteBuffer): List<kotlin.Float> {
        val len = buf.getInt()
        return List<kotlin.Float>(len) {
            FfiConverterFloat.read(buf)
        }
    }

    override fun allocationSize(value: List<kotlin.Float>): ULong {
        val sizeForLength = 4UL
        val sizeForItems = value.map { FfiConverterFloat.allocationSize(it) }.sum()
        return sizeForLength + sizeForItems
    }

    override fun write(
        value: List<kotlin.Float>,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.size)
        value.iterator().forEach {
            FfiConverterFloat.write(it, buf)
        }
    }
}

/**
 * @suppress
 */
public object FfiConverterSequenceTypeChatMessage : FfiConverterRustBuffer<List<ChatMessage>> {
    override fun read(buf: ByteBuffer): List<ChatMessage> {
        val len = buf.getInt()
        return List<ChatMessage>(len) {
            FfiConverterTypeChatMessage.read(buf)
        }
    }

    override fun allocationSize(value: List<ChatMessage>): ULong {
        val sizeForLength = 4UL
        val sizeForItems = value.map { FfiConverterTypeChatMessage.allocationSize(it) }.sum()
        return sizeForLength + sizeForItems
    }

    override fun write(
        value: List<ChatMessage>,
        buf: ByteBuffer,
    ) {
        buf.putInt(value.size)
        value.iterator().forEach {
            FfiConverterTypeChatMessage.write(it, buf)
        }
    }
}

/**
 * Version string of the `cera-ffi` crate. Useful as a smoke test
 * from the foreign-language side — if this is callable, the binding
 * pipeline works end-to-end.
 */
fun `ceraFfiVersion`(): kotlin.String =
    FfiConverterString.lift(
        uniffiRustCall { _status ->
            UniffiLib.uniffi_cera_ffi_fn_func_cera_ffi_version(_status)
        },
    )

/**
 * One-line CPU backend report for this host — the resolved SIMD tier plus the
 * detected feature flags, e.g. `cpu: tier=neon+dotprod [neon dotprod]`. A host
 * property independent of any loaded model; callable without an engine. Handy
 * for telemetry and bug reports (tells you which kernel path actually ran).
 */
fun `cpuBackendReport`(): kotlin.String =
    FfiConverterString.lift(
        uniffiRustCall { _status ->
            UniffiLib.uniffi_cera_ffi_fn_func_cpu_backend_report(_status)
        },
    )
