"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.withTimeDifference = exports.takeUntilCompleteOrError = exports.mapMonitoringParameterValuesStatusMessageToDeviceParameters = exports.mapMonitoringParameterValuesStatusMessageToParameterValues = exports.selectMotionMasterMessageStatusByKey = exports.selectMotionMasterMessageByTopic = exports.transformMotionMasterMessageToStatus = exports.extendStatus = exports.selectMotionMasterMessageStatusByMessageId = void 0;
const rxjs_1 = require("rxjs");
const request_status_resolver_1 = require("./request-status-resolver");
const parameter_1 = require("./parameter");
/**
 * Creates an operator that filters Motion Master messages by message ID and extracts
 * a specific status field from the message payload.
 *
 * If a message with the matching ID contains a `systemEvent` status instead of the expected status,
 * an error is thrown. This typically happens when a request refers to an invalid device address.
 *
 * @typeParam T - The type corresponding to the selected status field.
 * @param key - The status field name to extract from the message.
 * @param id - The message ID that identifies the corresponding request/response pair.
 *
 * @returns An operator that emits the resolved status when a matching message arrives.
 *
 * @throws Error
 * Thrown when a SystemEvent status is received while a different status type was expected.
 *
 * @example
 * source$.pipe(
 *   selectMotionMasterMessageStatusByMessageId<'deviceInfo'>('deviceInfo', 'req-123')
 * ).subscribe(info => console.log(info));
 */
function selectMotionMasterMessageStatusByMessageId(key, id) {
    return (0, rxjs_1.pipe)((0, rxjs_1.filter)((message) => message.id === id), (0, rxjs_1.map)((message) => {
        var _a, _b, _c;
        // Handling the special case when Motion Master sends SystemEvent instead of e.g. DeviceParameterInfo status for
        // GetDeviceParameterInfo request message. This typically happens when request contains an invalid device address.
        if (((_a = message.status) === null || _a === void 0 ? void 0 : _a.systemEvent) && key !== 'systemEvent') {
            if (message.status.systemEvent.error) {
                throw new Error(`Error selecting message: ${(_b = message.status.systemEvent.error.message) !== null && _b !== void 0 ? _b : ''}`);
            }
            else {
                throw new Error(`Error selecting message. Received System Event instead of "${key.toString()}": ${JSON.stringify(message)}`);
            }
        }
        return (_c = message.status) === null || _c === void 0 ? void 0 : _c[key];
    }));
}
exports.selectMotionMasterMessageStatusByMessageId = selectMotionMasterMessageStatusByMessageId;
/**
 * Enriches a status object by appending request metadata, including its originating message ID
 * and a resolved request state derived from the status contents.
 *
 * This serves as a post-processing step after a status has been extracted from a message.
 *
 * @typeParam T - The status shape being extended.
 * @param data - Context containing the originating message ID and the status key.
 *
 * @returns An operator that transforms each emitted status into an augmented status object.
 *
 * @example
 * source$.pipe(
 *   extendStatus({ statusKey: 'deviceInfo', messageId: 'req-123' })
 * ).subscribe(status => {
 *   console.log(status.messageId);
 *   console.log(status.request); // e.g. "pending", "succeeded", "failed"
 * });
 */
function extendStatus(data) {
    return (0, rxjs_1.pipe)((0, rxjs_1.map)((status) => {
        var _a;
        const { statusKey, messageId } = data;
        const request = (_a = request_status_resolver_1.requestStatusResolver[statusKey]) === null || _a === void 0 ? void 0 : _a.call(request_status_resolver_1.requestStatusResolver, status);
        return Object.assign(Object.assign({}, status), { messageId, request });
    }));
}
exports.extendStatus = extendStatus;
/**
 * Produces a complete request-response processing pipeline for Motion Master messages.
 * This operator:
 * 1. Extracts a specific status type from messages matching the given message ID.
 * 2. Applies a timeout to fail if a response is not received in time.
 * 3. Extends the emitted status with request metadata.
 * 4. Continues emitting until the resolved request state reaches `"succeeded"` or `"failed"`.
 *
 * @typeParam T - Status type being tracked.
 * @param statusKey - The status field name expected in the response.
 * @param requestTimeout - Maximum allowed duration (ms) to wait for the response.
 * @param messageId - Message ID identifying the request/response sequence.
 *
 * @returns Operator that emits request lifecycle updates until completion or failure.
 *
 * @throws Error
 * If requestTimeout is not a number.
 *
 * @example
 * source$.pipe(
 *   transformMotionMasterMessageToStatus('deviceInfo', 5000, 'req-123')
 * ).subscribe(status => {
 *   console.log(status.request);
 * });
 */
