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

import {
  View,
  ScrollView,
  Text,
  StyleSheet,
  ViewStyle,
  StyleProp,
  TextInput,
  Platform,
  ScrollViewProps,
} from 'react-native';
import { TestSuite, TestCase } from '@rnoh/testerino';
import React, { useEffect, useRef, useState } from 'react';
import { Button, ObjectDisplayer } from '../components';
import { GestureResponderEvent } from 'react-native/Libraries/Types/CoreEventTypes';

const COMMON_PROPS = {
  style: {
    borderWidth: 3,
    borderColor: 'firebrick',
  },
  contentContainerStyle: {
    alignItems: 'center' as 'center',
    justifyContent: 'center' as 'center',
  },
  children: getScrollViewContent({}),
};

export function ScrollViewTest() {
  return (
    <TestSuite name="ScrollView">
      <TestSuite name="styles">
        <TestCase
          modal
          itShould="render scroll view with different border radii (topLeft, topRight, ...)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                borderWidth: 3,
                borderTopLeftRadius: 10,
                borderTopRightRadius: 20,
                borderBottomRightRadius: 30,
                borderBottomLeftRadius: 40,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view with different border widths (left, right, top, bottom)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                borderLeftWidth: 3,
                borderTopWidth: 6,
                borderRightWidth: 9,
                borderBottomWidth: 12,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view with different border colors (left, right, top, bottom)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                backgroundColor: 'beige',
                borderWidth: 3,
                borderLeftColor: 'firebrick',
                borderTopColor: 'chocolate',
                borderRightColor: 'black',
                borderBottomColor: 'blue',
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render  scroll view with different border radii (start, end)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                borderWidth: 3,
                borderTopStartRadius: 10,
                borderTopEndRadius: 20,
                borderBottomEndRadius: 30,
                borderBottomStartRadius: 40,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view with different border widths (start, end)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                borderStartWidth: 3,
                borderTopWidth: 6,
                borderEndWidth: 9,
                borderBottomWidth: 12,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view with different border colors (start, end)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                backgroundColor: 'beige',
                borderWidth: 3,
                borderStartColor: 'firebrick',
                borderEndColor: 'black',
                borderTopColor: 'chocolate',
                borderBottomColor: 'blue',
              }}
            />
          </View>
        </TestCase>
      </TestSuite>
      <TestSuite name="contentContainerStyle">
        <TestCase
          modal
          itShould="render scrollview with content container with different border radii (topLeft, topRight, ...) (contentContainerStyle)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderWidth: 3,
                borderColor: 'green',
              }}
              contentContainerStyle={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                overflow: 'hidden',
                borderWidth: 3,
                borderTopLeftRadius: 10,
                borderTopRightRadius: 20,
                borderBottomRightRadius: 30,
                borderBottomLeftRadius: 40,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view with contentContainer with different border widths (left, right, top, bottom) (contentContainerStyle)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderWidth: 3,
                borderColor: 'green',
              }}
              contentContainerStyle={{
                borderColor: 'firebrick',
                backgroundColor: 'beige',
                borderLeftWidth: 3,
                borderWidth: 3,
                borderTopWidth: 6,
                borderRightWidth: 9,
                borderBottomWidth: 12,
              }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="render scroll view contentContainer with different border colors (left, right, top, bottom) (contentContainerStyle)">
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              style={{
                borderWidth: 3,
                borderColor: 'green',
              }}
              contentContainerStyle={{
                backgroundColor: 'beige',
                borderWidth: 3,
                borderLeftColor: 'firebrick',
                borderTopColor: 'chocolate',
                borderRightColor: 'black',
                borderBottomColor: 'blue',
              }}
            />
          </View>
        </TestCase>
      </TestSuite>
      <TestSuite name="scroll indicators / scrollbar">
        <TestCase modal itShould="have persistent scrollbar">
          <View style={styles.wrapperView}>
            <ScrollView persistentScrollbar={true} {...COMMON_PROPS} />
          </View>
        </TestCase>
        <TestCase modal itShould="shows white vertical scroll indicator">
          <View style={styles.wrapperView}>
            <ScrollView {...COMMON_PROPS} indicatorStyle={'white'} />
          </View>
        </TestCase>
        <TestCase modal itShould="show vertical scroll indicator">
          <View style={styles.wrapperView}>
            <ScrollView {...COMMON_PROPS} showsVerticalScrollIndicator={true} />
          </View>
        </TestCase>
        <TestCase modal itShould="hide vertical scroll indicator">
          <View style={styles.wrapperView}>
            <ScrollView
              showsVerticalScrollIndicator={false}
              {...COMMON_PROPS}
            />
          </View>
        </TestCase>
        <TestCase modal itShould="show horizontal scroll indicator">
          <View style={styles.wrapperView}>
            <ScrollView
              showsHorizontalScrollIndicator={true}
              horizontal
              {...COMMON_PROPS}>
              {getScrollViewContentHorizontal({})}
            </ScrollView>
          </View>
        </TestCase>
        <TestCase modal itShould="hide horizontal scroll indicator">
          <View style={styles.wrapperView}>
            <ScrollView
              showsHorizontalScrollIndicator={false}
              horizontal
              {...COMMON_PROPS}>
              {getScrollViewContentHorizontal({})}
            </ScrollView>
          </View>
        </TestCase>
      </TestSuite>
      <TestSuite
        name="sticky headers" /* (sticky headers fail on Android when Fabric is enabled) */
      >
        <TestCase
          itShould="stick item 1 and 4 (stickyHeaderIndices)"
          skip={Platform.OS === 'android'}>
          <View style={styles.wrapperView}>
            <ScrollView stickyHeaderIndices={[0, 3]} nestedScrollEnabled>
              {getScrollViewContent({})}
            </ScrollView>
          </View>
        </TestCase>
        <TestCase
          skip={Platform.OS === 'android'}
          itShould="hide sticked item 1 or 4 when scrolling down (stickyHeaderHiddenOnScroll)">
          <View style={styles.wrapperView}>
            <ScrollView
              stickyHeaderIndices={[0, 3]}
              nestedScrollEnabled
              stickyHeaderHiddenOnScroll>
              {getScrollViewContent({})}
            </ScrollView>
          </View>
        </TestCase>
        <TestCase
          skip={Platform.OS === 'android'}
          itShould="stick item 13 and 20 to the bottom (invertStickyHeaders)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/309
        >
          <View style={styles.wrapperView}>
            <ScrollView
              stickyHeaderIndices={[12, 19]}
              nestedScrollEnabled
              invertStickyHeaders>
              {getScrollViewContent({})}
            </ScrollView>
          </View>
        </TestCase>
        <TestCase itShould="display Text 'custom sticky header' in the place of components 1 and 4 (shouldn't stick) (StickyHeaderComponent)">
          <View style={styles.wrapperView}>
            <ScrollView
              stickyHeaderIndices={[0, 3]}
              nestedScrollEnabled
              StickyHeaderComponent={() => <Text>custom sticky header</Text>}>
              {getScrollViewContent({})}
            </ScrollView>
          </View>
        </TestCase>
      </TestSuite>
      <TestSuite name="pointer events">
        <TestCase
          itShould="call inner and outer view when pressing inner"
          initialState={{ inner: false, outer: false, outerContainer: false }}
          arrange={({ setState, reset }) => {
            return (
              <PointerEventsView
                pointerEventsOuter="auto"
                setState={setState}
                reset={reset}
              />
            );
          }}
          assert={({ expect, state }) => {
            expect(state).to.be.deep.eq({
              inner: true,
              outer: true,
              outerContainer: true,
            });
          }}
        />
        <TestCase
          //it seems there's a bug on Android, which causes pointerEvents to not work correctly for Scrollviews
          skip // https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/424
          itShould="[FAILS on Android/Harmony] call only outer when pressing inner view"
          initialState={{ inner: false, outer: false, outerContainer: true }}
          arrange={({ setState, reset }) => {
            return (
              <PointerEventsView
                pointerEventsOuter="box-only"
                setState={setState}
                reset={reset}
              />
            );
          }}
          assert={({ expect, state }) => {
            expect(state).to.be.deep.eq({
              inner: false,
              outer: true,
              outerContainer: true,
            });
          }}
        />
        <TestCase
          //it seems there's a bug on Android, which causes pointerEvents to not work correctly for Scrollviews
          itShould="[FAILS on Android] call inner and outer only when pressing inner view"
          initialState={{ inner: false, outer: false, outerContainer: false }}
          arrange={({ setState, reset }) => {
            return (
              <PointerEventsView
                disableOuterContainerTouch
                pointerEventsOuter="box-none"
                setState={setState}
                reset={reset}
              />
            );
          }}
          assert={({ expect, state }) => {
            expect(state.inner).to.be.true;
            expect(state.outer).to.be.true;
          }}
        />
        <TestCase
          //it seems there's a bug on Android, which causes pointerEvents to not work correctly for Scrollviews
          itShould="[FAILS on Android] not call inner or outer when pressing inner or outer views"
          initialState={{ inner: false, outer: false, outerContainer: false }}
          arrange={({ setState, reset }) => {
            return (
              <PointerEventsView
                pointerEventsOuter="none"
                setState={setState}
                reset={reset}
              />
            );
          }}
          assert={({ expect, state }) => {
            expect(state).to.be.deep.eq({
              inner: false,
              outer: false,
              outerContainer: true,
            });
          }}
        />
      </TestSuite>
      <TestSuite name="snapTo*">
        <SnapTestCases
          scrollViewProps={{ disableIntervalMomentum: false, horizontal: false }}
        />
      </TestSuite>
      <TestSuite name="disableIntervalMomentum">
        <SnapTestCases
          scrollViewProps={{ disableIntervalMomentum: true, horizontal: false }}
        />
      </TestSuite>
      <TestSuite name="other props">
        <TestCase
          modal
          itShould="scroll should be disabled (it renders with the 5th element at the top)">
          <View style={styles.wrapperView}>
            <ScrollView {...COMMON_PROPS} scrollEnabled={false} />
          </View>
        </TestCase>
        <TestCase modal itShould="display horizontal scroll view">
          <View
            style={{
              width: '100%',
              height: 150,
            }}>
            <ScrollView {...COMMON_PROPS} horizontal={true} />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="display ScrollView with the third view at the top (contentOffset)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/305
        >
          <ContentOffsetTestCase />
        </TestCase>
        <TestCase
          modal
          itShould="scroll when contentOffset property is changed (contentOffset)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/305
        >
          <ToggleContentOffsetTestCase />
        </TestCase>
        <TestCase
          modal
          itShould="toggle backface visibility on button press (the component should become invisible)">
          <BackfaceVisibilityTestCase />
        </TestCase>
        <TestCase
          modal
          skip
          itShould="[FAILS on Harmony/Android] display ScrollView with different contentInsets (contentInset)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/304
        >
          <View style={styles.wrapperView}>
            <ScrollView
              {...COMMON_PROPS}
              contentInset={{ top: 10, right: 20, bottom: 30, left: 40 }}
            />
          </View>
        </TestCase>
        <TestCase
          modal
          skip
          itShould="[FAILS on Harmony/Android] adjust the scrollview when showing keyboard (automaticallyAdjustKeyboardInsets)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/302
        >
          <View style={styles.wrapperView}>
            <TextInput style={styles.textInput} />
            <ScrollView {...COMMON_PROPS} automaticallyAdjustKeyboardInsets />
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="display amount of on drag/momentum begin/end events">
          <MomentumTestCase />
        </TestCase>
        <TestCase
          modal
          itShould="display current contentHeight (onContentSizeChange)">
          <OnContentSizeChangeTestCase />
        </TestCase>
        <TestCase
          modal
          itShould="display onScroll native event throttled every second">
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <ScrollView
                  {...COMMON_PROPS}
                  scrollEventThrottle={1000}
                  onScroll={(e: { nativeEvent: Object }) => {
                    setObject(e.nativeEvent);
                  }}
                />
              );
            }}
          />
        </TestCase>
        <TestCase
          modal
          itShould="the left scrollview should decelerate faster (stops earlier) than the right one (decelarationRate)">
          <View style={[styles.wrapperView, { flexDirection: 'row' }]}>
            <ScrollView {...COMMON_PROPS} decelerationRate={0.8} />
            <ScrollView {...COMMON_PROPS} decelerationRate={0.999} />
          </View>
        </TestCase>
        <TestCase
          modal
          skip
          itShould="the left scrollview should dismiss the keyboard on scroll and the right one shouldn't (keyboardDismissMode)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/310
        >
          <View>
            <TextInput style={styles.textInput} />
            <View style={[styles.wrapperView, { flexDirection: 'row' }]}>
              <ScrollView {...COMMON_PROPS} keyboardDismissMode={'on-drag'}>
                {getScrollViewContent({})}
              </ScrollView>
              <ScrollView {...COMMON_PROPS} keyboardDismissMode={'none'}>
                {getScrollViewContent({})}
              </ScrollView>
            </View>
          </View>
        </TestCase>
        <TestCase
          modal
          skip
          itShould="[FAILS on Harmony/ Android Emulator] the left scrollview should dismiss the keyboard on tap and the right one shouldn't (keyboardShouldPersistTaps)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/311
        >
          <View>
            <TextInput style={styles.textInput} />
            <View style={[styles.wrapperView, { flexDirection: 'row' }]}>
              <ScrollView
                {...COMMON_PROPS}
                keyboardShouldPersistTaps={'never'}
              />
              <ScrollView
                {...COMMON_PROPS}
                keyboardShouldPersistTaps={'always'}
              />
            </View>
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="the left scrollview should bounce (briefly scroll beyond the content to show the view below and then come back to top/bottom accordingly)">
          <View style={[styles.wrapperView, { flexDirection: 'row' }]}>
            <ScrollView {...COMMON_PROPS} />
            <ScrollView {...COMMON_PROPS} bounces={false} />
          </View>
        </TestCase>
        <TestCase
          modal
          skip
          itShould="[FAILS on Harmony/Android] scroll outside of the content when pressing the button (scrollToOverflowEnabled)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/315
        >
          <ScrollToOverflowEnabledTestCase />
        </TestCase>
        <TestCase
          modal
          skip
          itShould="the left scrollview should allow for nested scroll while the right one shouldn't (nestedScrollEnabled)"
        //https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/312
        >
          <View
            style={[
              styles.wrapperView,
              { flexDirection: 'row', alignContent: 'space-between' },
            ]}>
            <ScrollView {...COMMON_PROPS}>
              <ScrollView
                nestedScrollEnabled
                style={{
                  width: '70%',
                  height: 200,
                  borderColor: 'firebrick',
                  borderWidth: 2,
                }}>
                {getScrollViewContent({
                  style: { backgroundColor: 'green' },
                  amountOfChildren: 5,
                })}
              </ScrollView>
              {getScrollViewContent({})}
            </ScrollView>
            <ScrollView {...COMMON_PROPS}>
              <ScrollView
                nestedScrollEnabled={false}
                style={{
                  width: '70%',
                  height: 200,
                  borderColor: 'firebrick',
                  borderWidth: 2,
                }}>
                {getScrollViewContent({
                  style: { backgroundColor: 'green' },
                  amountOfChildren: 5,
                })}
              </ScrollView>
              {getScrollViewContent({})}
            </ScrollView>
          </View>
        </TestCase>
        <TestCase
          modal
          itShould="scroll down on the btn press, but prevent scrolling by dragging (scrollEnabled)">
          <ScrollEnabledTestCase />
        </TestCase>
      </TestSuite>
      <TestCase modal itShould="flash scroll indicators">
        <FlashIndicatorsTest />
      </TestCase>
      <TestCase
        modal
        itShould="maintain scroll position when adding/removing elements">
        <AppendingList />
      </TestCase>
      <TestCase
        modal
        skip // https://gl.swmansion.com/rnoh/react-native-harmony/-/issues/498
        itShould="fill the remaining space of scroll view with yellow color but the element inside scroll view remains transparent">
        <ScrollViewEndFillColorTest />
      </TestCase>
    </TestSuite>
  );
}

