// <auto-generated>
// This file was generated by BoltFFI. Do not edit.
// </auto-generated>
#nullable enable

using System;
{%- if class.has_streams() %}
using System.Collections.Generic;
{%- endif %}
using System.Threading;
{%- if class.has_pinned_params() || class.has_streams() %}
using System.Runtime.CompilerServices;
{%- endif %}
{%- if class.has_streams() %}
using System.Threading.Tasks;
{%- endif %}
{%- if class.needs_system_text() %}
using System.Text;
{%- endif %}

namespace {{ namespace }}
{
{{ self::summary_doc_block(class.summary_doc, "    ") }}    public sealed class {{ class.class_name }} : IDisposable
    {
        private IntPtr _handle;

        // Private handle-adopting ctor; used only by chained `: this(...)` and the static factories below.
        private {{ class.class_name }}(IntPtr handle)
        {
            _handle = handle;
        }
{%- for ctor in class.constructors %}
{%- match ctor.kind %}
{%- when CSharpConstructorKind::Primary with { helper_method_name } %}

{{ self::summary_doc_block(ctor.summary_doc, "        ") }}        public {{ class.class_name }}({% for p in ctor.params %}{{ p.csharp_type }} {{ p.name }}{% if !loop.last %}, {% endif %}{% endfor %})
            : this({{ helper_method_name }}({% for p in ctor.params %}{{ p.name }}{% if !loop.last %}, {% endif %}{% endfor %})) { }

        private static IntPtr {{ helper_method_name }}({% for p in ctor.params %}{{ p.csharp_type }} {{ p.name }}{% if !loop.last %}, {% endif %}{% endfor %})
        {
{%- for param in ctor.params %}
{%- if let Some(decl) = param.setup_declaration() %}
            {{ decl }}
{%- endif %}
{%- endfor %}
{%- for writer in ctor.wire_writers %}
            using var {{ writer.binding_name }} = new WireWriter({{ writer.size_expr }});
{%- for stmt in writer.encode_stmts %}
            {{ stmt }};
{%- endfor %}
            byte[] {{ writer.bytes_binding_name }} = {{ writer.binding_name }}.ToArray();
{%- endfor %}
{%- if ctor.has_pinned_params() %}
            unsafe
            {
{%- for param in ctor.params %}
{%- match param.kind %}
{%- when CSharpParamKind::PinnedArray with { element_type, ptr_local } %}
                fixed ({{ element_type }}* {{ ptr_local }} = {{ param.name }})
                {
{%- when _ %}
{%- endmatch %}
{%- endfor %}
                return NativeMethods.{{ ctor.native_method_name }}({{ ctor.native_call_args() }});
{%- for param in ctor.params %}
{%- if param.is_pinned() %}
                }
{%- endif %}
{%- endfor %}
            }
{%- else %}
            return NativeMethods.{{ ctor.native_method_name }}({{ ctor.native_call_args() }});
{%- endif %}
        }
{%- when CSharpConstructorKind::StaticFactory with { name } %}

{{ self::summary_doc_block(ctor.summary_doc, "        ") }}        public static {{ class.class_name }} {{ name }}({% for p in ctor.params %}{{ p.csharp_type }} {{ p.name }}{% if !loop.last %}, {% endif %}{% endfor %})
        {
{%- for param in ctor.params %}
{%- if let Some(decl) = param.setup_declaration() %}
            {{ decl }}
{%- endif %}
{%- endfor %}
{%- for writer in ctor.wire_writers %}
            using var {{ writer.binding_name }} = new WireWriter({{ writer.size_expr }});
{%- for stmt in writer.encode_stmts %}
            {{ stmt }};
{%- endfor %}
            byte[] {{ writer.bytes_binding_name }} = {{ writer.binding_name }}.ToArray();
{%- endfor %}
{%- if ctor.has_pinned_params() %}
            unsafe
            {
{%- for param in ctor.params %}
{%- match param.kind %}
{%- when CSharpParamKind::PinnedArray with { element_type, ptr_local } %}
                fixed ({{ element_type }}* {{ ptr_local }} = {{ param.name }})
                {
{%- when _ %}
{%- endmatch %}
{%- endfor %}
                return new {{ class.class_name }}(NativeMethods.{{ ctor.native_method_name }}({{ ctor.native_call_args() }}));
{%- for param in ctor.params %}
{%- if param.is_pinned() %}
                }
{%- endif %}
{%- endfor %}
            }
{%- else %}
            return new {{ class.class_name }}(NativeMethods.{{ ctor.native_method_name }}({{ ctor.native_call_args() }}));
{%- endif %}
        }
{%- endmatch %}
{%- endfor %}
{%- for method in class.methods %}
{% include "render_csharp/class_method.txt" %}
{%- endfor %}
{%- for stream in class.streams %}

{{ self::summary_doc_block(stream.summary_doc, "        ") }}        /// <remarks>
        /// The returned <see cref="IAsyncEnumerable{T}"/> owns the underlying
        /// subscription. Disposing the owning <see cref="{{ class.class_name }}"/>
        /// instance does not, by itself, end iteration - the producer simply
        /// stops emitting events. Callers must either cancel the supplied
        /// <see cref="CancellationToken"/> or break out of <c>await foreach</c>
        /// to release the subscription.
        /// </remarks>
        public async IAsyncEnumerable<{{ stream.item_type }}> {{ stream.name }}([EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            ThrowIfDisposed();
            IntPtr subscription = NativeMethods.{{ stream.subscribe_method_name }}(_handle);
            if (subscription == IntPtr.Zero) yield break;

            global::System.Threading.Tasks.Task<int>? _pendingWait = null;
            try
            {
                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    while (true)
                    {
                        {{ stream.item_type }}[] _items = new {{ stream.item_type }}[BoltFFIStream.DefaultBatchSize];
                        int _itemCount;
                        UIntPtr _count;
                        unsafe
                        {
                            fixed ({{ stream.item_type }}* _itemsPtr = _items)
                            {
                                _count = NativeMethods.{{ stream.pop_batch_method_name }}(subscription, (IntPtr)_itemsPtr, (UIntPtr)_items.Length);
                            }
                        }
                        _itemCount = checked((int)(nuint)_count);
                        if (_itemCount == 0) break;

                        for (int _i = 0; _i < _itemCount; _i++)
                        {
                            cancellationToken.ThrowIfCancellationRequested();
                            yield return _items[_i];
                        }

                        if (_itemCount < BoltFFIStream.DefaultBatchSize) break;
                    }

                    _pendingWait = global::System.Threading.Tasks.Task.Run(
                        () => NativeMethods.{{ stream.wait_method_name }}(subscription, BoltFFIStream.WaitTimeoutMilliseconds),
                        cancellationToken);
                    int _waitResult = await _pendingWait.ConfigureAwait(false);
                    _pendingWait = null;
                    if (_waitResult == BoltFFIStream.WaitResultUnsubscribed) yield break;
                }
            }
            finally
            {
                NativeMethods.{{ stream.unsubscribe_method_name }}(subscription);
                if (_pendingWait is not null)
                {
                    try { await _pendingWait.ConfigureAwait(false); }
                    catch { /* wait may have observed cancellation; ignore */ }
                }
                NativeMethods.{{ stream.free_method_name }}(subscription);
            }
        }
{%- endfor %}

        internal IntPtr RawHandle => _handle;

        private void ThrowIfDisposed()
        {
            if (_handle == IntPtr.Zero) throw new ObjectDisposedException(nameof({{ class.class_name }}));
        }

        public void Dispose()
        {
            IntPtr handle = Interlocked.Exchange(ref _handle, IntPtr.Zero);
            if (handle == IntPtr.Zero) return;
            NativeMethods.{{ class.native_free_method_name }}(handle);
            GC.SuppressFinalize(this);
        }

        ~{{ class.class_name }}()
        {
            Dispose();
        }
    }
}
