{%- if module.needs_ffi_buf() %}
    [StructLayout(LayoutKind.Sequential)]
    internal struct FfiBuf
    {
        public IntPtr ptr;
        public UIntPtr len;
        public UIntPtr cap;
        public UIntPtr align;
    }

{% endif %}
{%- if module.needs_wire_reader() %}
    internal sealed class WireReader
    {
        // Reads directly from the unmanaged FfiBuf pointer — no eager copy into
        // a managed byte[]. Safe APIs only: Marshal.Read* for primitives,
        // Marshal.PtrToStringUTF8 for length-prefixed UTF-8, Marshal.Copy for
        // bytes. BitConverter.IsLittleEndian is a JIT intrinsic that folds the
        // byte-swap branch away on little-endian hosts (all our supported
        // targets).
        private readonly IntPtr _ptr;
        private readonly int _length;
        private int _pos;

        internal WireReader(FfiBuf buf)
        {
            _ptr = buf.ptr;
            _length = buf.ptr == IntPtr.Zero ? 0 : checked((int)(nuint)buf.len);
            _pos = 0;
        }

        internal bool ReadBool() => ReadU8() != 0;

        internal sbyte ReadI8()
        {
            Require(1, "i8");
            sbyte v = (sbyte)Marshal.ReadByte(_ptr, _pos);
            _pos += 1;
            return v;
        }

        internal byte ReadU8()
        {
            Require(1, "u8");
            byte v = Marshal.ReadByte(_ptr, _pos);
            _pos += 1;
            return v;
        }

        internal short ReadI16()
        {
            Require(2, "i16");
            short v = Marshal.ReadInt16(_ptr, _pos);
            _pos += 2;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal ushort ReadU16()
        {
            Require(2, "u16");
            ushort v = (ushort)Marshal.ReadInt16(_ptr, _pos);
            _pos += 2;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal int ReadI32()
        {
            Require(4, "i32");
            int v = Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal uint ReadU32()
        {
            Require(4, "u32");
            uint v = (uint)Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal long ReadI64()
        {
            Require(8, "i64");
            long v = Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal ulong ReadU64()
        {
            Require(8, "u64");
            ulong v = (ulong)Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal float ReadF32()
        {
            Require(4, "f32");
            int bits = Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.Int32BitsToSingle(BitConverter.IsLittleEndian ? bits : BinaryPrimitives.ReverseEndianness(bits));
        }

        internal double ReadF64()
        {
            Require(8, "f64");
            long bits = Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.Int64BitsToDouble(BitConverter.IsLittleEndian ? bits : BinaryPrimitives.ReverseEndianness(bits));
        }

        // usize/isize travel as 64-bit integers on the wire so the wire
        // layout stays stable across pointer widths.
        internal nint ReadNInt() => (nint)ReadI64();
        internal nuint ReadNUInt() => (nuint)ReadU64();

        internal string ReadString()
        {
            int len = ReadI32();
            if (len == 0) return "";
            if (len < 0) throw new InvalidOperationException("corrupt wire: negative string length");
            Require(len, "string payload");
            string v = Marshal.PtrToStringUTF8(_ptr + _pos, len)
                ?? throw new InvalidOperationException("PtrToStringUTF8 returned null");
            _pos += len;
            return v;
        }

        internal byte[] ReadBytes()
        {
            int len = ReadI32();
            if (len == 0) return Array.Empty<byte>();
            if (len < 0) throw new InvalidOperationException("corrupt wire: negative bytes length");
            Require(len, "bytes payload");
            byte[] v = new byte[len];
            Marshal.Copy(_ptr + _pos, v, 0, len);
            _pos += len;
            return v;
        }

        /// Reads the remaining bytes of this buffer as a `T[]`, assuming
        /// the buffer holds a raw element array with no length prefix.
        /// This matches the wire shape of a top-level `Vec<T>` return,
        /// where the FfiBuf's own `len` provides the element count.
        internal T[] ReadBlittableArray<T>() where T : unmanaged
        {
            int byteCount = _length - _pos;
            if (byteCount < 0)
                throw new InvalidOperationException("corrupt wire: read position past end");
            int elementSize = Unsafe.SizeOf<T>();
            if (byteCount % elementSize != 0)
                throw new InvalidOperationException(
                    "corrupt wire: blittable array byte count is not a multiple of element size");
            if (byteCount == 0) return Array.Empty<T>();
            int count = byteCount / elementSize;
            T[] result = new T[count];
            // Fast path via Marshal.Copy's per-type overloads for the types
            // that ship with one (byte, short, int, long, float, double).
            // Unsigned primitives and any other unmanaged T fall through to
            // a byte scratch buffer reinterpreted via MemoryMarshal.AsBytes.
            // Little-endian hosts only; big-endian would need byte-swap.
            switch (result)
            {
                case byte[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, byteCount);
                    break;
                case short[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case int[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case long[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case float[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case double[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                default:
                    byte[] scratch = new byte[byteCount];
                    Marshal.Copy(_ptr + _pos, scratch, 0, byteCount);
                    scratch.AsSpan().CopyTo(MemoryMarshal.AsBytes(result.AsSpan()));
                    break;
            }
            _pos += byteCount;
            return result;
        }

        internal bool[] ReadBoolArray()
        {
            int count = _length - _pos;
            if (count < 0)
                throw new InvalidOperationException("corrupt wire: read position past end");
            if (count == 0) return Array.Empty<bool>();
            bool[] result = new bool[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = Marshal.ReadByte(_ptr, _pos + i) != 0;
            }
            _pos += count;
            return result;
        }

        internal nint[] ReadNIntArray()
        {
            long[] raw = ReadBlittableArray<long>();
            nint[] result = new nint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nint)raw[i];
            return result;
        }

        internal nuint[] ReadNUIntArray()
        {
            long[] raw = ReadBlittableArray<long>();
            nuint[] result = new nuint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nuint)(ulong)raw[i];
            return result;
        }

        /// Reads a length-prefixed `T[]` whose bytes follow the 4-byte count.
        /// Used for `Vec<T>` nested inside an encoded outer container, where
        /// the outer codec needs an explicit count to know how far to advance
        /// the cursor between sibling elements.
        internal T[] ReadLengthPrefixedBlittableArray<T>() where T : unmanaged
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<T>();
            int elementSize = Unsafe.SizeOf<T>();
            int byteCount = checked(count * elementSize);
            Require(byteCount, "blittable array payload");
            T[] result = new T[count];
            switch (result)
            {
                case byte[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, byteCount);
                    break;
                case short[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case int[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case long[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case float[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case double[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                default:
                    byte[] scratch = new byte[byteCount];
                    Marshal.Copy(_ptr + _pos, scratch, 0, byteCount);
                    scratch.AsSpan().CopyTo(MemoryMarshal.AsBytes(result.AsSpan()));
                    break;
            }
            _pos += byteCount;
            return result;
        }

        internal bool[] ReadLengthPrefixedBoolArray()
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<bool>();
            Require(count, "bool array payload");
            bool[] result = new bool[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = Marshal.ReadByte(_ptr, _pos + i) != 0;
            }
            _pos += count;
            return result;
        }

        internal nint[] ReadLengthPrefixedNIntArray()
        {
            long[] raw = ReadLengthPrefixedBlittableArray<long>();
            nint[] result = new nint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nint)raw[i];
            return result;
        }

        internal nuint[] ReadLengthPrefixedNUIntArray()
        {
            long[] raw = ReadLengthPrefixedBlittableArray<long>();
            nuint[] result = new nuint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nuint)(ulong)raw[i];
            return result;
        }

        /// Reads a length-prefixed array whose elements are decoded by
        /// invoking `read` once per slot. Used for `Vec<T>` where each
        /// element has variable wire width (strings, nested encoded vecs,
        /// records).
        internal T[] ReadEncodedArray<T>(Func<WireReader, T> read)
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<T>();
            T[] result = new T[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = read(this);
            }
            return result;
        }

        private void Require(int n, string kind)
        {
            if (n < 0 || n > _length - _pos) throw new InvalidOperationException("corrupt wire: truncated " + kind);
        }
    }

{% endif %}
{%- if module.needs_wire_writer() %}
    internal sealed class WireWriter : IDisposable
    {
        private const int MinCapacity = 16;

        private byte[] _buffer;
        private int _pos;
        private bool _disposed;

        internal WireWriter(int initialCapacity)
        {
            int cap = Math.Max(initialCapacity, MinCapacity);
            _buffer = ArrayPool<byte>.Shared.Rent(cap);
            _pos = 0;
            _disposed = false;
        }

        /// <summary>Copy the written bytes into a fresh managed array.</summary>
        internal byte[] ToArray()
        {
            if (_pos == 0) return Array.Empty<byte>();
            byte[] result = new byte[_pos];
            Buffer.BlockCopy(_buffer, 0, result, 0, _pos);
            return result;
        }

        internal int Position => _pos;

        internal void WriteBool(bool v) { EnsureCapacity(1); _buffer[_pos++] = (byte)(v ? 1 : 0); }
        internal void WriteI8(sbyte v) { EnsureCapacity(1); _buffer[_pos++] = (byte)v; }
        internal void WriteU8(byte v) { EnsureCapacity(1); _buffer[_pos++] = v; }
        internal void WriteI16(short v) { EnsureCapacity(2); BinaryPrimitives.WriteInt16LittleEndian(_buffer.AsSpan(_pos), v); _pos += 2; }
        internal void WriteU16(ushort v) { EnsureCapacity(2); BinaryPrimitives.WriteUInt16LittleEndian(_buffer.AsSpan(_pos), v); _pos += 2; }
        internal void WriteI32(int v) { EnsureCapacity(4); BinaryPrimitives.WriteInt32LittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteU32(uint v) { EnsureCapacity(4); BinaryPrimitives.WriteUInt32LittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteI64(long v) { EnsureCapacity(8); BinaryPrimitives.WriteInt64LittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }
        internal void WriteU64(ulong v) { EnsureCapacity(8); BinaryPrimitives.WriteUInt64LittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }
        internal void WriteF32(float v) { EnsureCapacity(4); BinaryPrimitives.WriteSingleLittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteF64(double v) { EnsureCapacity(8); BinaryPrimitives.WriteDoubleLittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }

        internal void WriteNInt(nint v) => WriteI64((long)v);
        internal void WriteNUInt(nuint v) => WriteU64((ulong)v);

        internal void WriteString(string v)
        {
            int byteCount = Encoding.UTF8.GetByteCount(v);
            WriteI32(byteCount);
            if (byteCount == 0) return;
            EnsureCapacity(byteCount);
            Encoding.UTF8.GetBytes(v, 0, v.Length, _buffer, _pos);
            _pos += byteCount;
        }

        internal void WriteBytes(byte[] v)
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            EnsureCapacity(v.Length);
            Buffer.BlockCopy(v, 0, _buffer, _pos, v.Length);
            _pos += v.Length;
        }

        internal void WriteBlittableArray<T>(T[] v) where T : unmanaged
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            int byteCount = checked(v.Length * Unsafe.SizeOf<T>());
            EnsureCapacity(byteCount);
            // Reinterpret the source T[] as bytes and block-copy into the
            // managed buffer. Zero extra allocations, one copy. Little-endian
            // hosts only; big-endian would need byte-swap.
            MemoryMarshal.AsBytes(v.AsSpan()).CopyTo(_buffer.AsSpan(_pos, byteCount));
            _pos += byteCount;
        }

        internal void WriteBoolArray(bool[] v)
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            EnsureCapacity(v.Length);
            for (int i = 0; i < v.Length; i++)
            {
                _buffer[_pos + i] = (byte)(v[i] ? 1 : 0);
            }
            _pos += v.Length;
        }

        internal void WriteNIntArray(nint[] v)
        {
            long[] widened = new long[v.Length];
            for (int i = 0; i < v.Length; i++) widened[i] = (long)v[i];
            WriteBlittableArray(widened);
        }

        internal void WriteNUIntArray(nuint[] v)
        {
            long[] widened = new long[v.Length];
            for (int i = 0; i < v.Length; i++) widened[i] = (long)(ulong)v[i];
            WriteBlittableArray(widened);
        }

        /// Byte size of an encoded `T[]`: the `sizeof(int)` length prefix
        /// plus the per-element size. Size expressions for `Vec<T>` with
        /// variable-width elements (strings, nested vecs, records) use this
        /// helper to drive the `WireWriter` pre-size so its initial
        /// capacity matches the payload and no growth copy is needed.
        internal static int EncodedArraySize<T>(T[] v, Func<T, int> sizer)
        {
            int total = sizeof(int);
            for (int i = 0; i < v.Length; i++) total += sizer(v[i]);
            return total;
        }

        private void EnsureCapacity(int additional)
        {
            if (_pos + additional <= _buffer.Length) return;
            int next = Math.Max(_buffer.Length * 2, _pos + additional);
            byte[] grown = ArrayPool<byte>.Shared.Rent(next);
            Buffer.BlockCopy(_buffer, 0, grown, 0, _pos);
            ArrayPool<byte>.Shared.Return(_buffer);
            _buffer = grown;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            ArrayPool<byte>.Shared.Return(_buffer);
            _buffer = Array.Empty<byte>();
        }
    }

{% endif %}
    internal static class NativeMethods
    {
        private const string LibName = "{{ module.lib_name }}";
{%- if module.needs_ffi_buf() %}

        [DllImport(LibName, EntryPoint = "{{ module.prefix }}_free_buf")]
        internal static extern void FreeBuf(FfiBuf buf);
{%- endif %}
{%- for func in module.functions %}

        [DllImport(LibName, EntryPoint = "{{ func.ffi_name }}")]
{%- if func.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ func.native_return_type() }} {{ func.name }}({{ func.native_param_list() }});
{%- endfor %}
{%- for enumeration in module.enums %}
{%- for method in enumeration.methods %}

        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
{%- if method.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ method.native_method_name }}({{ method.native_param_list(enumeration.class_name, false) }});
{%- endfor %}
{%- endfor %}
    }
}
