Source: lib/message_serialization.js

// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

/**
 * Check if a value is a TypedArray
 * @param {*} value - The value to check
 * @returns {boolean} True if the value is a TypedArray
 */
function isTypedArray(value) {
  return ArrayBuffer.isView(value) && !(value instanceof DataView);
}

/**
 * Check if a value needs JSON conversion (BigInt, functions, etc.)
 * @param {*} value - The value to check
 * @returns {boolean} True if the value needs special JSON handling
 */
function needsJSONConversion(value) {
  return (
    typeof value === 'bigint' ||
    typeof value === 'function' ||
    typeof value === 'undefined' ||
    value === Infinity ||
    value === -Infinity ||
    (typeof value === 'number' && isNaN(value))
  );
}

/**
 * Convert a message to plain arrays (TypedArray -> regular Array)
 * @param {*} obj - The object to convert
 * @returns {*} The converted object with plain arrays
 */
function toPlainArrays(obj) {
  if (obj === null || obj === undefined) {
    return obj;
  }

  if (isTypedArray(obj)) {
    return Array.from(obj);
  }

  if (Array.isArray(obj)) {
    return obj.map((item) => toPlainArrays(item));
  }

  if (typeof obj === 'object' && obj !== null) {
    const result = {};
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        result[key] = toPlainArrays(obj[key]);
      }
    }
    return result;
  }

  return obj;
}

/**
 * Convert a message to be fully JSON-safe
 * @param {*} obj - The object to convert
 * @returns {*} The JSON-safe converted object
 */
function toJSONSafe(obj) {
  if (obj === null || obj === undefined) {
    return obj;
  }

  if (isTypedArray(obj)) {
    return Array.from(obj).map((item) => toJSONSafe(item));
  }

  if (needsJSONConversion(obj)) {
    if (typeof obj === 'bigint') {
      // Convert BigInt to string with 'n' suffix to indicate it was a BigInt
      return obj.toString() + 'n';
    }
    if (obj === Infinity) return 'Infinity';
    if (obj === -Infinity) return '-Infinity';
    if (typeof obj === 'number' && isNaN(obj)) return 'NaN';
    if (typeof obj === 'undefined') return null;
    if (typeof obj === 'function') return '[Function]';
  }

  if (Array.isArray(obj)) {
    return obj.map((item) => toJSONSafe(item));
  }

  if (typeof obj === 'object' && obj !== null) {
    const result = {};
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        result[key] = toJSONSafe(obj[key]);
      }
    }
    return result;
  }

  return obj;
}

/**
 * Convert a message to a JSON string
 * @param {*} obj - The object to convert
 * @param {number} [space] - Space parameter for JSON.stringify formatting
 * @returns {string} The JSON string representation
 */
function toJSONString(obj, space) {
  const jsonSafeObj = toJSONSafe(obj);
  return JSON.stringify(jsonSafeObj, null, space);
}

/**
 * Apply serialization mode conversion to a message object
 * @param {*} message - The message object to convert
 * @param {string} serializationMode - The serialization mode ('default', 'plain', 'json')
 * @returns {*} The converted message
 */
function applySerializationMode(message, serializationMode) {
  switch (serializationMode) {
    case 'default':
      // No conversion needed - use native rclnodejs behavior
      return message;

    case 'plain':
      // Convert TypedArrays to regular arrays
      return toPlainArrays(message);

    case 'json':
      // Convert to fully JSON-safe format
      return toJSONSafe(message);

    default:
      throw new TypeError(
        `Invalid serializationMode: ${serializationMode}. Valid modes are: 'default', 'plain', 'json'`
      );
  }
}

/**
 * Validate serialization mode
 * @param {string} mode - The serialization mode to validate
 * @returns {boolean} True if valid
 */
function isValidSerializationMode(mode) {
  return ['default', 'plain', 'json'].includes(mode);
}

module.exports = {
  isTypedArray,
  needsJSONConversion,
  toPlainArrays,
  toJSONSafe,
  toJSONString,
  applySerializationMode,
  isValidSerializationMode,
};