function SnapTestCases(props: { scrollViewProps: ScrollViewProps }) {
  return (
    <>
      <TestCase
        modal
        itShould="not snap after item 6 when snapToEnd is set to false">
        <ScrollViewComparator
          scrollViewLength={ITEM_HEIGHT * 5}
          commonProps={{
            ...props.scrollViewProps,
            snapToOffsets: [ITEM_HEIGHT * 5],
            children: getScrollViewContent({ amountOfChildren: 25 }),
          }}
          lhsProps={{ snapToEnd: true }}
          rhsProps={{ snapToEnd: false }}
        />
      </TestCase>
      <TestCase
        modal
        itShould="not snap before item 6 when snapToStart is set to false">
        <ScrollViewComparator
          scrollViewLength={ITEM_HEIGHT * 5}
          commonProps={{
            ...props.scrollViewProps,
            snapToOffsets: [ITEM_HEIGHT * 5],
            children: getScrollViewContent({ amountOfChildren: 25 }),
          }}
          lhsProps={{ snapToStart: true }}
          rhsProps={{ snapToStart: false }}
        />
      </TestCase>
      <TestCase modal itShould="snap to page">
        <ScrollViewComparator
          scrollViewLength={ITEM_HEIGHT * 5}
          commonProps={{
            ...props.scrollViewProps,
            children: getScrollViewContent({ amountOfChildren: 25 }),
          }}
          lhsProps={{ pagingEnabled: false }}
          rhsProps={{ pagingEnabled: true }}
        />
      </TestCase>
      <TestCase modal itShould="snap to item 1, 3, 5, 7, 9, ...">
        <ScrollViewComparator
          scrollViewLength={ITEM_HEIGHT * 5}
          commonProps={{
            ...props.scrollViewProps,
            children: getScrollViewContent({ amountOfChildren: 25 }),
          }}
          lhsProps={{}}
          rhsProps={{ snapToInterval: ITEM_HEIGHT * 2 }}
        />
      </TestCase>
      <TestCase modal itShould="snap to item 2, 3, 7, and 11 and 21">
        <ScrollViewComparator
          scrollViewLength={ITEM_HEIGHT * 5}
          commonProps={{
            ...props.scrollViewProps,
            children: getScrollViewContent({ amountOfChildren: 25 }),
          }}
          lhsProps={{}}
          rhsProps={{
            snapToOffsets: [
              ITEM_HEIGHT,
              ITEM_HEIGHT * 2,
              ITEM_HEIGHT * 6,
              ITEM_HEIGHT * 10,
            ],
          }}
        />
      </TestCase>
      <TestSuite name="snapToAlignment">
        <TestCase modal itShould="snap to item {lhs: start, rhs: center}">
          <ScrollViewComparator
            scrollViewLength={ITEM_HEIGHT * 1.5}
            commonProps={{
              ...props.scrollViewProps,
              children: getScrollViewContent({ amountOfChildren: 25 }),
              snapToInterval: ITEM_HEIGHT,
            }}
            lhsProps={{ snapToAlignment: 'start' }}
            rhsProps={{ snapToAlignment: 'center' }}
          />
        </TestCase>
        <TestCase modal itShould="snap to item {lhs: start, rhs: end}">
          <ScrollViewComparator
            scrollViewLength={ITEM_HEIGHT * 1.5}
            commonProps={{
              ...props.scrollViewProps,
              children: getScrollViewContent({ amountOfChildren: 25 }),
              snapToInterval: ITEM_HEIGHT,
            }}
            lhsProps={{ snapToAlignment: 'start' }}
            rhsProps={{ snapToAlignment: 'end' }}
          />
        </TestCase>
      </TestSuite>
    </>
  );
}
const Item = (props: { label: string; mode: 'dark' | 'light' }) => {
  const stylesheet = StyleSheet.create({
    dark: {
      backgroundColor: '#47443D',
      color: 'white',
    },
    light: {
      backgroundColor: '#D9D0BB',
      color: 'black',
    },
    item: {
      height: 100,
    },
  });
  return (
    <View
      style={[
        props.mode === 'dark' ? stylesheet.dark : stylesheet.light,
        stylesheet.item,
      ]}>
      <Text>{props.label}</Text>
    </View>
  );
};