function transformMotionMasterMessageToStatus(statusKey, requestTimeout, messageId) {
    if (typeof requestTimeout !== 'number') {
        throw new Error(`Invalid requestTimeout=${requestTimeout} provided for ${statusKey}`);
    }
    return (0, rxjs_1.pipe)(selectMotionMasterMessageStatusByMessageId(statusKey, messageId), (0, rxjs_1.timeout)(requestTimeout), extendStatus({ statusKey, messageId }), (0, rxjs_1.takeWhile)((status) => status.request !== 'succeeded' && status.request !== 'failed', true));
}
exports.transformMotionMasterMessageToStatus = transformMotionMasterMessageToStatus;
/**
 * Creates an operator that selects only messages whose topic matches the specified topic
 * and emits the associated data payload.
 *
 * This is commonly used when incoming data arrives as `[topic, payload]` tuples.
 *
 * @typeParam T - The payload type associated with the topic.
 * @param topic - Topic identifier to match.
 *
 * @returns Operator that emits only payloads belonging to the given topic.
 *
 * @example
 * source$.pipe(
 *   selectMotionMasterMessageByTopic<DeviceStatus>('device/status')
 * ).subscribe(status => console.log(status));
 */
function selectMotionMasterMessageByTopic(topic) {
    return (0, rxjs_1.pipe)((0, rxjs_1.filter)(([t]) => t === topic), (0, rxjs_1.map)(([, data]) => data));
}
exports.selectMotionMasterMessageByTopic = selectMotionMasterMessageByTopic;
/**
 * Filters Motion Master messages that contain a specific status field and emits the value
 * of that status field.
 *
 * This operator is useful when the source stream contains heterogeneous message types.
 *
 * @typeParam T - The type corresponding to the selected status entry.
 * @param key - Status field key to extract if present.
 *
 * @returns Operator that emits typed status entries for matching messages.
 *
 * @example
 * source$.pipe(
 *   selectMotionMasterMessageStatusByKey<DeviceInfo>('deviceInfo')
 * ).subscribe(info => console.log(info));
 */
function selectMotionMasterMessageStatusByKey(key) {
    return (0, rxjs_1.pipe)((0, rxjs_1.filter)((message) => !!(message.status && message.status[key])), (0, rxjs_1.map)((message) => { var _a; return (_a = message.status) === null || _a === void 0 ? void 0 : _a[key]; }));
}
exports.selectMotionMasterMessageStatusByKey = selectMotionMasterMessageStatusByKey;
/**
 * Creates an operator that extracts only the raw parameter values from monitoring status messages.
 * If monitoring parameter values are present in the incoming message, each value is converted into
 * its resolved primitive representation (e.g., number, boolean, string) via `getParameterValue`.
 *
 * When a message contains no monitoring payload or parameter value list, an empty array is emitted.
 *
 * @returns An operator that maps an `Observable<IMotionMasterMessage>` to an array of resolved parameter values.
 *
 * @example
 * source$.pipe(
 *   mapMonitoringParameterValuesStatusMessageToParameterValues()
 * ).subscribe(values => {
 *   console.log('Raw parameter values:', values);
 * });
 *
 * @remarks
 * - Does not perform device-level lookup or metadata resolution.
 * - Produces values in the same order as provided by the device.
 */
function mapMonitoringParameterValuesStatusMessageToParameterValues() {
    return (0, rxjs_1.pipe)((0, rxjs_1.map)((message) => {
        var _a;
        if ((_a = message.status) === null || _a === void 0 ? void 0 : _a.monitoringParameterValues) {
            const { deviceParameterValues } = message.status.monitoringParameterValues;
            if (deviceParameterValues === null || deviceParameterValues === void 0 ? void 0 : deviceParameterValues.parameterValues) {
                return deviceParameterValues.parameterValues.map((parameterValue) => (0, parameter_1.getParameterValue)(parameterValue));
            }
        }
        return [];
    }));
}
exports.mapMonitoringParameterValuesStatusMessageToParameterValues = mapMonitoringParameterValuesStatusMessageToParameterValues;
/**
 * Creates an operator that transforms Motion Master monitoring status messages into a list of
 * resolved `DeviceParameter` objects. For each incoming message, parameter values are extracted,
 * device parameter metadata is resolved using the provided request client, and both pieces of
 * information are merged into a final typed representation.
 *
 * More specifically:
 * - Extracts raw parameter values from a monitoring status message.
 * - Determines the associated device address.
 * - Fetches a parameter info map for that device via `request.resolveDeviceParameterInfoMap(...)`.
 * - Combines resolved metadata (e.g., name, unit, scaling) with current parameter values.
 * - Produces a `DeviceParameter[]` representing the current state.
 *
 * If the incoming message does not include parameter values, an empty array is emitted.
 *
 * @param request - A client used to resolve parameter info metadata per device.
 * @returns Operator that maps a stream of `IMotionMasterMessage` into parameter arrays.
 *
 * @example
 * source$.pipe(
 *   mapMonitoringParameterValuesStatusMessageToDeviceParameters(request)
 * ).subscribe(parameters => {
 *   console.log('Resolved device parameters:', parameters);
 * });
 *
 * @remarks
 * - The operator handles missing or incomplete monitoring payloads gracefully.
 * - The returned parameters contain both identification (index/subindex) and resolved metadata.
 */
