"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Server = void 0;
const logger_service_1 = require("@nestjs/common/services/logger.service");
const load_package_util_1 = require("@nestjs/common/utils/load-package.util");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const constants_1 = require("../constants");
const incoming_request_deserializer_1 = require("../deserializers/incoming-request.deserializer");
const identity_serializer_1 = require("../serializers/identity.serializer");
const utils_1 = require("../utils");
/**
 * @publicApi
 */
class Server {
    constructor() {
        this.messageHandlers = new Map();
        this.logger = new logger_service_1.Logger(Server.name);
        this.onProcessingStartHook = (transportId, context, done) => done();
        this._status$ = new rxjs_1.ReplaySubject(1);
    }
    /**
     * Returns an observable that emits status changes.
     */
    get status() {
        return this._status$.asObservable().pipe((0, operators_1.distinctUntilChanged)());
    }
    /**
     * Sets the transport identifier.
     * @param transportId Unique transport identifier.
     */
    setTransportId(transportId) {
        this.transportId = transportId;
    }
    /**
     * Sets a hook that will be called when processing starts.
     */
    setOnProcessingStartHook(hook) {
        this.onProcessingStartHook = hook;
    }
    /**
     * Sets a hook that will be called when processing ends.
     */
    setOnProcessingEndHook(hook) {
        this.onProcessingEndHook = hook;
    }
    addHandler(pattern, callback, isEventHandler = false, extras = {}) {
        const normalizedPattern = this.normalizePattern(pattern);
        callback.isEventHandler = isEventHandler;
        callback.extras = extras;
        if (this.messageHandlers.has(normalizedPattern) && isEventHandler) {
            const headRef = this.messageHandlers.get(normalizedPattern);
            const getTail = (handler) => handler?.next ? getTail(handler.next) : handler;
            const tailRef = getTail(headRef);
            tailRef.next = callback;
        }
        else {
            this.messageHandlers.set(normalizedPattern, callback);
        }
    }
    getHandlers() {
        return this.messageHandlers;
    }
    getHandlerByPattern(pattern) {
        const route = this.getRouteFromPattern(pattern);
        return this.messageHandlers.has(route)
            ? this.messageHandlers.get(route)
            : null;
    }
    send(stream$, respond) {
        const dataQueue = [];
        let isProcessing = false;
        const scheduleOnNextTick = (data) => {
            if (data.isDisposed && dataQueue.length > 0) {
                dataQueue[dataQueue.length - 1].isDisposed = true;
            }
            else {
                dataQueue.push(data);
            }
            if (!isProcessing) {
                isProcessing = true;
                process.nextTick(async () => {
                    while (dataQueue.length > 0) {
                        const packet = dataQueue.shift();
                        if (packet) {
                            await respond(packet);
                        }
                    }
                    isProcessing = false;
                });
            }
        };
        return stream$
            .pipe((0, operators_1.catchError)((err) => {
            scheduleOnNextTick({ err });
            return rxjs_1.EMPTY;
        }), (0, operators_1.finalize)(() => scheduleOnNextTick({ isDisposed: true })))
            .subscribe((response) => scheduleOnNextTick({ response }));
    }
    async handleEvent(pattern, packet, context) {
        const handler = this.getHandlerByPattern(pattern);
        if (!handler) {
            return this.logger.error((0, constants_1.NO_EVENT_HANDLER) `${pattern}`);
        }
        return this.onProcessingStartHook(this.transportId, context, async () => {
            const resultOrStream = await handler(packet.data, context);
            if ((0, rxjs_1.isObservable)(resultOrStream)) {
                const connectableSource = (0, rxjs_1.connectable)(resultOrStream.pipe((0, operators_1.finalize)(() => this.onProcessingEndHook?.(this.transportId, context))), {
                    connector: () => new rxjs_1.Subject(),
                    resetOnDisconnect: false,
                });
                connectableSource.connect();
            }
            else {
                this.onProcessingEndHook?.(this.transportId, context);
            }
        });
    }
    transformToObservable(resultOrDeferred) {
        if (resultOrDeferred instanceof Promise) {
            return (0, rxjs_1.from)(resultOrDeferred).pipe((0, operators_1.mergeMap)(val => ((0, rxjs_1.isObservable)(val) ? val : (0, rxjs_1.of)(val))));
        }
        if ((0, rxjs_1.isObservable)(resultOrDeferred)) {
            return resultOrDeferred;
        }
        return (0, rxjs_1.of)(resultOrDeferred);
    }
    getOptionsProp(obj, prop, defaultValue = undefined) {
        return obj && prop in obj ? obj[prop] : defaultValue;
    }
    handleError(error) {
        this.logger.error(error);
    }
    loadPackage(name, ctx, loader) {
        return (0, load_package_util_1.loadPackage)(name, ctx, loader);
    }
    initializeSerializer(options) {
        this.serializer =
            (options &&
                options.serializer) ||
                new identity_serializer_1.IdentitySerializer();
    }
    initializeDeserializer(options) {
        this.deserializer =
            (options &&
                options.deserializer) ||
                new incoming_request_deserializer_1.IncomingRequestDeserializer();
    }
    /**
     * Transforms the server Pattern to valid type and returns a route for him.
     *
     * @param  {string} pattern - server pattern
     * @returns string
     */
    getRouteFromPattern(pattern) {
        let validPattern;
        try {
            validPattern = JSON.parse(pattern);
        }
        catch (error) {
            // Uses a fundamental object (`pattern` variable without any conversion)
            validPattern = pattern;
        }
        return this.normalizePattern(validPattern);
    }
    normalizePattern(pattern) {
        return (0, utils_1.transformPatternToRoute)(pattern);
    }
}
exports.Server = Server;