const ITEMS = Array.from({ length: 20 }, (_, index) => (
  <Item
    label={`Item${index}`}
    key={`item${index}`}
    mode={index % 2 ? 'light' : 'dark'}
  />
));
let itemCount = 20;
const AppendingList = () => {
  const [items, setItems] = useState<React.JSX.Element[]>(ITEMS);
  const styles = StyleSheet.create({
    scrollView: {
      width: 300,
      marginVertical: 50,
      height: 500,
    },
    row: {
      flexDirection: 'row',
    },
  });
  return (
    <View>
      <ScrollView
        automaticallyAdjustContentInsets={false}
        maintainVisibleContentPosition={{
          minIndexForVisible: 0,
          autoscrollToTopThreshold: 10,
        }}
        nestedScrollEnabled
        style={styles.scrollView}>
        {items.map((value, _index, _array) => React.cloneElement(value))}
      </ScrollView>
      <View style={styles.row}>
        <Button
          label="Add to top"
          onPress={() => {
            setItems(prev => {
              const idx = itemCount++;
              return [
                <Item
                  label={`added Item ${idx}`}
                  key={`item${idx}`}
                  mode={idx % 2 ? 'light' : 'dark'}
                />,
              ].concat(prev);
            });
          }}
        />
        <Button
          label="Remove top"
          onPress={() => {
            setItems(prev => prev.slice(1));
          }}
        />
      </View>
      <View style={styles.row}>
        <Button
          label="Add to end"
          onPress={() => {
            setItems(prev => {
              const idx = itemCount++;
              return prev.concat([
                <Item
                  label={`added Item ${idx}`}
                  key={`item${idx}`}
                  mode={idx % 2 ? 'light' : 'dark'}
                />,
              ]);
            });
          }}
        />
        <Button
          label="Remove end"
          onPress={() => {
            setItems(prev => prev.slice(0, -1));
          }}
        />
      </View>
    </View>
  );
};

