/**
 * interpolate() — Drop-in replacement for Remotion's interpolate.
 *
 * Maps a value from an input range to an output range with support for:
 * - Multi-point ranges (N input values → N output values)
 * - Per-segment easing functions
 * - Extrapolation modes: 'clamp', 'extend', 'identity'
 *
 * API is 1:1 compatible with `import { interpolate } from 'remotion'`.
 */

export type ExtrapolateType = 'clamp' | 'extend' | 'identity';

export interface InterpolateOptions {
  easing?: ((t: number) => number) | ((t: number) => number)[];
  extrapolateLeft?: ExtrapolateType;
  extrapolateRight?: ExtrapolateType;
}

/**
 * Interpolate a value from inputRange to outputRange.
 *
 * @param value - The input value to interpolate
 * @param inputRange - Array of input values (must be monotonically increasing)
 * @param outputRange - Array of output values (same length as inputRange)
 * @param options - Easing and extrapolation options
 */
export function interpolate(
  value: number,
  inputRange: readonly number[],
  outputRange: readonly number[],
  options?: InterpolateOptions
): number {
  if (inputRange.length !== outputRange.length) {
    throw new Error(
      `interpolate: inputRange (${inputRange.length}) and outputRange (${outputRange.length}) must have the same length`
    );
  }

  if (inputRange.length < 2) {
    throw new Error('interpolate: inputRange must have at least 2 values');
  }

  const {
    easing,
    extrapolateLeft = 'extend',
    extrapolateRight = 'extend',
  } = options ?? {};

  // ── Handle extrapolation ──

  // Below input range
  if (value < inputRange[0]) {
    switch (extrapolateLeft) {
      case 'clamp':
        return outputRange[0];
      case 'identity':
        return value;
      case 'extend':
      default:
        // Fall through to normal interpolation (linear extension)
        break;
    }
  }

  // Above input range
  if (value > inputRange[inputRange.length - 1]) {
    switch (extrapolateRight) {
      case 'clamp':
        return outputRange[outputRange.length - 1];
      case 'identity':
        return value;
      case 'extend':
      default:
        // Fall through to normal interpolation (linear extension)
        break;
    }
  }

  // ── Find the segment ──

  let segmentIndex = 0;
  for (let i = 1; i < inputRange.length; i++) {
    if (value <= inputRange[i]) {
      segmentIndex = i - 1;
      break;
    }
    segmentIndex = i - 1;
  }

  // Clamp segment index
  segmentIndex = Math.min(segmentIndex, inputRange.length - 2);

  const inputMin = inputRange[segmentIndex];
  const inputMax = inputRange[segmentIndex + 1];
  const outputMin = outputRange[segmentIndex];
  const outputMax = outputRange[segmentIndex + 1];

  // ── Normalize to [0, 1] ──

  let t: number;
  if (inputMin === inputMax) {
    t = 0;
  } else {
    t = (value - inputMin) / (inputMax - inputMin);
  }

  // ── Apply easing ──

  if (easing) {
    if (Array.isArray(easing)) {
      // Per-segment easing: use the easing function for this segment
      const easingFn = easing[segmentIndex];
      if (easingFn) {
        t = easingFn(t);
      }
    } else {
      // Single easing function for all segments
      t = easing(t);
    }
  }

  // ── Map to output range ──

  return outputMin + t * (outputMax - outputMin);
}
