/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

import type { EndResult } from './animations/Animation';
import { AnimatedEvent, attachNativeEvent } from './AnimatedEvent';
import AnimatedImplementation from './AnimatedImplementation';
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
import AnimatedNode from './nodes/AnimatedNode';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';
import createAnimatedComponent from './createAnimatedComponent';
import type { EndCallback } from './animations/Animation';
import type { TimingAnimationConfig } from './animations/TimingAnimation';
import type { DecayAnimationConfig } from './animations/DecayAnimation';
import type { SpringAnimationConfig } from './animations/SpringAnimation';
import type { Numeric as AnimatedNumeric } from './AnimatedImplementation';
import AnimatedColor from './nodes/AnimatedColor';

/**
 * Animations are a source of flakiness in snapshot testing. This mock replaces
 * animation functions from AnimatedImplementation with empty animations for
 * predictability in tests. When possible the animation will run immediately
 * to the final state.
 */

// Prevent any callback invocation from recursively triggering another
// callback, which may trigger another animation
let inAnimationCallback = false;
declare function mockAnimationStart(start: (callback?: ?EndCallback) => void): (callback?: ?EndCallback) => void;
export type CompositeAnimation = {
  start: (callback?: ?EndCallback) => void,
  stop: () => void,
  reset: () => void,
  _startNativeLoop: (iterations?: number) => void,
  _isUsingNativeDriver: () => boolean,
  ...
};
const emptyAnimation = {
  start: () => {},
  stop: () => {},
  reset: () => {},
  _startNativeLoop: () => {},
  _isUsingNativeDriver: () => {
    return false;
  }
};
declare var mockCompositeAnimation: (animations: Array<CompositeAnimation>) => CompositeAnimation;
const spring = function (value: AnimatedValue | AnimatedValueXY | AnimatedColor, config: SpringAnimationConfig): CompositeAnimation {
  const anyValue: any = value;
  return {
    ...emptyAnimation,
    start: mockAnimationStart((callback?: ?EndCallback): void => {
      anyValue.setValue(config.toValue);
      callback?.({
        finished: true
      });
    })
  };
};
const timing = function (value: AnimatedValue | AnimatedValueXY | AnimatedColor, config: TimingAnimationConfig): CompositeAnimation {
  const anyValue: any = value;
  return {
    ...emptyAnimation,
    start: mockAnimationStart((callback?: ?EndCallback): void => {
      anyValue.setValue(config.toValue);
      callback?.({
        finished: true
      });
    })
  };
};
const decay = function (value: AnimatedValue | AnimatedValueXY | AnimatedColor, config: DecayAnimationConfig): CompositeAnimation {
  return emptyAnimation;
};
const sequence = function (animations: Array<CompositeAnimation>): CompositeAnimation {
  return mockCompositeAnimation(animations);
};
type ParallelConfig = {
  stopTogether?: boolean,
  ...
};
const parallel = function (animations: Array<CompositeAnimation>, config?: ?ParallelConfig): CompositeAnimation {
  return mockCompositeAnimation(animations);
};
const delay = function (time: number): CompositeAnimation {
  return emptyAnimation;
};
const stagger = function (time: number, animations: Array<CompositeAnimation>): CompositeAnimation {
  return mockCompositeAnimation(animations);
};
type LoopAnimationConfig = {
  iterations: number,
  resetBeforeIteration?: boolean,
  ...
};
const loop = function (animation: CompositeAnimation,
// $FlowFixMe[prop-missing]
{
  iterations = -1
}: LoopAnimationConfig = {}): CompositeAnimation {
  return emptyAnimation;
};
export type { AnimatedNumeric as Numeric };
export default {
  Value: AnimatedValue,
  ValueXY: AnimatedValueXY,
  Color: AnimatedColor,
  Interpolation: AnimatedInterpolation,
  Node: AnimatedNode,
  decay,
  timing,
  spring,
  add: AnimatedImplementation.add,
  subtract: AnimatedImplementation.subtract,
  divide: AnimatedImplementation.divide,
  multiply: AnimatedImplementation.multiply,
  modulo: AnimatedImplementation.modulo,
  diffClamp: AnimatedImplementation.diffClamp,
  delay,
  sequence,
  parallel,
  stagger,
  loop,
  event: AnimatedImplementation.event,
  createAnimatedComponent,
  attachNativeEvent,
  forkEvent: AnimatedImplementation.forkEvent,
  unforkEvent: AnimatedImplementation.unforkEvent,
  Event: AnimatedEvent
};