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 __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 _EnvelopeServer_instances, _EnvelopeServer_dispatcher, _EnvelopeServer_requestStreamsById, _EnvelopeServer_responseStreamsById, _EnvelopeServer_actor, _EnvelopeServer_id, _EnvelopeServer_idFactory, _EnvelopeServer_methodsByName, _EnvelopeServer_logger, _EnvelopeServer_handleAsync, _EnvelopeServer_handleSource, _EnvelopeServer_handleSink, _EnvelopeServer_handlePipe, _EnvelopeServer_emitResponseStreamAsEnvelopes, _EnvelopeServer_postDispatcher;
import { Envelope } from '@devvit/protos';
import { isDescendantHostname } from '@devvit/shared-types/HostnameUtil.js';
import { ReplaySubject, catchError, isObservable } from 'rxjs';
import { v4 as UUIDv4 } from 'uuid';
import { Log } from '../../lib/loggers/Log.js';
import { VoidSystemLogger } from '../../lib/loggers/SystemLogger.js';
import { envelopeMethod, requestMessageFromBinary, responseMessageToBinary, sendEnvelopeMessageToStream, } from './EnvelopeUtil.js';
/**
 * An EnvelopeServer is a wrapper around an Actor that allows it to receive Envelopes sent
 * via a Dispatcher.
 */
