/**
 * @import {VFXBasePathPoint, VFXComponentAnimation} from "./_types.mjs";
 */

const PI2 = Math.PI * 2;

/**
 * Interpolate rotation in radians between two angles.
 * @param {number} r1
 * @param {number} r2
 * @param {number} i
 * @returns {number}
 */
export function interpolateRotation(r1, r2, i) {
  const diff = Math.abs(r2 - r1);
  if (diff > Math.PI) {
    if (r2 > r1) r1 += PI2;
    else r2 += PI2;
  }
  const value = Math.mix(r1, r2, i);
  return ((value % PI2) + PI2) % PI2;
}

/* -------------------------------------------- */

/**
 * Parse rotation options normalized to radians from shared config objects
 * @param {object} data
 * @param {number} [data.angle]         Initial rotation in degrees
 * @param {number} [data.rotation]      Initial rotation in radians
 * @param {Point} [data.rotateTowards]  Incremental rotation towards a destination position
 * @param {Point} [data.origin]         Rotate from an origin position, needed if using rotateTowards
 * @returns {number}
 */
export function parseRotation(data) {
  let rotation = 0;
  if ( typeof data.rotation === "number" ) rotation = data.rotation;
  else if ( typeof data.angle === "number" ) rotation = Math.toRadians(data.angle);
  if ( data.rotateTowards ) {
    const {rotateTowards, origin} = data;
    if (rotateTowards && origin) {
      const dx = rotateTowards.x - origin.x;
      const dy = rotateTowards.y - origin.y;
      rotation += Math.atan2(dy, dx);
    }
  }
  return rotation;
}

/* -------------------------------------------- */

/**
 * Resolves a component animation by name.
 * @param {string} functionName     Name of the animation object in `foundry.vfx.CONFIG.animations`
 * @returns {VFXComponentAnimation} The resolved animations object
 */
export function resolveAnimation(functionName) {
  const animation = CONFIG.Canvas.vfx.animations[functionName];
  if ( typeof animation?.animate !== "function" ) {
    throw new Error(`VFX animation "${functionName}" is not defined in CONFIG.Canvas.vfx.animations"`);
  }
  return animation;
}

/* -------------------------------------------- */

/**
 * Resolves an anime.js easing function by name and initializes it with parameters.
 * @param {string} functionName         Name of the easing function
 * @param {any[]} easingParams          Optional positional parameters to initialize the easing function
 * @returns {(time: number) => number}  The resolved easing function
 */
export function resolveEasing(functionName, easingParams) {
  const easingFunction = animejs.eases[functionName];
  if ( typeof easingFunction !== "function") {
    throw new Error(`animejs easing function "${functionName}" not found`);
  }
  const isSimple = typeof easingFunction(1) === "number";
  if ( isSimple ) return easingFunction;
  if ( !easingParams ) throw new Error(`animejs easing function "${functionName}" requires provided parameters`);
  easingParams = easingParams ?? [];
  easingParams = Array.isArray(easingParams) ? easingParams : [easingParams];
  return easingFunction(...easingParams);
}

/* -------------------------------------------- */

/**
 * Calculates cubic Hermite basis functions for a given parameter t.
 * Returns an interpolation function that can be reused for multiple properties.
 * @param {number} t  Interpolation parameter
 * @returns {(p0: number, m0: number, p1: number, m1: number) => number} Interpolation function
 */
function hermiteBasis(t) {
  const t2 = t * t;
  const t3 = t2 * t;
  const h00 = (2 * t3) - (3 * t2) + 1;
  const h10 = t3 - (2 * t2) + t;
  const h01 = (-2 * t3) + (3 * t2);
  const h11 = t3 - t2;
  return (p0, m0, p1, m1) => (h00 * p0) + (h10 * m0) + (h01 * p1) + (h11 * m1);
}

/* -------------------------------------------- */

/**
 * Generates points along a cubic Hermite spline segment.
 * @param {VFXBasePathPoint} p0     Start control point
 * @param {Point} m0                Tangent vector at start point
 * @param {VFXBasePathPoint} p1     End control point
 * @param {Point} m1                Tangent vector at end point
 * @param {number} numPoints        Number of points to generate along the segment
 * @param {string[]} [auxiliary]    An array of auxiliary numeric attributes to interpolate
 * @returns {VFXBasePathPoint[]}    Array of points which may be augmented with additional auxiliary attributes
 */
export function generateHermiteSegment(p0, m0, p1, m1, numPoints, auxiliary) {
  if ( numPoints < 1 ) return [p0];
  const points = [];
  for (let i=0; i<=numPoints; i++) {
    const t = i / numPoints;
    const interpolate = hermiteBasis(t);
    const x = interpolate(p0.x, m0.x, p1.x, m1.x);
    const y = interpolate(p0.y, m0.y, p1.y, m1.y);
    const point = {x, y, ...interpolateProperties(p0, p1, t, auxiliary)};
    points.push(point);
  }
  return points;
}

/* -------------------------------------------- */

/**
 * Interpolate auxiliary attributes between two points.
 * @param {VFXBasePathPoint} p0     Start control point
 * @param {VFXBasePathPoint} p1     End control point
 * @param {number} t                Interpolation parameter
 * @param {string[]} [auxiliary]    An array of auxiliary numeric attributes to interpolate. These attributes must be
 *                                  present all points. Attributes are either interpolated or carried forward from
 *                                  their last-observed value.
 * @returns {object}                Interpolated auxiliary properties
 */
export function interpolateProperties(p0, p1, t, auxiliary=[]) {
  const aux = {};
  for ( const key of auxiliary ) { // Interpolate auxiliary attributes
    const a0 = p0[key];
    const has0 = typeof a0 === "number";
    if ( has0 ) {
      const a1 = p1[key];
      const has1 = typeof a1 === "number";
      if ( has1 ) aux[key] = Math.mix(a0, a1, t);
      else aux[key] = a0;
    }
  }
  return aux;
}


/* -------------------------------------------- */
