/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React, {useEffect, useRef, useState} from 'react';
import {Animated, View, Text, PanResponder, Pressable} from 'react-native';
import {TestSuite} from '@rnoh/testerino';
import {Button, Effect, TestCase} from '../../components';

export function AnimatedValueTestCore() {
  return (
    <>
      <TestSuite name="Animated.Value">
        <TestSuite name="addListener + removeListener + removeAllListeners + hasListeners">
          <TestCase.Manual
            itShould="move square 200px to the right and stop animation on pressing setValue"
            initialState={0}
            arrange={({setState}) => (
              <SetValueView singular setState={setState} />
            )}
            assert={({expect, state}) => {
              expect(state).to.be.eq(200);
            }}
          />
          <TestCase.Example itShould="add and remove listeners on click">
            <ListenerView singular={true} />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="setOffset">
          <TestCase.Example itShould="move square 200px to the right on pressing setOffset">
            <SetOffsetView singular={true} />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="event">
          <TestCase.Example itShould="move red square horizontally relatively to the scroll offset">
            <AnimatedScrollViewTestCase />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="tracking">
          <TestCase.Example
            skip="tracking value doesn't seem to work anywhere"
            itShould="move both squares, with blue square following the red with a spring">
            <TrackingValue />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="stopAnimation">
          <TestCase.Manual
            itShould="return the correct value in the callback"
            initialState={0}
            arrange={({setState}) => <GetValueTest setState={setState} />}
            assert={({expect, state}) => {
              expect(state).to.be.greaterThan(0);
            }}
          />
        </TestSuite>
      </TestSuite>

      <TestSuite name="Animated.ValueXY">
        <TestSuite name="core">
          <TestCase.Example itShould="move square both vertically and horizontally">
            <ValueXY />
          </TestCase.Example>
          <TestCase.Example itShould="move the blue square on drag">
            <AnimatedDraggableView />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="addListener + removeListener + removeAllListeners + hasListeners">
          <TestCase.Manual
            itShould="move square 100px to the right and stop animation on pressing setValue"
            initialState={0}
            arrange={({setState}) => <SetValueView setState={setState} />}
            assert={({expect, state}) => {
              expect(state).to.be.eq(100);
            }}
          />
          <TestCase.Example itShould="add and remove listeners on click">
            <ListenerView singular={false} />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="setOffset">
          <TestCase.Example itShould="move square 100px to the right on pressing setOffset">
            <SetOffsetView singular={false} />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="getLayout">
          <TestCase.Manual<Object>
            itShould="get layout of animated value"
            initialState={{}}
            arrange={({setState}) => {
              const animValue = new Animated.ValueXY({x: 1, y: 1});
              return (
                <Effect
                  onMount={() => {
                    setState(animValue.getLayout());
                  }}
                />
              );
            }}
            assert={({state, expect}) => {
              expect(JSON.stringify(state)).to.be.eq(
                JSON.stringify({left: 1, top: 1}),
              );
            }}
          />
          <TestCase.Example itShould="blue square should move the same as red square">
            <AnimatedGetLayout />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="extractOffset">
          <TestCase.Example itShould="move square to the right after extract offset">
            <ExtractOffsetView />
          </TestCase.Example>
        </TestSuite>

        <TestSuite name="flattenOffset">
          <TestCase.Example itShould="move square to the left after flatten offset">
            <FlattenOffsetView />
          </TestCase.Example>
        </TestSuite>
        <TestCase.Example itShould="maintain translation after toggling display to 'none' and back">
          <ToggleDisplayExample />
        </TestCase.Example>
      </TestSuite>
    </>
  );
}

const ToggleDisplayExampleInner = React.memo(function () {
  const animatedValue = React.useRef(new Animated.Value(0)).current;
  const animation = React.useRef(
    Animated.timing(animatedValue, {
      toValue: 200,
      duration: 1000,
      useNativeDriver: true,
    }),
  ).current;
  return (
    <View>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: animatedValue,
            },
          ],
        }}
      />
      <Button
        label="start"
        onPress={() => {
          animation.reset();
          animation.start();
        }}
      />
      <Button label="stop" onPress={() => animation.stop()} />
    </View>
  );
});

function ToggleDisplayExample() {
  const [visible, setVisible] = React.useState(true);

  return (
    <View style={{width: '100%'}}>
      <View style={{width: '100%', display: visible ? 'flex' : 'none'}}>
        <ToggleDisplayExampleInner />
      </View>
      <Button label="toggle display" onPress={() => setVisible(v => !v)} />
    </View>
  );
}

