// Types for Animation class
export interface AnimationOptions {
  duration?: number;
  timing?: 'easeIn' | 'easeOut' | 'easeInOut' | 'linear';
  onProcess?: (process: number) => void;
  onAnimationFinish?: () => void;
}

export interface TimingFunction {
  (pos: number): number;
}

export interface TimingFunctions {
  easeIn: TimingFunction;
  easeOut: TimingFunction;
  easeInOut: TimingFunction;
  linear: TimingFunction;
}

// Timing functions for animation
const Timing: TimingFunctions = {
  easeIn: function easeIn(pos) {
    return Math.pow(pos, 3);
  },
  easeOut: function easeOut(pos) {
    return Math.pow(pos - 1, 3) + 1;
  },
  easeInOut: function easeInOut(pos) {
    if ((pos /= 0.5) < 1) {
      return 0.5 * Math.pow(pos, 3);
    } else {
      return 0.5 * (Math.pow(pos - 2, 3) + 2);
    }
  },
  linear: function linear(pos) {
    return pos;
  }
};

// Animation class for chart animations
export class Animation {
  private isStop: boolean = false;
  private opts: Required<AnimationOptions>;

  constructor(opts: AnimationOptions) {
    this.isStop = false;
    this.opts = {
      duration: typeof opts.duration === 'undefined' ? 1000 : opts.duration,
      timing: opts.timing || 'easeInOut',
      onProcess: opts.onProcess || (() => {}),
      onAnimationFinish: opts.onAnimationFinish || (() => {})
    };

    const delay = 17;

    function createAnimationFrame() {
      if (typeof setTimeout !== 'undefined') {
        return function(step: (timestamp: number | null) => void, delay: number) {
          setTimeout(function() {
            const timeStamp = +new Date();
            step(timeStamp);
          }, delay);
        };
      } else if (typeof requestAnimationFrame !== 'undefined') {
        return requestAnimationFrame;
      } else {
        return function(step: (timestamp: number | null) => void) {
          step(null);
        };
      }
    }

    const animationFrame = createAnimationFrame();
    let startTimeStamp: number | null = null;

    const _step = (timestamp: number | null) => {
      if (timestamp === null || this.isStop === true) {
        this.opts.onProcess(1);
        this.opts.onAnimationFinish();
        return;
      }
      if (startTimeStamp === null) {
        startTimeStamp = timestamp;
      }
      if (timestamp - startTimeStamp < this.opts.duration) {
        let process = (timestamp - startTimeStamp) / this.opts.duration;
        const timingFunction = Timing[this.opts.timing];
        process = timingFunction(process);
        this.opts.onProcess(process);
        animationFrame(_step.bind(this), delay);
      } else {
        this.opts.onProcess(1);
        this.opts.onAnimationFinish();
      }
    };

    animationFrame(_step.bind(this), delay);
  }

  stop(): void {
    this.isStop = true;
  }
}

// Export the standalone function for backward compatibility
export function AnimationFunction(opts: AnimationOptions): Animation {
  return new Animation(opts);
}
