"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.SrpcByteStream = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const node_stream_1 = require("node:stream");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":3},{\"kind\":2,\"typeName\":\"(error?: Error | null) =\\u003e void\"},{\"kind\":18,\"typeName\":\"IByteStreamable\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false}]},{\"kind\":2}]]", []);
const ByteStreamInfo = Symbol('ByteStreamInfo');
const PENDING_RECEIVER_MAX_BYTES = 2 * 1024 * 1024;
const PENDING_RECEIVER_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
const PENDING_RECEIVER_MAX_COUNT = 1024;
const PENDING_RECEIVER_TTL_MS = 5000;
let _SrpcByteStreamMetadata_1;
class SrpcByteStream extends node_stream_1.Duplex {
    _id;
    parent;
    isSender;
    cleaned = false;
    remotelyDestroyed = false;
    remoteFinished = false;
    localFinished = false;
    get id() {
        return this._id;
    }
    constructor(stream, id = 0) {
        super();
        SrpcByteStream.ensureInfo(stream);
        this.parent = stream;
        this.parent.byteStream.attachDisconnectHandler(this.handleDisconnect);
        const info = stream.byteStream[ByteStreamInfo];
        if (id === 0) {
            this._id = info.nextId;
            info.nextId += info.step;
            this.isSender = true;
            info.senders.set(this._id, this);
        }
        else {
            this._id = id;
            this.isSender = false;
            info.receivers.set(this._id, this);
            this.flushPendingReceiver(info);
            this.on('end', () => this.cleanup());
        }
    }
    static init(stream, options) {
        const existing = stream.byteStream[ByteStreamInfo];
        if (existing)
            clearPendingReceivers(existing);
        stream.byteStream[ByteStreamInfo] = {
            receivers: new Map(),
            senders: new Map(),
            nextId: options.startId,
            step: options.step,
            pendingReceiverBytes: 0
        };
    }
    static createReceiver(stream, id) {
        if (typeof id !== 'number')
            throw new Error('Missing stream ID');
        if (stream.byteStream[ByteStreamInfo]?.receivers.has(id))
            throw new Error(`Stream ${id} already exists`);
        return new SrpcByteStream(stream, id);
    }
    static createSender(stream) {
        return new SrpcByteStream(stream);
    }
    static writeReceiver(stream, id, data) {
        const info = SrpcByteStream.ensureInfo(stream);
        const receiver = info.receivers.get(id);
        const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
        if (receiver) {
            receiver.push(chunk);
            return;
        }
        const pending = getPendingReceiver(info, id);
        if (!pending)
            return;
        if (pending.destroyedError)
            return;
        if (pending.bytes + chunk.length > PENDING_RECEIVER_MAX_BYTES ||
            info.pendingReceiverBytes + chunk.length > PENDING_RECEIVER_MAX_TOTAL_BYTES) {
            releasePendingReceiverBytes(info, pending);
            pending.destroyedError = new Error('Pending receiver exceeded max buffered bytes');
            pending.chunks = [];
            pending.bytes = 0;
            return;
        }
        pending.chunks.push(chunk);
        pending.bytes += chunk.length;
        info.pendingReceiverBytes += chunk.length;
    }
    static finishReceiver(stream, id) {
        const info = SrpcByteStream.ensureInfo(stream);
        const receiver = info.receivers.get(id);
        if (receiver) {
            receiver.remoteFinished = true;
            receiver.push(null);
            return;
        }
        const pending = getPendingReceiver(info, id);
        if (!pending)
            return;
        if (pending.destroyedError)
            return;
        pending.finished = true;
    }
    static destroySubstream(stream, id, err) {
        const info = SrpcByteStream.ensureInfo(stream);
        const error = err ? new Error(err) : undefined;
        const receiver = info.receivers.get(id);
        if (receiver) {
            receiver.remotelyDestroyed = true;
            receiver.destroy(error);
            return;
        }
        const sender = info.senders.get(id);
        if (sender) {
            sender.remotelyDestroyed = true;
            sender.destroy(error);
            return;
        }
        const pending = getPendingReceiver(info, id);
        if (!pending)
            return;
        releasePendingReceiverBytes(info, pending);
        pending.destroyedError = error ?? new Error('Remote stream destroyed');
        pending.chunks = [];
        pending.bytes = 0;
    }
    _read() { }
    _write(chunk, _encoding, callback) {
        try {
            Promise.resolve(this.parent.byteStream.write(this._id, chunk)).then(result => {
                if (result === false)
                    callback(new Error(`SRPC byte stream ${this._id} is not writable`));
                else
                    callback();
            }, error => callback(error instanceof Error ? error : new Error(String(error))));
        }
        catch (error) {
            callback(error instanceof Error ? error : new Error(String(error)));
        }
    }
    _final(callback) {
        try {
            this.localFinished = true;
            this.parent.byteStream.finish(this._id);
            callback();
        }
        catch (error) {
            callback(error instanceof Error ? error : new Error(String(error)));
        }
    }
    _destroy(error, callback) {
        const normalSenderFinish = this.isSender && this.localFinished && !error;
        if (!this.remotelyDestroyed && !normalSenderFinish && (this.isSender || !this.remoteFinished)) {
            this.parent.byteStream.destroy(this._id, error ?? undefined);
        }
        this.cleanup();
        callback(error);
    }
    flushPendingReceiver(info) {
        const pending = info.pendingReceivers?.get(this._id);
        if (!pending)
            return;
        deletePendingReceiver(info, this._id, pending);
        if (pending.destroyedError) {
            this.destroy(pending.destroyedError);
            return;
        }
        for (const chunk of pending.chunks)
            this.push(chunk);
        if (pending.finished) {
            this.remoteFinished = true;
            this.push(null);
        }
    }
    handleDisconnect = () => {
        this.remotelyDestroyed = true;
        this.destroy();
    };
    cleanup() {
        if (this.cleaned)
            return;
        this.cleaned = true;
        const info = this.parent.byteStream[ByteStreamInfo];
        if (this.isSender)
            info?.senders.delete(this._id);
        else
            info?.receivers.delete(this._id);
        this.parent.byteStream.detachDisconnectHandler(this.handleDisconnect);
    }
    static ensureInfo(stream) {
        stream.byteStream[ByteStreamInfo] ??= {
            receivers: new Map(),
            senders: new Map(),
            nextId: 1,
            step: 1,
            pendingReceiverBytes: 0
        };
        return stream.byteStream[ByteStreamInfo];
    }
    static __tsfType = _SrpcByteStreamMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"SrpcByteStream\",\"typeName\":\"SrpcByteStream\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"_read\",\"parameters\":[],\"returnType\":{\"$tsfType\":0}},{\"name\":\"_write\",\"parameters\":[{\"name\":\"chunk\",\"type\":{\"kind\":16,\"typeName\":\"Buffer\",\"classType\":{\"$tsf\":1}},\"optional\":false,\"default\":false},{\"name\":\"_encoding\",\"type\":{\"kind\":16,\"typeName\":\"BufferEncoding\",\"classType\":{\"$tsf\":2}},\"optional\":false,\"default\":false},{\"name\":\"callback\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":0}},{\"name\":\"_final\",\"parameters\":[{\"name\":\"callback\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":0}},{\"name\":\"_destroy\",\"parameters\":[{\"name\":\"error\",\"type\":{\"kind\":12,\"types\":[{\"kind\":16,\"typeName\":\"Error\",\"classType\":{\"$tsf\":3}},{\"kind\":5}]},\"optional\":false,\"default\":false},{\"name\":\"callback\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":0}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"stream\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false},{\"name\":\"id\",\"type\":{\"$tsfType\":3},\"optional\":true,\"default\":true}]}]", [() => SrpcByteStream, () => (typeof Buffer !== "undefined" ? Buffer : (typeof exports !== "undefined" ? exports.Buffer : undefined)), () => (typeof BufferEncoding !== "undefined" ? BufferEncoding : (typeof exports !== "undefined" ? exports.BufferEncoding : undefined)), () => (typeof Error !== "undefined" ? Error : (typeof exports !== "undefined" ? exports.Error : undefined))], __tsf_metadata_type);
}
exports.SrpcByteStream = SrpcByteStream;
_SrpcByteStreamMetadata_1.classType = SrpcByteStream;
SrpcByteStream.__tsfType = _SrpcByteStreamMetadata_1;
function getPendingReceiver(info, id) {
    info.pendingReceivers ??= new Map();
    const existing = info.pendingReceivers.get(id);
    if (existing)
        return existing;
    if (info.pendingReceivers.size >= PENDING_RECEIVER_MAX_COUNT)
        return undefined;
    const pending = { chunks: [], bytes: 0, finished: false };
    pending.timeout = setTimeout(() => {
        deletePendingReceiver(info, id, pending);
    }, PENDING_RECEIVER_TTL_MS);
    pending.timeout.unref?.();
    info.pendingReceivers.set(id, pending);
    return pending;
}
function releasePendingReceiverBytes(info, pending) {
    if (pending.bytes <= 0)
        return;
    info.pendingReceiverBytes = Math.max(0, info.pendingReceiverBytes - pending.bytes);
    pending.bytes = 0;
}
function deletePendingReceiver(info, id, pending) {
    releasePendingReceiverBytes(info, pending);
    if (pending.timeout)
        clearTimeout(pending.timeout);
    info.pendingReceivers?.delete(id);
}
function clearPendingReceivers(info) {
    for (const [id, pending] of info.pendingReceivers ?? [])
        deletePendingReceiver(info, id, pending);
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"IByteStream\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"IByteStreamable\":{\"kind\":18,\"typeName\":\"IByteStreamable\",\"types\":[{\"kind\":20,\"name\":\"byteStream\",\"type\":{\"kind\":18,\"typeName\":\"IByteStream\",\"types\":[{\"kind\":20,\"name\":\"parentStreamId\",\"type\":{\"kind\":6},\"optional\":false}]},\"optional\":false}]}}]", [], __tsf_metadata_type);
//# sourceMappingURL=SrpcByteStream.js.map