const ValueXY = () => {
  const square1Anim = useRef(new Animated.ValueXY({x: 50, y: 50})).current;

  const animation = Animated.sequence([
    Animated.timing(square1Anim, {
      toValue: {x: 0, y: 0},
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: {x: 0, y: 50},
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: {x: 50, y: 0},
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: {x: 50, y: 50},
      duration: 1000,
      useNativeDriver: true,
    }),
  ]);
  const handleAnimation = () => {
    animation.reset();
    animation.start();
  };

  return (
    <Pressable style={{height: 120, width: '100%'}} onPress={handleAnimation}>
      <View style={{height: 100}}>
        <Animated.View
          style={{
            height: 40,
            width: 40,
            backgroundColor: 'red',
            transform: [
              {
                translateX: square1Anim.x,
              },
              {
                translateY: square1Anim.y,
              },
            ],
          }}
        />
      </View>
      <Text style={{height: 20}}>Press me to start animation</Text>
    </Pressable>
  );
};

const ExtractOffsetView = () => {
  const value = useRef(new Animated.Value(0)).current;
  const animation = Animated.loop(
    Animated.sequence([
      Animated.timing(value, {
        toValue: 100,
        duration: 1000,
        useNativeDriver: true,
      }),
      Animated.timing(value, {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]),
  );
  return (
    <View style={{width: '100%'}}>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: value,
            },
          ],
        }}
      />
      <View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
        <Button
          label="start"
          onPress={() => {
            animation.reset();
            animation.start();
          }}
        />
        <Button label="stop" onPress={() => animation.stop()} />
        <Button label="extract offset" onPress={() => value.extractOffset()} />
      </View>
    </View>
  );
};
const FlattenOffsetView = () => {
  const value = useRef(new Animated.Value(0)).current;
  const animation = Animated.loop(
    Animated.sequence([
      Animated.timing(value, {
        toValue: 100,
        duration: 1000,
        useNativeDriver: true,
      }),
      Animated.timing(value, {
        toValue: 0,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]),
  );
  value.setOffset(100);
  return (
    <View style={{width: '100%'}}>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: value,
            },
          ],
        }}
      />
      <View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
        <Button
          label="start"
          onPress={() => {
            animation.reset();
            animation.start();
          }}
        />
        <Button label="stop" onPress={() => animation.stop()} />
        <Button
          label="flatten offset"
          onPress={() => {
            value.flattenOffset();
          }}
        />
      </View>
    </View>
  );
};
const SetOffsetView = (props: {singular: boolean}) => {
  const animValue = useRef(new Animated.Value(0)).current;
  const animValueXY = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  if (props.singular) {
    return (
      <MovingSquare
        animValue={animValue}
        labels={['set offset']}
        onPresses={[() => animValue.setOffset(200)]}
      />
    );
  } else {
    return (
      <MovingSquareXY
        animValueXY={animValueXY}
        labels={['set offset']}
        onPresses={[() => animValueXY.setOffset({x: 100, y: 0})]}
      />
    );
  }
};

const ListenerView = (props: {singular: boolean}) => {
  const [text, setText] = useState('');
  const [listeners, setListeners] = useState<string[]>([]);
  const animValue = useRef(new Animated.Value(0)).current;
  const animValueXY = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  const listener = () => {};
  const addListener = () => {
    listeners.push(animValue.addListener(listener));
    setListeners(listeners);
    checkListener();
  };
  const removeListener = () => {
    const lastListener = listeners.pop();
    if (lastListener) {
      animValue.removeListener(lastListener);
    }
    checkListener();
  };
  const removeAll = () => {
    animValue.removeAllListeners();
    checkListener();
  };
  const checkListener = () => {
    setText(
      animValue.hasListeners()
        ? `listener: ${listeners}`
        : 'no listener is attached',
    );
  };
  return (
    <>
      <Text>{text}</Text>
      {props.singular ? (
        <MovingSquare
          animValue={animValue}
          labels={['add', 'remove', 'removeAll']}
          onPresses={[addListener, removeListener, removeAll]}
        />
      ) : (
        <MovingSquareXY
          animValueXY={animValueXY}
          labels={['add', 'remove', 'removeAll']}
          onPresses={[addListener, removeListener, removeAll]}
        />
      )}
    </>
  );
};

