Source: utils.js

/**
 * @module utils
 * @description Core utility functions for generic mathematical and array operations.
 * This module provides common helpers used across the color library.
 */

// --- Constants ---

/**
 * Standard CIE Illuminant D65 reference white point in XYZ, scaled to Y=100.
 * @type {Readonly<{X: number, Y: number, Z: number}>}
 * @see {@link https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D}
 */
export const D65_WHITE_POINT_XYZ = Object.freeze({ X: 95.047, Y: 100.0, Z: 108.883 });

// --- Angle Conversions ---

/**
 * Converts an angle from degrees to radians.
 * @param {number} degrees - The angle in degrees.
 * @returns {number} The angle in radians.
 * @throws {TypeError} if degrees is not a number.
 * @example
 * const rad = degreesToRadians(180); // Math.PI
 */
export function degreesToRadians(degrees) {
  if (typeof degrees !== 'number') {
    throw new TypeError('Input degrees must be a number.');
  }
  return degrees * (Math.PI / 180);
}

/**
 * Converts an angle from radians to degrees.
 * @param {number} radians - The angle in radians.
 * @returns {number} The angle in degrees.
 * @throws {TypeError} if radians is not a number.
 * @example
 * const deg = radiansToDegrees(Math.PI); // 180
 */
export function radiansToDegrees(radians) {
  if (typeof radians !== 'number') {
    throw new TypeError('Input radians must be a number.');
  }
  return radians * (180 / Math.PI);
}

// --- Matrix/Vector Operations ---

/**
 * Multiplies a 3x3 matrix by a 3x1 column vector.
 * Optimized inline implementation from abridged version.
 * @param {ReadonlyArray<ReadonlyArray<number>>} matrix - The 3x3 matrix.
 * @param {ReadonlyArray<number>} vector - The 3-element vector.
 * @returns {number[]} The resulting 3-element vector.
 * @throws {TypeError} if inputs are not valid matrix/vector.
 */
export function multiplyMatrixVector(matrix, vector) {
  if (!Array.isArray(matrix) || matrix.length !== 3 ||
      !matrix.every(row => Array.isArray(row) && row.length === 3 && row.every(el => typeof el === 'number'))) {
    throw new TypeError('Matrix must be a 3x3 array of numbers.');
  }
  if (!Array.isArray(vector) || vector.length !== 3 || !vector.every(el => typeof el === 'number')) {
    throw new TypeError('Vector must be a 3-element array of numbers.');
  }

  // Optimized inline calculation from abridged version
  return [
    matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
    matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
    matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2],
  ];
}

// --- General Math Helpers ---

/**
 * Clamps a value between a minimum and maximum.
 * @param {number} value - The value to clamp.
 * @param {number} min - The minimum value.
 * @param {number} max - The maximum value.
 * @returns {number} The clamped value.
 */
export function clamp(value, min, max) {
  return Math.max(min, Math.min(value, max));
}

/**
 * Performs linear interpolation between two values.
 * @param {number} a - The start value.
 * @param {number} b - The end value.
 * @param {number} t - The interpolation factor (usually between 0 and 1).
 * @returns {number} The interpolated value.
 */
export function lerp(a, b, t) {
  return a + t * (b - a);
}

/**
 * Normalizes a hue angle to the range [0, 360).
 * @param {number} h - The hue angle in degrees.
 * @returns {number} The normalized hue angle.
 */
export function normalizeHue(h) {
  return ((h % 360) + 360) % 360;
}

/**
 * Applies an exponent to the absolute value of a number, then reapplies the original sign.
 * result = sign(value) * (abs(value) ^ exponent)
 * @param {number} value - The base value.
 * @param {number} exponent - The exponent.
 * @returns {number} The result of the sign-preserving power operation.
 * @throws {TypeError} if inputs are not numbers.
 * @example
 * signPreservingPow(-8, 1/3); // -2 (cube root of -8)
 * signPreservingPow(9, 0.5);   // 3 (square root of 9)
 */
export function signPreservingPow(value, exponent) {
  if (typeof value !== 'number' || typeof exponent !== 'number') {
    throw new TypeError('Both value and exponent must be numbers.');
  }
  
  if (value === 0) return 0;
  
  const sign = value < 0 ? -1 : 1;
  const absValue = Math.abs(value);
  const result = Math.pow(absValue, exponent);
  
  return sign * result;
}