export class EnvelopeServerBuilder {
    static build(name, provides, actor, logger = new VoidSystemLogger(), idFactory = UUIDv4) {
        return new EnvelopeServer(name, provides, actor, idFactory, logger);
    }
}
class EnvelopeServer {
    constructor(hostname, provides, 
    // TODO: remove use of any below
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    actor, idFactory, logger) {
        _EnvelopeServer_instances.add(this);
        _EnvelopeServer_dispatcher.set(this, void 0);
        _EnvelopeServer_requestStreamsById.set(this, {});
        _EnvelopeServer_responseStreamsById.set(this, {});
        _EnvelopeServer_actor.set(this, void 0);
        _EnvelopeServer_id.set(this, void 0);
        _EnvelopeServer_idFactory.set(this, void 0);
        _EnvelopeServer_methodsByName.set(this, Object.create(null));
        _EnvelopeServer_logger.set(this, void 0);
        if (actor == null)
            throw Error('EnvelopeServer requires a nonnullish actor to forward requests to and await responses from.');
        this.hostname = hostname;
        __classPrivateFieldSet(this, _EnvelopeServer_actor, actor, "f");
        __classPrivateFieldSet(this, _EnvelopeServer_id, idFactory(), "f");
        __classPrivateFieldSet(this, _EnvelopeServer_idFactory, idFactory, "f");
        __classPrivateFieldSet(this, _EnvelopeServer_logger, logger, "f");
        for (const definition of provides) {
            for (const method of Object.values(definition.methods)) {
                __classPrivateFieldGet(this, _EnvelopeServer_methodsByName, "f")[envelopeMethod(definition, method.name)] = method;
            }
        }
    }
    onBind(dispatcher) {
        if (!dispatcher.hostname || !isDescendantHostname(this.hostname, dispatcher.hostname)) {
            throw new Error(`Server's hostname (${this.hostname}) must match or be under *.[dispatcher-hostname] (${dispatcher.hostname}).`);
        }
        __classPrivateFieldSet(this, _EnvelopeServer_dispatcher, dispatcher, "f");
    }
    onUnbind(_dispatcher) {
        __classPrivateFieldSet(this, _EnvelopeServer_dispatcher, undefined, "f");
    }
    // Should only be called by the dispatcher
    post(_caller, envelope) {
        const definition = __classPrivateFieldGet(this, _EnvelopeServer_methodsByName, "f")[envelope.method];
        if (!definition) {
            throw new Error(`Service definition does not provide method "${envelope.method}".  Have ${Object.keys(__classPrivateFieldGet(this, _EnvelopeServer_methodsByName, "f")).join(', ')}`);
        }
        const method = __classPrivateFieldGet(this, _EnvelopeServer_actor, "f")[definition.name]?.bind(__classPrivateFieldGet(this, _EnvelopeServer_actor, "f"));
        if (method == null)
            throw Error(`Service does not implement "${definition.name}" method.`);
        // Promised message responses are only awaited in the handle*() call, not
        // here. An actor is expected to be single-threaded and to block this shared
        // thread servicing any outstanding asynchronous request before servicing
        // any subsequent request.
        if (definition.requestStream && definition.responseStream) {
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_handlePipe).call(this, method, envelope, definition);
        }
        else if (definition.requestStream && !definition.responseStream) {
            // No awaiting the response. #handleSink handles rejections.
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_handleSink).call(this, method, envelope, definition);
        }
        else if (!definition.requestStream && definition.responseStream) {
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_handleSource).call(this, method, envelope, definition);
        }
        else if (!definition.requestStream && !definition.responseStream) {
            // No awaiting the response. #handleAsync handles rejections.
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_handleAsync).call(this, method, envelope, definition);
        }
        else {
            throw new Error(`Unsupported "${definition.name}" service method request / response definition.`);
        }
    }
    // todo: should this call this.#actor.dispose?.();
    async dispose() {
        for (const streamId of Object.keys(__classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f"))) {
            __classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f")[streamId].unsubscribe();
        }
        for (const streamId of Object.keys(__classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f"))) {
            __classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f")[streamId].error(new Error('EnvelopeServer is going away'));
        }
    }
}
_EnvelopeServer_dispatcher = new WeakMap(), _EnvelopeServer_requestStreamsById = new WeakMap(), _EnvelopeServer_responseStreamsById = new WeakMap(), _EnvelopeServer_actor = new WeakMap(), _EnvelopeServer_id = new WeakMap(), _EnvelopeServer_idFactory = new WeakMap(), _EnvelopeServer_methodsByName = new WeakMap(), _EnvelopeServer_logger = new WeakMap(), _EnvelopeServer_instances = new WeakSet(), _EnvelopeServer_handleAsync = async function _EnvelopeServer_handleAsync(method, req, definition) {
    // These are constants for the response.
    const envelope = {
        complete: true,
        dst: req.src,
        metadata: req.metadata,
        method: req.method,
        request: false,
        src: req.dst,
        streamId: req.streamId,
        traceId: req.traceId,
    };
    const msg = requestMessageFromBinary(req, definition);
    const promisedMessage = method(msg, req.metadata);
    // to-do: we can probably safely allow synchronous services. This was an old
    // sanity test assertion that probably doesn't make sense to enforce.
    assertPromise(promisedMessage);
    try {
        const message = await promisedMessage;
        __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
            ...envelope,
            message: responseMessageToBinary(message, definition),
            success: true,
        });
    }
    catch (err) {
        let errMsg = err;
        if (err instanceof Error) {
            errMsg = err.message;
        }
        await __classPrivateFieldGet(this, _EnvelopeServer_logger, "f").messageError(req, err);
        __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, { ...envelope, message: errMsg });
    }
}, _EnvelopeServer_handleSource = function _EnvelopeServer_handleSource(method, req, definition) {
    // todo: add for other places toooooooo
    if (!req.request || !req.success) {
        // todo: add quick test here for 404 and 500 type envelopes and report in the warning.
        Log.warn(`Received response (${!req.request}) or erroneous envelope (${!req.success}). Closing stream ${req.streamId}.`);
        __classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f")[req.streamId]?.unsubscribe();
        delete __classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f")[req.streamId];
        return;
    }
    const responseStream = method(requestMessageFromBinary(req, definition), req.metadata);
    assertObservable(responseStream);
    __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_emitResponseStreamAsEnvelopes).call(this, responseStream, req, definition);
}, _EnvelopeServer_handleSink = 
/** Accepts many request messages, returns a single response. i.e `tail foo > sink`. */
async function _EnvelopeServer_handleSink(method, req, definition) {
    // Is the stream already opened?
    let requestStream = __classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f")[req.streamId];
    if (requestStream) {
        req.message = requestMessageFromBinary(req, definition);
        sendEnvelopeMessageToStream(req, requestStream);
    }
    else {
        // make a stream.  By default, streams don't have memory, so ReplaySubject(1) is asking the stream
        // to buffer the first envelope.  Otherwise the first envelope gets dropped.
        __classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f")[req.streamId] = requestStream = new ReplaySubject(1);
        req.message = requestMessageFromBinary(req, definition);
        sendEnvelopeMessageToStream(req, requestStream);
        // These are constants for the response.
        const envelope = {
            complete: true,
            dst: req.src,
            metadata: req.metadata,
            method: req.method,
            request: false,
            src: req.dst,
            streamId: req.streamId,
            traceId: req.traceId,
        };
        // Open / start stream.
        const promisedMessage = method(requestStream, req.metadata);
        assertPromise(promisedMessage);
        try {
            const message = await promisedMessage;
            // Block until stream is closed by actor.
            // this is the "exit status". all requests have been processed. the stream is now closed. this is the last action on the stream.
            // This post never throws. Send the close envelope with the message from the actor.
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
                ...envelope,
                message: responseMessageToBinary(message, definition),
                success: true,
            });
        }
        catch (err) {
            let errMsg = err;
            if (err instanceof Error) {
                errMsg = err.message;
            }
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
                ...envelope,
                message: errMsg,
                success: false,
            });
        }
    }
}, _EnvelopeServer_handlePipe = function _EnvelopeServer_handlePipe(method, req, definition) {
    let requestStream = __classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f")[req.streamId];
    if (requestStream) {
        // keep passing messages into the stream just like sink.
        req.message = requestMessageFromBinary(req, definition);
        sendEnvelopeMessageToStream(req, requestStream);
    }
    else {
        // make a stream.  By default, streams don't have memory, so ReplaySubject(1) is asking the stream
        // to buffer the first envelope.  Otherwise the first envelope gets dropped.
        __classPrivateFieldGet(this, _EnvelopeServer_requestStreamsById, "f")[req.streamId] = requestStream = new ReplaySubject(1);
        req.message = requestMessageFromBinary(req, definition);
        sendEnvelopeMessageToStream(req, requestStream);
        const responseStream = method(requestStream, req.metadata);
        assertObservable(responseStream);
        __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_emitResponseStreamAsEnvelopes).call(this, responseStream, req, definition);
    }
}, _EnvelopeServer_emitResponseStreamAsEnvelopes = function _EnvelopeServer_emitResponseStreamAsEnvelopes(stream, req, definition) {
    // These are constants for the duration of the stream.
    const envelope = {
        dst: req.src,
        metadata: req.metadata,
        method: req.method,
        request: false,
        src: req.dst,
        streamId: req.streamId,
        traceId: req.traceId,
    };
    if (__classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f")[req.streamId]) {
        console.warn('Already had a stream id for', req.streamId);
    }
    __classPrivateFieldGet(this, _EnvelopeServer_responseStreamsById, "f")[req.streamId] = stream
        .pipe(
    // Filter errors with something supporting async, then rethrow.
    catchError(async (err) => {
        await __classPrivateFieldGet(this, _EnvelopeServer_logger, "f").messageError(req, err);
        throw err;
    }))
        .subscribe({
        next: (message) => {
            __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
                ...envelope,
                complete: false,
                message: responseMessageToBinary(message, definition),
                success: true,
            });
        },
        error: (err) => __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
            ...envelope,
            complete: true,
            message: err.message,
            success: false,
        }),
        complete: () => __classPrivateFieldGet(this, _EnvelopeServer_instances, "m", _EnvelopeServer_postDispatcher).call(this, {
            ...envelope,
            complete: true,
            message: undefined,
            success: true,
        }),
    });
}, _EnvelopeServer_postDispatcher = function _EnvelopeServer_postDispatcher(partialEnvelope) {
    if (!__classPrivateFieldGet(this, _EnvelopeServer_dispatcher, "f"))
        throw Error('No dispatcher bound to server: ' + this.hostname + ' - ' + __classPrivateFieldGet(this, _EnvelopeServer_id, "f"));
    const envelope = Envelope.fromPartial({ ...partialEnvelope, id: __classPrivateFieldGet(this, _EnvelopeServer_idFactory, "f").call(this) });
    __classPrivateFieldGet(this, _EnvelopeServer_dispatcher, "f").post(this, envelope);
};
function assertPromise(val) {
    const keys = ['catch', 'then'];
    if (val == null || typeof val !== 'object' || keys.some((key) => !(key in val)))
        throw Error('Expected Promise.');
}
function assertObservable(val) {
    if (!isObservable(val))
        throw Error('Expected Observable.');
}