const SetValueView = (props: {
  singular?: boolean;
  setState: React.Dispatch<React.SetStateAction<number>>;
}) => {
  const animValue = useRef(new Animated.Value(0)).current;
  const animValueXY = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

  useEffect(() => {
    if (props.singular) {
      animValue.addListener(({value}) => {
        if (value === 200) {
          props.setState(value);
        }
      });
    } else {
      animValueXY.addListener(({x}) => {
        if (x === 100) {
          props.setState(x);
        }
      });
    }
  }, []);

  if (props.singular) {
    return (
      <MovingSquare
        animValue={animValue}
        labels={['set value']}
        onPresses={[() => animValue.setValue(200)]}
      />
    );
  } else {
    return (
      <MovingSquareXY
        animValueXY={animValueXY}
        labels={['set valueXY']}
        onPresses={[() => animValueXY.setValue({x: 100, y: 0})]}
      />
    );
  }
};

const MovingSquare = (props: {
  animValue: Animated.Value;
  labels: string[];
  onPresses: (() => void)[];
}) => {
  const [isRunning, setIsRunning] = useState(false);
  const animation = Animated.loop(
    Animated.sequence([
      Animated.timing(props.animValue, {
        toValue: 100,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(props.animValue, {
        toValue: 0,
        duration: 500,
        useNativeDriver: true,
      }),
    ]),
  );
  const animate = () => {
    if (isRunning) {
      animation.stop();
      animation.reset();
      props.animValue.setOffset(0);
      setIsRunning(false);
    } else {
      setIsRunning(true);
      animation.start();
    }
  };
  const buttons = props.labels.map((value, index) => (
    <Button label={value} onPress={props.onPresses[index]} key={index} />
  ));
  return (
    <View style={{width: '100%'}}>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: props.animValue,
            },
          ],
        }}
      />
      <View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
        <Button label="animate" onPress={animate} />
        {buttons}
      </View>
    </View>
  );
};

const MovingSquareXY = (props: {
  animValueXY: Animated.ValueXY;
  labels: string[];
  onPresses: (() => void)[];
}) => {
  const [isRunning, setIsRunning] = useState(false);
  const animation = Animated.loop(
    Animated.sequence([
      Animated.timing(props.animValueXY, {
        toValue: {x: 25, y: 25},
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(props.animValueXY, {
        toValue: {x: 0, y: 0},
        duration: 500,
        useNativeDriver: true,
      }),
    ]),
  );
  const animate = () => {
    if (isRunning) {
      animation.stop();
      props.animValueXY.setOffset({x: 0, y: 0});
      setIsRunning(false);
    } else {
      setIsRunning(true);
      animation.start();
    }
  };
  const buttons = props.labels.map((value, index) => (
    <Button label={value} onPress={props.onPresses[index]} key={index} />
  ));
  return (
    <View style={{width: '100%', height: 100}}>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: props.animValueXY.x,
            },
            {translateY: props.animValueXY.y},
          ],
        }}
      />
      <View style={{flexDirection: 'row', flexWrap: 'wrap', marginTop: 20}}>
        <Button label="animate" onPress={animate} />
        {buttons}
      </View>
    </View>
  );
};

const AnimatedGetLayout = () => {
  const [isRunning, setIsRunning] = useState(false);
  const animValueXY = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

  const animation = Animated.loop(
    Animated.sequence([
      Animated.timing(animValueXY, {
        toValue: {x: 80, y: 80},
        duration: 500,
        useNativeDriver: false,
      }),
      Animated.timing(animValueXY, {
        toValue: {x: 0, y: 0},
        duration: 500,
        useNativeDriver: false,
      }),
    ]),
  );

  const animate = () => {
    if (isRunning) {
      animation.stop();
      animValueXY.setOffset({x: 0, y: 0});
      setIsRunning(false);
    } else {
      setIsRunning(true);
      animation.start();
    }
  };

  return (
    <View>
      <View style={{width: '100%', height: 100, flexDirection: 'row'}}>
        <View style={{flex: 1}}>
          <Animated.View
            style={{
              height: 20,
              width: 20,
              backgroundColor: 'red',
              transform: animValueXY.getTranslateTransform(),
            }}
          />
        </View>
        <View style={{flex: 1, position: 'relative'}}>
          <Animated.View
            style={[
              animValueXY.getLayout(),
              {
                position: 'absolute',
                height: 20,
                width: 20,
                backgroundColor: 'blue',
              },
            ]}
          />
        </View>
      </View>
      <View style={{flexDirection: 'row', flexWrap: 'wrap'}}>
        <Button label="animate" onPress={animate} />
      </View>
    </View>
  );
};