function MomentumTestCase() {
  const [hasDragBegan, setHasDragBegan] = useState(0);
  const [hasDragEnded, setHasDragEnded] = useState(0);
  const [hasMomentumBegan, setHasMomentumBegan] = useState(0);
  const [hasMomentumEnded, setHasMomentumEnded] = useState(0);

  return (
    <>
      <Button
        label="Reset"
        onPress={() => {
          setHasDragBegan(0);
          setHasDragEnded(0);
          setHasMomentumBegan(0);
          setHasMomentumEnded(0);
        }}
      />
      <View style={{ backgroundColor: 'white', width: '100%' }}>
        <Text style={{ height: 16 }}>hasMomentumBegan: {hasMomentumBegan}</Text>
        <Text style={{ height: 16 }}>hasMomentumEnded: {hasMomentumEnded}</Text>
        <Text style={{ height: 16 }}>hasDragBegan: {hasDragBegan}</Text>
        <Text style={{ height: 16 }}>hasDragEnded: {hasDragEnded}</Text>
      </View>

      <View style={{ width: 200, height: 200 }}>
        <ScrollView
          onScrollBeginDrag={() => {
            setHasDragBegan(p => p + 1);
          }}
          onScrollEndDrag={() => {
            setHasDragEnded(p => p + 1);
          }}
          onMomentumScrollBegin={() => {
            setHasMomentumBegan(p => p + 1);
          }}
          onMomentumScrollEnd={() => {
            setHasMomentumEnded(p => p + 1);
          }}>
          <View style={{ backgroundColor: 'red', width: '100%', height: 150 }} />
          <View style={{ backgroundColor: 'blue', width: '100%', height: 150 }} />
          <View
            style={{ backgroundColor: 'green', width: '100%', height: 150 }}
          />
          <View style={{ backgroundColor: 'red', width: '100%', height: 150 }} />
        </ScrollView>
      </View>
    </>
  );
}