function mapMonitoringParameterValuesStatusMessageToDeviceParameters(request) {
    return (0, rxjs_1.pipe)((0, rxjs_1.mergeMap)((message) => {
        var _a, _b, _c, _d, _e, _f;
        const parameterValues = (_c = (_b = (_a = message.status) === null || _a === void 0 ? void 0 : _a.monitoringParameterValues) === null || _b === void 0 ? void 0 : _b.deviceParameterValues) === null || _c === void 0 ? void 0 : _c.parameterValues;
        if (!parameterValues) {
            return (0, rxjs_1.of)([]);
        }
        const deviceAddress = (_f = (_e = (_d = message === null || message === void 0 ? void 0 : message.status) === null || _d === void 0 ? void 0 : _d.monitoringParameterValues) === null || _e === void 0 ? void 0 : _e.deviceParameterValues) === null || _f === void 0 ? void 0 : _f.deviceAddress;
        return request.resolveDeviceParameterInfoMap(deviceAddress).pipe((0, rxjs_1.map)((infoMap) => {
            return parameterValues.map((parameter) => {
                var _a, _b;
                const index = (_a = parameter.index) !== null && _a !== void 0 ? _a : 0x0000;
                const subindex = (_b = parameter.subindex) !== null && _b !== void 0 ? _b : 0x00;
                const id = (0, parameter_1.makeParameterId)(index, subindex);
                const infoParameter = infoMap.get(id);
                const value = (0, parameter_1.getParameterValue)(parameter);
                return Object.assign(Object.assign({ id, index, subindex }, infoParameter), { value });
            });
        }));
    }));
}
exports.mapMonitoringParameterValuesStatusMessageToDeviceParameters = mapMonitoringParameterValuesStatusMessageToDeviceParameters;
/**
 * Returns an operator that completes the source observable when the notifier observable either
 * completes or errors. Unlike `takeUntil`, which reacts only to the notifier’s `next` emissions,
 * this operator also stops the source when the notifier finishes or fails.
 *
 * @typeParam T - The value type of the source observable.
 * @param notifier$ - Observable whose termination (complete or error) will stop the source.
 * @returns A `MonoTypeOperatorFunction` that completes the source when the notifier completes or errors.
 *
 * @example
 * const notifier$ = ajax.getJSON('/config'); // May error or complete
 *
 * source$.pipe(
 *   takeUntilCompleteOrError(notifier$)
 * ).subscribe({
 *   next: console.log,
 *   complete: () => console.log('Stopped due to notifier completing'),
 *   error: console.error
 * });
 *
 * // The subscription to source$ ends as soon as:
 * // 1) notifier$ completes, or
 * // 2) notifier$ errors.
 *
 * // NOTE: Any values emitted by notifier$ are ignored.
 */
function takeUntilCompleteOrError(notifier$) {
    // Transform notifier to emit a value on complete or error
    const stop$ = notifier$.pipe((0, rxjs_1.materialize)(), // Convert next/error/complete to Notification objects
    (0, rxjs_1.filter)((n) => n.kind === 'C' || n.kind === 'E') // Only react to complete or error
    );
    return (0, rxjs_1.takeUntil)(stop$);
}
exports.takeUntilCompleteOrError = takeUntilCompleteOrError;
/**
 * Custom RxJS operator that accumulates notifications with their
 * elapsed time relative to a provided start time.
 *
 * Each emitted notification string is converted into a `NotificationEntry`
 * object, and the operator maintains an array of all previous entries.
 *
 * @param startTime - The timestamp (in milliseconds) to use as the reference
 *   point for calculating elapsed time. Typically obtained via `performance.now()`.
 * @returns An OperatorFunction that transforms an Observable of strings into
 *   an Observable of `NotificationEntry[]`.
 *
 * @example
 * ```ts
 * const startTime = performance.now();
 * notifications$.pipe(withTimeDifference(startTime)).subscribe(entries => {
 *   console.log(entries); // Array of NotificationEntry objects with elapsed time
 * });
 * ```
 */
function withTimeDifference(startTime) {
    return (source) => source.pipe((0, rxjs_1.scan)((acc, message) => {
        const endTime = performance.now();
        const timeDifference = endTime - startTime;
        acc.push({ message, timeDifference });
        return acc;
    }, []));
}
exports.withTimeDifference = withTimeDifference;
//# sourceMappingURL=operators.js.map