const AnimatedDraggableView = () => {
  const pan = useRef(new Animated.ValueXY()).current;

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: Animated.event(
      [
        null,
        {
          dx: pan.x,
          dy: pan.y,
        },
      ],
      {
        useNativeDriver: false,
      },
    ),
    onPanResponderRelease: () => {
      Animated.spring(pan, {
        toValue: {x: 0, y: 0},
        useNativeDriver: false,
      }).start();
    },
  });

  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        height: 400,
      }}>
      <Animated.View
        {...panResponder.panHandlers}
        style={[
          pan.getLayout(),
          {backgroundColor: '#61dafb', width: 80, height: 80, borderRadius: 4},
        ]}
      />
    </View>
  );
};

const AnimatedScrollViewTestCase = () => {
  const scrollY = new Animated.Value(0);
  const translation = scrollY.interpolate({
    inputRange: [0, 200],
    outputRange: [0, 200],
    extrapolate: 'clamp',
  });

  return (
    <View
      style={{
        width: '100%',
        height: 100,
        position: 'relative',
        overflow: 'hidden',
      }}>
      <Animated.ScrollView
        style={{width: '100%', height: '100%'}}
        contentContainerStyle={{alignItems: 'center', justifyContent: 'center'}}
        scrollEventThrottle={16}
        onScroll={Animated.event(
          [{nativeEvent: {contentOffset: {y: scrollY}}}],
          {
            useNativeDriver: true,
          },
        )}>
        {new Array(3).fill(0).map((_, idx) => {
          return (
            <View
              key={idx}
              style={{
                width: '100%',
                height: 50,
                backgroundColor: 'gray',
                marginBottom: 50,
              }}
            />
          );
        })}
      </Animated.ScrollView>
      <Animated.View
        style={[
          {
            position: 'absolute',
            bottom: 0,
            transform: [{translateX: translation}],
            width: 32,
            height: 32,
            backgroundColor: 'red',
          },
        ]}
      />
    </View>
  );
};

const TrackingValue = () => {
  const square1Anim = useRef(new Animated.Value(50)).current;
  const square2Anim = useRef(new Animated.Value(50)).current;

  const animation = Animated.sequence([
    Animated.timing(square1Anim, {
      toValue: 0,
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: 50,
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: 0,
      duration: 1000,
      useNativeDriver: true,
    }),
    Animated.timing(square1Anim, {
      toValue: 50,
      duration: 1000,
      useNativeDriver: true,
    }),
  ]);

  const handleAnimation = () => {
    animation.reset();
    animation.start();

    const tracking = Animated.spring(square2Anim, {
      toValue: square1Anim,
      useNativeDriver: true,
      mass: 10,
    });
    tracking.start();
  };

  return (
    <Pressable style={{height: 120, width: '100%'}} onPress={handleAnimation}>
      <View style={{height: 100}}>
        <Animated.View
          style={{
            height: 40,
            width: 40,
            backgroundColor: 'red',
            transform: [{translateX: square1Anim}],
          }}
        />
        <Animated.View
          style={{
            height: 40,
            width: 40,
            backgroundColor: 'blue',
            transform: [{translateX: square2Anim}],
          }}
        />
      </View>
      <Text style={{height: 20}}>Press me to start animation</Text>
    </Pressable>
  );
};

const GetValueTest = ({
  setState,
}: {
  setState: React.Dispatch<React.SetStateAction<number>>;
}) => {
  const animationValue = useRef(
    new Animated.Value(0, {useNativeDriver: true}),
  ).current;

  const startTest = () => {
    Animated.timing(animationValue, {
      toValue: 200,
      duration: 2000,
      useNativeDriver: true,
    }).start();

    setTimeout(() => {
      animationValue.stopAnimation(currentValue => {
        setState(currentValue);
      });
    }, 1000);
  };

  return (
    <View style={{width: '100%', height: 100}}>
      <Animated.View
        style={{
          height: 20,
          width: 20,
          margin: 10,
          backgroundColor: 'red',
          transform: [
            {
              translateX: animationValue,
            },
          ],
        }}
      />
      <Button label="start" onPress={startTest} />
    </View>
  );
};