var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _EnvelopeClient_instances, _EnvelopeClient_idFactory, _EnvelopeClient_dispatcher, _EnvelopeClient_callbacksByStreamID, _EnvelopeClient_logger, _EnvelopeClient_createAsyncMethod, _EnvelopeClient_createSourceHandler, _EnvelopeClient_createSinkHandler, _EnvelopeClient_createPipeHandler, _EnvelopeClient_emitRequestStreamAsEnvelopes, _EnvelopeClient_postDispatcher;
import { Envelope } from '@devvit/protos/types/devvit/runtime/envelope.js';
import { Observable } from 'rxjs';
import { v4 as UUIDv4 } from 'uuid';
import { VoidSystemLogger } from '../../lib/loggers/SystemLogger.js';
import { envelopeAsError, envelopeMethod, extractMetadataTraceID, requestMessageToBinary, responseMessageFromBinary, sendEnvelopeMessageToStream, } from './EnvelopeUtil.js';
/**
 * An EnvelopeClient is a wrapper around an Dispatcher that looks like a simply callable object of type T.
 * All of the method calls are translated into Envelope messages and sent to the underlying Dispatcher.
 * It's also an EnvelopeRecipient because it receives responses to the requests it makes from the dispatcher.
 */
export class EnvelopeClientBuilder {
    static build(serverHostname, knows, logger = new VoidSystemLogger(), idFactory = UUIDv4) {
        return new EnvelopeClient(serverHostname, knows, idFactory, logger);
    }
}
class EnvelopeClient {
    /**
     * Client hostnames always match their dispatcher. This is not a property
     * unique to clients.
     *
     * |                         |                        |                         |                                                                    |
     * | ----------------------- | ---------------------- | ----------------------- | ------------------------------------------------------------------ |
     * | Client                  | Server                 | Dispatcher              | Description                                                        |
     * | local                   | inspector.plugins.node | local                   | One of the local runtime's clients to a remote server's inspector. |
     * | local                   | logger.plugins.local   | local                   | One of the local runtime's clients to a local logger.              |
     * | node                    | supervisor.system.node | node                    | One of the Node.js runtime's clients to the local supervisor.      |
     * | supervisor.system.local | runtime.system.local   | supervisor.system.local | One of the supervisor's clients to the local runtime.              |
     *
     * When issuing requests, this is the Envelope.src. When receiving responses,
     * this is the nondistinct Envelope.dst. The client's dispatcher is expected
     * resolve by stream ID since the hostname is not unique among its clients.
     */
    get hostname() {
        return __classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f")?.hostname;
    }
    constructor(serverHostname, knows, idFactory, logger) {
        _EnvelopeClient_instances.add(this);
        this.type = 'client';
        _EnvelopeClient_idFactory.set(this, void 0);
        _EnvelopeClient_dispatcher.set(this, void 0);
        _EnvelopeClient_callbacksByStreamID.set(this, {});
        _EnvelopeClient_logger.set(this, void 0);
        this.serverHostname = serverHostname;
        this.id = idFactory();
        __classPrivateFieldSet(this, _EnvelopeClient_idFactory, idFactory, "f");
        __classPrivateFieldSet(this, _EnvelopeClient_logger, logger, "f");
        for (const definition of knows) {
            for (const method of Object.values(definition.methods)) {
                if (method.requestStream && method.responseStream) {
                    this[method.name] = __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_createPipeHandler).call(this, definition, method).bind(this);
                }
                else if (method.requestStream && !method.responseStream) {
                    this[method.name] = __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_createSinkHandler).call(this, definition, method).bind(this);
                }
                else if (!method.requestStream && method.responseStream) {
                    this[method.name] = __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_createSourceHandler).call(this, definition, method).bind(this);
                }
                else if (!method.requestStream && !method.responseStream) {
                    this[method.name] = __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_createAsyncMethod).call(this, definition, method).bind(this);
                }
                else {
                    throw new Error(`Unsupported "${definition.name}" client method request / response definition.`);
                }
            }
        }
    }
    async onBind(dispatcher) {
        if (__classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f") != null) {
            throw Error(`Dispatcher: "${__classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f").hostname}" already bound.`);
        }
        __classPrivateFieldSet(this, _EnvelopeClient_dispatcher, dispatcher, "f");
    }
    async onUnbind(_dispatcher) {
        __classPrivateFieldSet(this, _EnvelopeClient_dispatcher, undefined, "f");
    }
    // Called by dispatcher only, to send responses to the client.
    post(_caller, envelope) {
        if (__classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[envelope.streamId] == null)
            throw Error(`Missing callbacks for stream ID "${envelope.streamId}" for envelope to ${envelope.dst} from ${envelope.src}.`);
        if (envelope.request)
            throw Error('Clients cannot be posted request Envelopes.');
        __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[envelope.streamId](envelope);
        if (envelope.complete) {
            delete __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[envelope.streamId];
        }
    }
    async dispose() {
        await __classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f")?.release(this);
    }
}
_EnvelopeClient_idFactory = new WeakMap(), _EnvelopeClient_dispatcher = new WeakMap(), _EnvelopeClient_callbacksByStreamID = new WeakMap(), _EnvelopeClient_logger = new WeakMap(), _EnvelopeClient_instances = new WeakSet(), _EnvelopeClient_createAsyncMethod = function _EnvelopeClient_createAsyncMethod(definition, method) {
    return (message, metadata) => {
        const streamID = __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        const traceID = extractMetadataTraceID(metadata) || __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        return new Promise((resolve, reject) => {
            __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[streamID] = (envelope) => {
                if (envelope.success) {
                    const msg = responseMessageFromBinary(envelope, method);
                    resolve(msg);
                }
                else {
                    reject(envelopeAsError(envelope));
                }
            };
            __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_postDispatcher).call(this, {
                complete: true,
                message: requestMessageToBinary(message, method),
                metadata,
                method: envelopeMethod(definition, method.name),
                request: true,
                streamId: streamID,
                success: true,
                traceId: traceID,
            });
        });
    };
}, _EnvelopeClient_createSourceHandler = function _EnvelopeClient_createSourceHandler(definition, method) {
    return (message, metadata) => {
        const streamID = __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        const traceID = extractMetadataTraceID(metadata) || __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        // This nesty structure is needed as far as I can tell to initialize all of the mutual references.
        return new Observable((subscriber) => {
            // TODO: Because the request is kicked off whenever a subscriber is registered,
            //  this doesn't currently play nicely with multiple subscribers. Allowing it
            //  would clobber the existing entry in `callbacksByStreamID`. Using a RepeatSubject
            //  instead of an Observable would be nice here, but it breaks error-handling in
            //  a number of tests in non-obvious ways.
            if (__classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[streamID]) {
                throw new Error('Multiple subscribers to a source is not supported');
            }
            __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[streamID] = (envelope) => {
                envelope.message = responseMessageFromBinary(envelope, method);
                sendEnvelopeMessageToStream(envelope, subscriber);
            };
            __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_postDispatcher).call(this, {
                complete: true,
                message: requestMessageToBinary(message, method),
                metadata,
                method: envelopeMethod(definition, method.name),
                request: true,
                streamId: streamID,
                success: true,
                traceId: traceID,
            });
        });
    };
}, _EnvelopeClient_createSinkHandler = function _EnvelopeClient_createSinkHandler(definition, method) {
    return (messages, metadata) => {
        const streamID = __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        const traceID = extractMetadataTraceID(metadata) || __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        // Set up the callback for the response promise.  We do this first to avoid a race.
        const response = new Promise((resolve, reject) => {
            __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[streamID] = (envelope) => {
                if (envelope.success) {
                    resolve(responseMessageFromBinary(envelope, method));
                }
                else {
                    reject(envelopeAsError(envelope));
                }
            };
        });
        __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_emitRequestStreamAsEnvelopes).call(this, definition, method, streamID, traceID, messages, metadata);
        // Return the response promise.
        return response;
    };
}, _EnvelopeClient_createPipeHandler = function _EnvelopeClient_createPipeHandler(definition, method) {
    return (messages, metadata) => {
        const streamID = __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        const traceID = extractMetadataTraceID(metadata) || __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this);
        // Set up the response stream.  We do this before the request envelope so that there's not a race.
        return new Observable((subscriber) => {
            // Set up response stream handler.
            __classPrivateFieldGet(this, _EnvelopeClient_callbacksByStreamID, "f")[streamID] = (envelope) => {
                envelope.message = responseMessageFromBinary(envelope, method);
                sendEnvelopeMessageToStream(envelope, subscriber);
            };
            __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_emitRequestStreamAsEnvelopes).call(this, definition, method, streamID, traceID, messages, metadata);
        });
    };
}, _EnvelopeClient_emitRequestStreamAsEnvelopes = function _EnvelopeClient_emitRequestStreamAsEnvelopes(definition, method, streamID, traceID, reqStream, metadata) {
    // These are constants for the duration of the stream.
    const envelope = {
        metadata,
        method: envelopeMethod(definition, method.name),
        request: true,
        streamId: streamID,
        traceId: traceID,
    };
    reqStream.subscribe({
        next: (message) => __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_postDispatcher).call(this, {
            ...envelope,
            complete: false,
            message: requestMessageToBinary(message, method),
            success: true,
        }),
        error: (err) => __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_postDispatcher).call(this, {
            ...envelope,
            complete: true,
            message: err.message,
            success: false,
        }),
        complete: () => __classPrivateFieldGet(this, _EnvelopeClient_instances, "m", _EnvelopeClient_postDispatcher).call(this, { ...envelope, complete: true, message: undefined, success: true }),
    });
}, _EnvelopeClient_postDispatcher = function _EnvelopeClient_postDispatcher(partialEnvelope) {
    const envelope = Envelope.fromPartial({
        ...partialEnvelope,
        metadata: partialEnvelope.metadata ?? {},
        dst: this.serverHostname,
        id: __classPrivateFieldGet(this, _EnvelopeClient_idFactory, "f").call(this),
        src: this.hostname ?? '',
    });
    if (__classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f") == null)
        throw Error('No dispatcher bound to client:' + this.hostname);
    __classPrivateFieldGet(this, _EnvelopeClient_dispatcher, "f").post(this, envelope);
};