function ScrollEnabledTestCase() {
  const scrollRef = React.useRef<ScrollView>(null);
  return (
    <View style={styles.wrapperView}>
      <Button
        label={'Scroll To offset y 150'}
        onPress={() => {
          scrollRef.current?.scrollTo({ x: 0, y: 150, animated: false });
        }}
      />
      <ScrollView style={{ flex: 1 }} scrollEnabled={false} ref={scrollRef}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}

function FlashIndicatorsTest() {
  const scrollRef = React.useRef<ScrollView>(null);
  return (
    <View style={styles.wrapperView}>
      <Button
        label={'Flash Indicators'}
        onPress={() => {
          scrollRef.current?.flashScrollIndicators();
        }}
      />
      <ScrollView
        style={{ flex: 1 }}
        scrollEnabled={true}
        showsVerticalScrollIndicator={false}
        ref={scrollRef}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}

function ScrollToOverflowEnabledTestCase() {
  const ref = useRef<ScrollView>(null);
  return (
    <View style={styles.wrapperView}>
      <Button
        label={'Scroll outside of the content'}
        onPress={() => {
          ref.current?.scrollTo({ x: 0, y: -60, animated: true });
        }}
      />
      <ScrollView scrollToOverflowEnabled={true} ref={ref}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}
function OnContentSizeChangeTestCase() {
  const [amountOfChildren, setAmountOfChildren] = useState(3);
  return (
    <ObjectDisplayer
      renderContent={setObject => {
        return (
          <View style={{ width: '100%', height: '70%' }}>
            <Button
              label={'Add one more item'}
              onPress={() => {
                setAmountOfChildren(amountOfChildren + 1);
              }}
            />
            <ScrollView
              onContentSizeChange={(_, contentHeight) => {
                setObject({ contentHeight });
              }}>
              {getScrollViewContent({ amountOfChildren: amountOfChildren })}
            </ScrollView>
          </View>
        );
      }}
    />
  );
}

function ContentOffsetTestCase() {
  return (
    <View style={styles.wrapperView}>
      <ScrollView
        style={{
          ...styles.wrapperView,
        }}
        contentOffset={{ x: 0, y: 100 }}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}

function ToggleContentOffsetTestCase() {
  const [contentOffset, setContentOffset] = useState(100);
  useEffect(() => {
    const id = setInterval(() => {
      setContentOffset(prev => (prev + 50) % 200);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  }, []);

  return (
    <View style={styles.wrapperView}>
      <ScrollView
        style={{
          ...styles.wrapperView,
        }}
        contentOffset={{ x: 0, y: contentOffset }}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}

function BackfaceVisibilityTestCase() {
  const [backfaceVisibility, setBackfaceVisibility] = useState(true);

  return (
    <View style={styles.wrapperView}>
      <Button
        label={`Make backface ${backfaceVisibility ? 'invisible' : 'visible'}`}
        onPress={() => {
          setBackfaceVisibility(!backfaceVisibility);
        }}
      />
      <ScrollView
        style={{
          ...styles.wrapperView,
          backfaceVisibility: backfaceVisibility ? 'visible' : 'hidden',
          transform: [{ rotateX: '180deg' }],
        }}>
        {getScrollViewContent({})}
      </ScrollView>
    </View>
  );
}

interface ScrollViewContentProps {
  style?: StyleProp<ViewStyle>;
  amountOfChildren?: number;
  onTouchEnd?: (event: GestureResponderEvent) => void;
  pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto' | undefined;
}

const ITEM_HEIGHT = 50;

// using this as a component breaks sticky headers because react native sees it then as a single component
function getScrollViewContent({
  style,
  amountOfChildren = 20,
  onTouchEnd,
  pointerEvents,
}: ScrollViewContentProps) {
  return new Array(amountOfChildren).fill(0).map((_, idx) => {
    return (
      <View
        key={idx}
        style={[
          {
            width: '100%',
            height: 50,
            backgroundColor: idx % 2 ? 'pink' : 'beige',
            justifyContent: 'center',
          },
          style,
        ]}
        pointerEvents={pointerEvents}
        onTouchEnd={onTouchEnd}>
        <Text style={{ textAlign: 'center', height: 15 }}> {idx + 1}</Text>
      </View>
    );
  });
}

function getScrollViewContentHorizontal({
  style,
  amountOfChildren = 20,
}: ScrollViewContentProps) {
  return new Array(amountOfChildren).fill(0).map((_, idx) => {
    return (
      <View
        key={idx}
        style={[
          {
            width: 50,
            height: '100%',
            backgroundColor: idx % 2 ? 'pink' : 'beige',
            justifyContent: 'center',
          },
          style,
        ]}>
        <Text style={{ textAlign: 'center', height: 15 }}> {idx + 1}</Text>
      </View>
    );
  });
}
function PointerEventsView(props: {
  disableOuterContainerTouch?: boolean;
  pointerEventsOuter?: 'box-none' | 'none' | 'box-only' | 'auto';
  pointerEventsInner?: 'box-none' | 'none' | 'box-only' | 'auto';
  setState: React.Dispatch<
    React.SetStateAction<{
      inner: boolean;
      outer: boolean;
      outerContainer: boolean;
    }>
  >;
  reset: () => void;
}) {
  return (
    <View style={{ height: 100, width: '100%', flexDirection: 'row' }}>
      <View
        style={{ backgroundColor: 'red' }}
        onTouchEnd={
          props.disableOuterContainerTouch
            ? undefined
            : () => {
              props.setState(prev => ({ ...prev, outerContainer: true }));
            }
        }>
        <ScrollView
          nestedScrollEnabled
          style={{
            height: 100,
            width: 100,
            backgroundColor: 'green',
            padding: 20,
          }}
          pointerEvents={props.pointerEventsOuter}
          onTouchEnd={() => {
            props.setState(prev => ({ ...prev, outer: true }));
          }}>
          {getScrollViewContent({
            amountOfChildren: 3,
            onTouchEnd: () => {
              props.setState(prev => ({ ...prev, inner: true }));
            },
            pointerEvents: props.pointerEventsInner,
          })}
        </ScrollView>
      </View>
      <Button label="reset" onPress={props.reset} />
    </View>
  );
}

function ScrollViewComparator({
  scrollViewLength,
  commonProps,
  lhsProps,
  rhsProps,
}: {
  scrollViewLength: number;
  commonProps: ScrollViewProps;
  lhsProps: ScrollViewProps;
  rhsProps: ScrollViewProps;
}) {
  return (
    <View style={{ width: '100%' }}>
      <View
        style={{ flexDirection: 'row', width: '100%', alignItems: 'flex-end' }}>
        <View style={{ flex: 1 }}>
          <Text style={{ fontSize: 12 }}>{JSON.stringify(lhsProps)}</Text>
          <View style={{ height: scrollViewLength }}>
            <ScrollView
              style={{ flex: 1, height: scrollViewLength }}
              {...{ ...commonProps, ...lhsProps }}
            />
          </View>
        </View>
        <View style={{ width: 4, height: '100%', backgroundColor: 'gray' }} />
        <View style={{ flex: 1 }}>
          <Text style={{ fontSize: 12 }}>{JSON.stringify(rhsProps)}</Text>
          <View style={{ height: scrollViewLength }}>
            <ScrollView {...{ ...commonProps, ...rhsProps }} style={{ flex: 1 }} />
          </View>
        </View>
      </View>
    </View>
  );
}

function ScrollViewEndFillColorTest() {
  return (
    <View
      style={{
        backgroundColor: 'pink',
        width: '100%',
        justifyContent: 'center',
      }}>
      <View
        style={{
          height: 400,
          marginTop: 50,
          marginBottom: 50,
        }}>
        <ScrollView endFillColor={'yellow'}>
          <View
            style={{
              height: 100,
              borderWidth: 1,
              borderColor: '#000',
              padding: 10,
            }}>
            <Text>Content smaller than scroll view</Text>
          </View>
        </ScrollView>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  wrapperView: {
    height: 300,
    width: '60%',
  },
  button: {
    width: 160,
    height: 36,
    backgroundColor: 'hsl(190, 50%, 70%)',
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 8,
  },
  textInput: {
    borderWidth: 1,
    borderColor: 'silver',
    backgroundColor: '#444',
    height: 32, // hack
    borderRadius: 8,
    marginTop: 8,
    padding: 8,
    fontSize: 16,
    color: 'white',
  },
});