/**
 * 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 {TestSuite} from '@rnoh/testerino';
import {createRef, forwardRef, useEffect, useRef, useState} from 'react';
import {
  Animated,
  FlatList,
  PanResponder,
  Pressable,
  RefreshControl,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  TouchableWithoutFeedback,
  View,
  ViewStyle,
} from 'react-native';
import {Button, StateKeeper, TestCase} from '../components';
import React from 'react';
import {PALETTE} from '../components/palette';
import {useEnvironment} from '../contexts';

export function TouchHandlingTest() {
  const [refreshing, setRefreshing] = React.useState(false);
  const {
    env: {driver},
  } = useEnvironment();
  const onRefresh = React.useCallback(() => {
    setRefreshing(true);
    setTimeout(() => {
      setRefreshing(false);
    }, 10000);
  }, []);
  return (
    <TestSuite name="Touch Handling">
      <TestCase.Automated
        tags={['sequential']}
        itShould="recognize the subsequent touch correctly after a target view has been removed during touch"
        initialState={{
          isVisible: true,
          ref1: createRef<View>(),
          ref2: createRef<View>(),
          wasTouchEndCalled: false,
        }}
        arrange={({state, setState, done}) => {
          return (
            <View style={{width: 128, height: 128}}>
              <View
                ref={state.ref1}
                style={{
                  width: 64,
                  height: 64,
                  backgroundColor: state.wasTouchEndCalled ? 'green' : 'red',
                }}
                onTouchEnd={() => {
                  setState(prev => ({...prev, wasTouchEndCalled: true}));
                  done();
                }}
              />
              {state.isVisible && (
                <View
                  ref={state.ref2}
                  style={{width: 64, height: 64, backgroundColor: 'blue'}}
                  onTouchStart={() => {
                    setState(prev => ({...prev, isVisible: false}));
                  }}
                />
              )}
            </View>
          );
        }}
        act={async ({state, done}) => {
          driver?.click({ref: state.ref2});
          await new Promise(resolve => {
            setTimeout(resolve, 0);
          });
          driver?.click({ref: state.ref1});
          await new Promise(resolve => {
            setTimeout(resolve, 1000);
          });
          done();
        }}
        assert={({expect, state}) => {
          expect(state.wasTouchEndCalled).to.be.eq(true);
        }}
      />

      <TestCase.Automated
        itShould="pass when pressed red rectangle"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => {
          return (
            <TouchIssue1
              onNRendersChanged={() => {
                driver?.click({ref: state.ref});
              }}
              ref={state.ref}
              onPress={() => {
                setState(prev => ({...prev, pressed: true}));
                done();
              }}
            />
          );
        }}
        act={() => {}}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Automated
        itShould="pass when pressed red rectangle which is outside its green parent view"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => {
          return (
            <TouchIssue2
              ref={state.ref}
              onPress={() => {
                setState(prev => ({...prev, pressed: true}));
                done();
              }}
            />
          );
        }}
        act={async ({state}) => {
          driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Automated
        itShould="register a touch after native transform animation"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => (
          <RectangleSlider
            ref={state.ref}
            onPress={() => {
              setState(prev => ({...prev, pressed: true}));
              done();
            }}
            onAnimationFinished={() => {
              setTimeout(() => {
                driver?.click({ref: state.ref});
              }, 100);
            }}
          />
        )}
        act={() => {}}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Automated
        itShould="handle press on rotated view"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => (
          <TouchableTransformedTest
            ref={state.ref}
            onPress={() => {
              setState(prev => ({...prev, pressed: true}));
              done();
            }}
            transform={[{rotate: '180deg'}, {translateX: 100}]}
          />
        )}
        act={async ({state}) => {
          await driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Automated
        itShould="handle press on scaled view"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => (
          <TouchableTransformedTest
            ref={state.ref}
            onPress={() => {
              setState(prev => ({...prev, pressed: true}));
              done();
            }}
            transform={[{scaleX: -1}, {translateX: 100}]}
          />
        )}
        act={async ({state}) => {
          await driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Manual
        itShould="pass after a multi-touch event"
        initialState={false}
        arrange={({setState}) => (
          <View style={{alignItems: 'center', backgroundColor: 'white'}}>
            <View
              style={{
                alignSelf: 'center',
                width: 150,
                height: 150,
                backgroundColor: 'red',
              }}
              onTouchStart={e => {
                if (e.nativeEvent.touches.length > 1) {
                  setState(true);
                }
              }}
            />
          </View>
        )}
        assert={({expect, state}) => {
          expect(state).to.be.true;
        }}
      />
      <TestCase.Example modal itShould="report transformed touch coordinates">
        <TouchCoordinatesTest
          transform={[
            {rotate: '45deg'},
            {translateY: 50},
            {translateX: -50},
            {scaleY: -1},
            {scale: 0.75},
          ]}
        />
      </TestCase.Example>
      <TestCase.Example
        modal
        itShould="report transformed touch coordinates (2)">
        <TouchCoordinatesTest
          transform={[
            {rotate: '-45deg'},
            {translateY: 50},
            {translateX: -50},
            {scaleX: -1},
            {scaleY: 1.25},
          ]}
        />
      </TestCase.Example>
      <TestCase.Example
        modal
        itShould="toggle color on press but not on scroll start">
        <StateKeeper
          initialValue={'red'}
          renderContent={(backgroundColor, setBackgroundColor) => {
            return (
              <ScrollView style={{height: 256}}>
                <TouchableOpacity
                  onPress={() =>
                    setBackgroundColor(prev =>
                      prev === 'red' ? 'green' : 'red',
                    )
                  }>
                  <View style={{width: 256, height: 512, backgroundColor}}>
                    <View
                      style={{
                        width: 256,
                        height: 128,
                        backgroundColor: 'white',
                        opacity: 0.75,
                        marginTop: 128,
                      }}
                    />
                    <View
                      style={{
                        width: 256,
                        height: 128,
                        backgroundColor: 'white',
                        opacity: 0.75,
                        marginTop: 128,
                      }}
                    />
                  </View>
                </TouchableOpacity>
              </ScrollView>
            );
          }}
        />
      </TestCase.Example>
      <TestCase.Automated
        itShould="respond to touches on disabled components when wrapped in Touchables"
        tags={['sequential']}
        //TODO: https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/1522
        skip={'broken'}
        initialState={{
          pressed: false,
          ref: createRef<TextInput>(),
        }}
        arrange={({setState, state, done}) => (
          <TouchableOpacity
            onPress={() => {
              setState(prev => ({...prev, pressed: true}));
              done();
            }}>
            <TextInput
              ref={state.ref}
              editable={false}
              style={{
                borderWidth: 2,
                borderColor: 'blue',
              }}
              value={'Non-editable TextInput'}
            />
          </TouchableOpacity>
        )}
        act={async ({state}) => {
          await driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Example
        modal
        itShould="allow vertical scroll when flinging fast after horizontal swipe on gray area">
        <ScrollViewLockedIssue />
      </TestCase.Example>
      <TestCase.Automated
        itShould="pass after tapping cyan area but not red area (child's hitSlop)"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => {
          return (
            <View style={{alignItems: 'center', backgroundColor: 'black'}}>
              <View
                style={{
                  flexDirection: 'row',
                  padding: 48,
                  backgroundColor: PALETTE.REACT_CYAN_LIGHT,
                  justifyContent: 'center',
                  width: 48 * 3,
                }}>
                <TouchableOpacity
                  onPress={() => {
                    setState(prev => ({...prev, pressed: true}));
                    done();
                  }}>
                  <View
                    ref={state.ref}
                    style={{backgroundColor: 'purple', width: 48, height: 48}}
                    hitSlop={{top: 48, bottom: 48, left: 48, right: 48}}
                  />
                </TouchableOpacity>
                <View
                  style={{
                    backgroundColor: 'red',
                    width: 48,
                    height: 48,
                    position: 'absolute',
                    top: 48,
                  }}
                />
              </View>
            </View>
          );
        }}
        act={async ({state}) => {
          await driver?.click({ref: state.ref, offset: {x: 48, y: 48}});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Automated
        itShould="handle views with scale: 0 correctly"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, done, state}) => {
          return (
            <View>
              <TouchableOpacity
                onPress={() => {
                  setState(prev => ({...prev, pressed: true}));
                  done();
                }}>
                <View
                  ref={state.ref}
                  style={{
                    backgroundColor: 'red',
                    width: 100,
                    height: 100,
                  }}
                />
              </TouchableOpacity>
              <View
                style={{
                  position: 'absolute',
                  transform: [{scale: 0}],
                  backgroundColor: 'green',
                  width: 100,
                  height: 100,
                }}
              />
              <View
                style={{
                  position: 'absolute',
                  transform: [{scaleX: 0}],
                  backgroundColor: 'blue',
                  width: 100,
                  height: 100,
                }}
              />
              <View
                style={{
                  position: 'absolute',
                  transform: [{scaleY: 0}],
                  backgroundColor: 'yellow',
                  width: 100,
                  height: 100,
                }}
              />
            </View>
          );
        }}
        act={async ({state}) => {
          await driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Example itShould="emit touch events with resonable timestamps (event.timeStamp is the UNIX time, nativeEvent.timestamp is the device uptime, both are in ms)">
        <TimestampExample />
      </TestCase.Example>
      <TestCase.Automated
        itShould="report touches to transformed children that exceed parent"
        tags={['sequential']}
        initialState={{
          pressed: false,
          ref: createRef<View>(),
        }}
        arrange={({setState, state, done}) => {
          return (
            <View>
              <TouchableWithoutFeedback
                onPress={() => {
                  setState(prev => ({...prev, pressed: false}));
                  done();
                }}>
                <View
                  style={{
                    width: 50,
                    height: 50,
                    backgroundColor: 'red',
                  }}>
                  <TouchableWithoutFeedback
                    onPress={() => {
                      setState(prev => ({...prev, pressed: true}));
                      done();
                    }}>
                    <View
                      ref={state.ref}
                      style={{
                        width: 50,
                        height: 50,
                        backgroundColor: 'blue',
                        transform: [{translateX: 100}],
                      }}
                    />
                  </TouchableWithoutFeedback>
                </View>
              </TouchableWithoutFeedback>
            </View>
          );
        }}
        act={async ({state}) => {
          await driver?.click({ref: state.ref});
        }}
        assert={({expect, state}) => {
          expect(state.pressed).to.be.true;
        }}
      />
      <TestCase.Manual
        modal
        itShould="take into account offset added by RefreshControl while refreshing - press 0 while refreshing to pass"
        initialState={undefined as number | undefined}
        arrange={({setState}) => {
          const styles = StyleSheet.create({
            item: {
              backgroundColor: '#f9c2ff',
              padding: 10,
              marginVertical: 4,
              marginHorizontal: 8,
            },
            title: {
              fontSize: 32,
            },
          });
          const DATA = [0, 1, 2, 3, 4];

          const Item = ({index}: {index: number}) => (
            <Pressable
              style={styles.item}
              onPress={() => {
                setState(index);
              }}>
              <Text style={styles.title}>{index}</Text>
            </Pressable>
          );
          return (
            <FlatList
              refreshing={false}
              data={DATA}
              style={{flex: 1, backgroundColor: 'yellow', height: 200}}
              renderItem={({index}) => <Item index={index} />}
              keyExtractor={item => `${item}`}
              refreshControl={
                <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
              }
            />
          );
        }}
        assert={({expect, state}) => {
          expect(state).to.be.eq(0);
        }}
      />
    </TestSuite>
  );
}

function TimestampExample() {
  const [nativEventTimestamp, setNativeEventTimestamp] = useState<number>(0);
  const [eventTimestamp, setEventTimestamp] = useState<number>(0);

  return (
    <>
      <Text>event.timeStamp: {eventTimestamp}</Text>
      <Text>event.nativeEvent.timestamp: {nativEventTimestamp}</Text>
      <TouchableOpacity
        onPress={event => {
          setEventTimestamp(event.timeStamp);
          setNativeEventTimestamp(event.nativeEvent.timestamp);
          console.log('event.timeStamp: ' + event.timeStamp);
          console.log(
            'event.nativeEvent.timestamp: ' + event.nativeEvent.timestamp,
          );
        }}>
        <Text>Press me to run the example</Text>
      </TouchableOpacity>
    </>
  );
}

const RectangleSlider = forwardRef<
  View,
  {onPress: () => void; onAnimationFinished: () => void}
>(({onPress, onAnimationFinished}, ref) => {
  const square1Anim = useRef(new Animated.Value(0)).current;
  const animation = Animated.timing(square1Anim, {
    toValue: 64,
    duration: 250,
    useNativeDriver: true,
  });
  const handleAnimation = () => {
    animation.reset();
    animation.start(onAnimationFinished);
  };

  useEffect(() => {
    handleAnimation();
  }, []);

  return (
    <>
      <Animated.View
        onTouchEnd={() => {
          onPress();
        }}
        ref={ref}
        style={{
          height: 64,
          width: 64,
          backgroundColor: 'red',
          transform: [
            {
              translateX: square1Anim,
            },
          ],
        }}
      />
      <Button label="Animate" onPress={handleAnimation} />
    </>
  );
});

const TouchableTransformedTest = forwardRef<
  View,
  {
    onPress: () => void;
    transform: ViewStyle['transform'];
  }
>(({onPress, transform}, ref) => {
  return (
    <View
      ref={ref}
      style={{
        alignSelf: 'center',
        width: 75,
        backgroundColor: 'red',
        transform,
      }}>
      <TouchableOpacity onPress={onPress}>
        <Text>Press me!</Text>
      </TouchableOpacity>
    </View>
  );
});

function TouchCoordinatesTest({
  transform,
}: {
  transform?: ViewStyle['transform'];
}) {
  const [position, setPosition] = React.useState({x: 0, y: 0});

  return (
    <View style={{height: 250}}>
      <Text>Touch coordinates: {JSON.stringify(position)}</Text>
      <View
        style={{
          alignSelf: 'center',
          width: 150,
          height: 150,
          backgroundColor: 'red',
          transform,
          opacity: 0.5,
        }}
        onTouchStart={e => {
          setPosition({
            x: Math.round(e.nativeEvent.locationX),
            y: Math.round(e.nativeEvent.locationY),
          });
        }}
        onTouchMove={e => {
          setPosition({
            x: Math.round(e.nativeEvent.locationX),
            y: Math.round(e.nativeEvent.locationY),
          });
        }}>
        <Text>Top left</Text>
      </View>
    </View>
  );
}

const TouchIssue1 = forwardRef<
  View,
  {onNRendersChanged: () => void; onPress: () => void}
>(({onPress, onNRendersChanged}, ref) => {
  const nPressesRef = useRef(0);
  const [nRenders, setNRenders] = useState(0);
  const [label, setLabel] = useState('hello');

  useEffect(() => {
    const timeout = setTimeout(() => {
      setNRenders(prev => prev + 1);
    }, 0);
    return () => {
      clearTimeout(timeout);
    };
  }, []);

  useEffect(() => {
    onNRendersChanged();
  }, [nRenders]);

  if (nRenders > 0) {
    return (
      <View style={{opacity: 1, marginTop: 50}}>
        <View collapsable={false}>
          <TouchableOpacity
            ref={ref}
            onPress={() => {
              onPress();
              setLabel(`${label}+${nPressesRef.current}`);
              nPressesRef.current++;
            }}>
            <Text>{label}</Text>
            <View
              id="foo"
              style={{height: 100, width: 100, backgroundColor: 'red'}}
            />
          </TouchableOpacity>
        </View>
      </View>
    );
  } else {
    return (
      <View style={{opacity: 0, marginTop: 50}}>
        <View collapsable={false}>
          <View style={{height: 100, width: 100}} />
        </View>
      </View>
    );
  }
});

const TouchIssue2 = forwardRef<View, {onPress: () => void}>(
  ({onPress}: {onPress: () => void}, ref) => {
    return (
      <View style={{height: 150}}>
        <View
          style={{opacity: 1, width: 100, height: 50, backgroundColor: 'green'}}
          collapsable={false}>
          <TouchableOpacity
            style={{marginTop: 50}}
            onPress={() => {
              onPress();
            }}>
            <View
              ref={ref}
              style={{height: 100, width: 100, backgroundColor: 'red'}}
            />
          </TouchableOpacity>
        </View>
      </View>
    );
  },
);

class ScrollViewLockedIssue extends React.Component {
  textInput: any;
  blur = () => {
    this.textInput.blur();
  };
  _gestureHandlers: any;
  componentWillMount() {
    this._gestureHandlers = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponder: (event, gestureState) => {
        const touchCapture =
          Math.abs(gestureState.dx) > 10 || Math.abs(gestureState.dy) > 10;
        console.log('====================touchCapture=' + touchCapture);
        return touchCapture;
      },
      onMoveShouldSetPanResponderCapture: () => false,
      onPanResponderMove: (_evt, _gestureState) => {
        console.warn('move');
      },
    });
  }

  render(): React.ReactNode {
    return (
      <ScrollView style={{height: '75%'}}>
        <View style={{height: 100}}>
          <Text>this is first part</Text>
        </View>
        <View
          style={{height: 300, backgroundColor: 'green'}}
          {...this._gestureHandlers.panHandlers}>
          <Text>this is second part</Text>
        </View>
        <View
          style={{
            height: 800,
            backgroundColor: 'yellow',
          }}
          {...this._gestureHandlers.panHandlers}>
          <TouchableOpacity onPress={() => console.log('1111111')}>
            <Text style={{backgroundColor: 'gray', height: 100}}>
              SWIPE HORIZONTALLY HERE
            </Text>
          </TouchableOpacity>
        </View>
      </ScrollView>
    );
  }
}