"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.MeshRemoteSrpcConnection = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-reflection/type-metadata-runtime"));
const SrpcByteStream_1 = require("../../srpc/SrpcByteStream");
const types_1 = require("../../srpc/types");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":6},{\"kind\":7},{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"remoteSenderIdParity\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":0},{\"kind\":10,\"literal\":1}]},{\"kind\":4}]},\"optional\":true}]},{\"kind\":3},{\"kind\":2},{\"kind\":22,\"type\":{\"kind\":3}},{\"kind\":8},{\"kind\":14,\"type\":{\"kind\":7}}]]", []);
const MaxBackpressuredReceiverStreams = 128;
const MaxTrackedReceiverStreams = 1024;
const MaxBufferedReceiverBytes = 8 * 1024 * 1024;
let _MeshRemoteSrpcConnectionMetadata_1;
class MeshRemoteSrpcConnection {
    id;
    clientId;
    connectedAt;
    ownerNodeId;
    ownerProcessId;
    ownerEndpointId;
    ownerEndpointPublicKey;
    capability;
    byteStream;
    senderIds;
    activeSenderIds = new Set();
    transport;
    supportsSenderIdRelease;
    disconnectHandlers = new Set();
    backpressuredReceivers = new Set();
    receiverBufferedBytes = new Map();
    _meta;
    stale = false;
    refillPending;
    lastUsedAt = Date.now();
    constructor(options) {
        this.id = options.id;
        this.clientId = options.clientId;
        this._meta = options.meta;
        this.connectedAt = options.connectedAt;
        this.ownerNodeId = options.ownerNodeId;
        this.ownerProcessId = options.ownerProcessId;
        this.ownerEndpointId = options.ownerEndpointId;
        this.ownerEndpointPublicKey = options.ownerEndpointPublicKey;
        this.capability = options.capability;
        this.senderIds = [...options.senderIds];
        this.transport = options.transport;
        this.supportsSenderIdRelease = options.supportsSenderIdRelease ?? Boolean(options.transport.releaseSenderIds);
        this.byteStream = {
            parentStreamId: this.id,
            remoteSenderIdParity: 1,
            allocateSenderId: () => {
                this.assertConnected();
                this.touch();
                const id = this.senderIds.shift();
                if (id === undefined) {
                    this.refillSenderIds();
                    throw new types_1.SrpcStreamClosedError('No reserved remote sRPC byte stream IDs remain');
                }
                this.activeSenderIds.add(id);
                if (this.senderIds.length <= 16)
                    this.refillSenderIds();
                return id;
            },
            attachReceiver: streamId => options.transport.attachReceiver(this, streamId),
            write: (streamId, data) => options.transport.writeStream(this, streamId, toBytes(data)),
            finish: streamId => this.finishStream(streamId),
            destroy: (streamId, error) => this.destroyStream(streamId, error),
            announceSender: streamId => this.activateSender(streamId),
            attachDisconnectHandler: handler => this.disconnectHandlers.add(handler),
            detachDisconnectHandler: handler => this.disconnectHandlers.delete(handler),
            getBufferedAmount: () => 0,
            receiverBufferChanged: (streamId, bufferedBytes) => this.updateReceiverBufferedBytes(streamId, bufferedBytes)
        };
        SrpcByteStream_1.SrpcByteStream.init(this, { startId: 2, step: 2 });
    }
    get connected() {
        return !this.stale;
    }
    resolveByteStream() {
        this.assertConnected();
        return this.byteStream;
    }
    get meta() {
        return this._meta;
    }
    get idleSince() {
        return this.lastUsedAt;
    }
    get hasActiveStreams() {
        return this.disconnectHandlers.size > 0;
    }
    touch() {
        this.lastUsedAt = Date.now();
    }
    applyMetadata(metadata) {
        this._meta = metadata;
    }
    async close(reason) {
        this.assertConnected();
        try {
            await this.transport.disconnect(this, reason);
        }
        finally {
            this.markStale();
        }
    }
    receiveWrite(streamId, data) {
        this.assertConnected();
        try {
            this.touch();
            const hasReceiver = SrpcByteStream_1.SrpcByteStream.hasReceiver(this, streamId);
            const wasBackpressured = this.backpressuredReceivers.has(streamId);
            const accepted = SrpcByteStream_1.SrpcByteStream.writeReceiver(this, streamId, data);
            this.updateReceiverBufferedBytes(streamId, SrpcByteStream_1.SrpcByteStream.getReceiverBufferedBytes(this, streamId));
            if (this.receiverBufferedBytes.size > MaxTrackedReceiverStreams || this.totalReceiverBufferedBytes() > MaxBufferedReceiverBytes) {
                const error = new types_1.SrpcBackpressureError('Remote sRPC byte stream receiver capacity exceeded');
                this.receiveDestroy(streamId, error.message);
                void this.transport.destroyStream(this, streamId, error).catch(() => { });
                return false;
            }
            if (accepted)
                return true;
            if (!hasReceiver)
                return false;
            // Readable.push(false) still accepted this chunk. Mesh relays pause
            // their receiver while a forwarded write is in flight, so multiple
            // chunks can arrive before it resumes. The aggregate receiver limit
            // above bounds that normal pressure without treating one mesh RTT as
            // a non-draining stream.
            if (!wasBackpressured && this.backpressuredReceivers.size < MaxBackpressuredReceiverStreams) {
                this.backpressuredReceivers.add(streamId);
            }
            return true;
        }
        catch (error) {
            this.markStale();
            throw error;
        }
    }
    receiveFinish(streamId) {
        if (this.stale)
            return;
        try {
            this.touch();
            this.activeSenderIds.delete(streamId);
            this.clearReceiverPressure(streamId);
            SrpcByteStream_1.SrpcByteStream.finishReceiver(this, streamId);
        }
        catch (error) {
            this.markStale();
            throw error;
        }
    }
    receiveDestroy(streamId, error) {
        if (this.stale)
            return;
        try {
            this.touch();
            this.activeSenderIds.delete(streamId);
            this.clearReceiverPressure(streamId);
            if (SrpcByteStream_1.SrpcByteStream.consumeTerminalSender(this, streamId))
                return;
            SrpcByteStream_1.SrpcByteStream.destroySubstream(this, streamId, error);
        }
        catch (cause) {
            this.markStale();
            throw cause;
        }
    }
    markStale() {
        if (this.stale)
            return;
        this.stale = true;
        this.backpressuredReceivers.clear();
        this.receiverBufferedBytes.clear();
        const reservedSenderIds = this.takeReservedSenderIds();
        const handlers = [...this.disconnectHandlers];
        this.disconnectHandlers.clear();
        for (const handler of handlers) {
            try {
                handler();
            }
            catch { }
        }
        this.releaseReservedSenderIds(reservedSenderIds);
    }
    takeReservedSenderIds() {
        const ids = [...this.senderIds, ...this.activeSenderIds];
        this.senderIds.length = 0;
        this.activeSenderIds.clear();
        return this.supportsSenderIdRelease ? ids : [];
    }
    async finishStream(streamId) {
        await this.transport.finishStream(this, streamId);
        this.activeSenderIds.delete(streamId);
    }
    async destroyStream(streamId, error) {
        await this.transport.destroyStream(this, streamId, error);
        this.activeSenderIds.delete(streamId);
    }
    activateSender(streamId) {
        const activate = this.transport.activateSender;
        if (!activate)
            return;
        void activate.call(this.transport, this, streamId).catch(() => {
            // Activation is the authority boundary for an allocated sender.
            // A failed signal leaves its ownership ambiguous, so fence the
            // complete handle and synchronously detach every substream.
            this.markStale();
        });
    }
    refillSenderIds() {
        if (this.refillPending || this.stale)
            return;
        this.refillPending = this.transport
            .reserveSenderIds(this)
            .then(ids => {
            if (!this.stale)
                this.senderIds.push(...ids);
            else
                this.releaseReservedSenderIds(ids);
        })
            .catch(() => { })
            .finally(() => {
            this.refillPending = undefined;
        });
    }
    assertConnected() {
        if (this.stale)
            throw new types_1.SrpcStaleConnectionError(this.clientId);
    }
    updateReceiverBufferedBytes(streamId, bufferedBytes) {
        const previous = this.receiverBufferedBytes.get(streamId) ?? 0;
        if (bufferedBytes < previous)
            this.backpressuredReceivers.delete(streamId);
        if (bufferedBytes > 0)
            this.receiverBufferedBytes.set(streamId, bufferedBytes);
        else
            this.receiverBufferedBytes.delete(streamId);
    }
    clearReceiverPressure(streamId) {
        this.backpressuredReceivers.delete(streamId);
        this.receiverBufferedBytes.delete(streamId);
    }
    totalReceiverBufferedBytes() {
        let total = 0;
        for (const bytes of this.receiverBufferedBytes.values())
            total += bytes;
        return total;
    }
    releaseReservedSenderIds(ids) {
        if (!this.supportsSenderIdRelease || !ids.length)
            return;
        const release = this.transport.releaseSenderIds;
        if (!release)
            return;
        try {
            void release(this, ids).catch(() => { });
        }
        catch { }
    }
    static __tsfType = _MeshRemoteSrpcConnectionMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"MeshRemoteSrpcConnection\",\"typeName\":\"MeshRemoteSrpcConnection\",\"classType\":{\"$tsf\":0},\"properties\":[{\"name\":\"id\",\"type\":{\"$tsfType\":0},\"optional\":false},{\"name\":\"clientId\",\"type\":{\"$tsfType\":0},\"optional\":false},{\"name\":\"connectedAt\",\"type\":{\"$tsfType\":1},\"optional\":false},{\"name\":\"ownerNodeId\",\"type\":{\"$tsfType\":1},\"optional\":false},{\"name\":\"ownerProcessId\",\"type\":{\"$tsfType\":0},\"optional\":true},{\"name\":\"ownerEndpointId\",\"type\":{\"$tsfType\":0},\"optional\":true},{\"name\":\"ownerEndpointPublicKey\",\"type\":{\"$tsfType\":0},\"optional\":true},{\"name\":\"capability\",\"type\":{\"$tsfType\":0},\"optional\":true},{\"name\":\"byteStream\",\"type\":{\"$tsfType\":2},\"optional\":false}],\"methods\":[{\"name\":\"resolveByteStream\",\"parameters\":[],\"returnType\":{\"$tsfType\":2}},{\"name\":\"touch\",\"parameters\":[],\"returnType\":{\"$tsfType\":3}},{\"name\":\"applyMetadata\",\"parameters\":[{\"name\":\"metadata\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"close\",\"parameters\":[{\"name\":\"reason\",\"type\":{\"$tsfType\":0},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":5}},{\"name\":\"receiveWrite\",\"parameters\":[{\"name\":\"streamId\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"kind\":16,\"typeName\":\"Uint8Array\",\"classType\":{\"$tsf\":1}},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":6}},{\"name\":\"receiveFinish\",\"parameters\":[{\"name\":\"streamId\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"receiveDestroy\",\"parameters\":[{\"name\":\"streamId\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false},{\"name\":\"error\",\"type\":{\"$tsfType\":0},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"markStale\",\"parameters\":[],\"returnType\":{\"$tsfType\":3}},{\"name\":\"takeReservedSenderIds\",\"parameters\":[],\"returnType\":{\"$tsfType\":7}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"options\",\"type\":{\"kind\":18,\"typeName\":\"MeshRemoteSrpcConnectionOptions\",\"types\":[{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"ownerNodeId\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"ownerProcessId\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"ownerEndpointId\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"ownerEndpointPublicKey\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"capability\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"senderIds\",\"type\":{\"kind\":14,\"type\":{\"kind\":7}},\"optional\":false},{\"kind\":20,\"name\":\"supportsSenderIdRelease\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"transport\",\"type\":{\"kind\":18,\"typeName\":\"MeshRemoteConnectionTransport\",\"types\":[]},\"optional\":false}]},\"optional\":false,\"default\":false}]}]", [() => MeshRemoteSrpcConnection, () => (typeof Uint8Array !== "undefined" ? Uint8Array : (typeof exports !== "undefined" ? exports.Uint8Array : undefined)), () => (typeof TMeta !== "undefined" ? TMeta : (typeof exports !== "undefined" ? exports.TMeta : undefined))], __tsf_metadata_type);
}
exports.MeshRemoteSrpcConnection = MeshRemoteSrpcConnection;
_MeshRemoteSrpcConnectionMetadata_1.classType = MeshRemoteSrpcConnection;
MeshRemoteSrpcConnection.__tsfType = _MeshRemoteSrpcConnectionMetadata_1;
function toBytes(value) {
    if (value instanceof Uint8Array)
        return value;
    throw new TypeError('sRPC byte stream writes require Uint8Array data');
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"MeshRemoteConnectionTransport\":{\"kind\":18,\"typeName\":\"MeshRemoteConnectionTransport\",\"types\":[],\"typeParameters\":[\"TMeta\"]},\"MeshRemoteSrpcConnectionOptions\":{\"kind\":18,\"typeName\":\"MeshRemoteSrpcConnectionOptions\",\"types\":[{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"clientId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"meta\",\"type\":{\"kind\":2,\"typeName\":\"TMeta\"},\"optional\":false},{\"kind\":20,\"name\":\"connectedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"ownerNodeId\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"ownerProcessId\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"ownerEndpointId\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"ownerEndpointPublicKey\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"capability\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"senderIds\",\"type\":{\"kind\":14,\"type\":{\"kind\":7}},\"optional\":false},{\"kind\":20,\"name\":\"supportsSenderIdRelease\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"transport\",\"type\":{\"kind\":18,\"typeName\":\"MeshRemoteConnectionTransport\",\"types\":[]},\"optional\":false}],\"typeParameters\":[\"TMeta\"]}}]")[1];
//# sourceMappingURL=mesh-srpc-remote-connection.js.map