/**
 * Copyright (c) 2026 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 {View, ScrollView, Text, StyleSheet, RefreshControl} from 'react-native';
import React, {createRef, useState} from 'react';
import {Button} from '../../components';
import {StylesTest} from './StylesTest';
import {ContentContainerStyleTest} from './ContentContainerStyleTest';
import {ScrollBarsTest} from './ScrollBarsTest';
import {StickyHeadersTest} from './StickyHeadersTest';
import {PointerEventsTest} from './PointerEventsTest';
import {SnapTest} from './SnapTest';
import {MomentumCallbacksTest} from './MomentumCallbacksTest';
import {KeyboardTest} from './KeyboardTest';
import {MiscPropsTest} from './MiscPropsTest';
import {ScrollToTest} from './ScrollToTest';
import {CenterContentTest} from './CenterContentTest';
import RemoveClippedTest from './RemoveClippedTest';
import {OverScrollTest} from './OverScrollTest';
import {FlingSpeedLimitTest} from './FlingSpeedLimitTest';
import {TestCase} from '../../components';
import {TestSuite} from '@rnoh/testerino';
import {useEnvironment} from '../../contexts';

export function ScrollViewTest() {
  const {
    env: {driver},
  } = useEnvironment();

  return (
    <TestSuite name="ScrollView">
      <TestSuite name="onScroll">
        <TestCase.Automated
          skip={{android: true, harmony: !driver}}
          tags={['sequential']}
          itShould="receive negative offsetY when refreshControl is provided"
          initialState={{
            ref: createRef<ScrollView>(),
            offsetsY: [] as number[],
            isRefreshing: false,
            events: [] as (
              | 'onMomentumScrollBegin'
              | 'onMomentumScrollEnd'
              | 'onScrollBeginDrag'
              | 'onScrollEndDrag'
            )[],
          }}
          arrange={({setState, state}) => {
            const pushEventName = (
              eventName: (typeof state)['events'][number],
            ) => {
              setState(current => ({
                ...current,
                events: [...current.events, eventName],
              }));
            };
            return (
              <>
                <ScrollView
                  ref={state.ref}
                  style={{height: 400}}
                  onScroll={e => {
                    const offsetY = e.nativeEvent.contentOffset.y;
                    setState(current => ({
                      ...current,
                      offsetsY: [...current.offsetsY, offsetY],
                    }));
                  }}
                  onMomentumScrollBegin={() => {
                    pushEventName('onMomentumScrollBegin');
                  }}
                  onMomentumScrollEnd={() => {
                    pushEventName('onMomentumScrollEnd');
                  }}
                  onScrollBeginDrag={() => {
                    pushEventName('onScrollBeginDrag');
                  }}
                  onScrollEndDrag={() => {
                    pushEventName('onScrollEndDrag');
                  }}
                  refreshControl={
                    <RefreshControl
                      refreshing={state.isRefreshing}
                      onRefresh={() => {
                        setState(current => ({...current, isRefreshing: true}));
                        setTimeout(() => {
                          setState(current => ({
                            ...current,
                            isRefreshing: false,
                          }));
                        }, 1000);
                      }}
                    />
                  }>
                  {[1, 2, 3, 4, 5].map(value => {
                    return (
                      <View
                        key={value}
                        style={{
                          width: '100%',
                          height: 64,
                          backgroundColor: 'pink',
                          marginBottom: 64,
                        }}
                      />
                    );
                  })}
                </ScrollView>
              </>
            );
          }}
          act={async ({state, done}) => {
            await driver?.swipe({
              ref: state.ref,
              fromOffset: {x: 0, y: 0},
              toOffset: {x: 0, y: 100},
              speed: 5000,
            });
            await new Promise(resolve => setTimeout(resolve, 2500));
            done();
          }}
          assert={({expect, state}) => {
            expect(
              state.offsetsY.some(offsetY => offsetY < -50),
              'Expected some offsetY to be significantly negative',
            ).to.be.true;
            expect(
              state.offsetsY.filter(offsetY => offsetY < 0).length,
              'Expected few onScroll events with a negative offset',
            ).to.be.greaterThan(5);
            expect(state.events).to.deep.equal([
              'onScrollBeginDrag', // User initiates pull-to-refresh
              'onScrollEndDrag', // User releases pointer
              'onMomentumScrollBegin', // Transitions from over-drag state to the Refreshing state
              'onMomentumScrollEnd', // Refreshing state began
              'onMomentumScrollBegin', // Transitions back to normal
              'onMomentumScrollEnd', // Done
            ]);
          }}
        />
      </TestSuite>
      <TestSuite name="scrolling">
        <TestCase.Automated
          skip={{android: true, harmony: !driver}}
          tags={['sequential']}
          itShould="scroll when padding is set"
          initialState={{
            scrollViewRef: createRef<ScrollView>(),
            targetRef: createRef<View>(),
            hasPressedTarget: false,
          }}
          arrange={({state, setState, done}) => {
            return (
              <ScrollView
                ref={state.scrollViewRef}
                style={{
                  paddingLeft: 6,
                  height: 256,
                  width: 256,
                }}>
                <View
                  style={{
                    width: 256,
                    height: 256,
                    backgroundColor: 'gray',
                  }}
                />
                <View
                  onTouchEnd={() => {
                    setState(prev => ({...prev, hasPressedTarget: true}));
                    done();
                  }}
                  ref={state.targetRef}
                  style={{
                    width: 256,
                    height: 64,
                    backgroundColor: 'red',
                  }}
                />
              </ScrollView>
            );
          }}
          act={async ({state}) => {
            await driver?.swipe({
              ref: state.scrollViewRef,
              fromOffset: {x: 0, y: 0},
              toOffset: {x: 0, y: -100},
              speed: 5000,
            });
            await new Promise(resolve => setTimeout(resolve, 1000));
            await driver?.click({ref: state.targetRef});
          }}
          assert={({expect, state}) => {
            expect(state.hasPressedTarget).to.be.true;
          }}
        />
      </TestSuite>
      <StylesTest />
      <ContentContainerStyleTest />
      <ScrollBarsTest />
      <StickyHeadersTest />
      <PointerEventsTest />
      <RemoveClippedTest />
      <SnapTest />
      <MomentumCallbacksTest />
      <KeyboardTest />
      <ScrollToTest />
      <MiscPropsTest />
      <CenterContentTest />
      <OverScrollTest />
      <FlingSpeedLimitTest />
      <TestCase.Example itShould="expose nativeEvent.velocity in ScrollView events">
        <VelocityPreview />
      </TestCase.Example>
      <TestCase.Example
        modal
        itShould="maintain scroll position when adding/removing elements">
        <AppendingList />
      </TestCase.Example>
      <TestCase.Example
        modal
        itShould="pick partially visible element as scroll anchor when growing the content size">
        <ScrollAnchorsTest />
      </TestCase.Example>
      <TestCase.Example
        modal
        skip={{harmony: true, android: false}} // 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.Example>
    </TestSuite>
  );
}

const VelocityPreview = () => {
  const [label, setLabel] = useState('-');
  const onAnyScroll = (e: any) => {
    const v = (e.nativeEvent as any)?.velocity;
    if (v && typeof v.x === 'number' && typeof v.y === 'number') {
      setLabel(`${v.x.toFixed(2)}, ${v.y.toFixed(2)}`);
    }
  };
  return (
    <View style={{padding: 8}}>
      <Text>{`velocity: ${label}`}</Text>
      <ScrollView
        style={{height: 140}}
        onScrollBeginDrag={onAnyScroll}
        onScroll={onAnyScroll}
        onScrollEndDrag={onAnyScroll}
        onMomentumScrollBegin={onAnyScroll}
        onMomentumScrollEnd={onAnyScroll}
        scrollEventThrottle={16}
      />
    </View>
  );
};

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>
  );
};

const ScrollAnchorsTest = () => {
  const [height, setHeight] = useState(400);
  const grow = () => {
    setHeight(prev => prev + 50);
  };

  return (
    <View
      style={{
        height: 500,
      }}>
      <Button onPress={grow} label="Grow" />
      <Button
        onPress={() => {
          setHeight(400);
        }}
        label="Reset"
      />
      <ScrollView
        style={{borderColor: 'black', borderWidth: 3}}
        contentContainerStyle={{alignItems: 'center'}}
        maintainVisibleContentPosition={{minIndexForVisible: 0}}>
        <View
          style={{
            height: height,
            width: '80%',
            backgroundColor: 'green',
            borderColor: 'black',
            borderWidth: 3,
          }}
          key="green"
        />
        <View
          style={{
            height: height,
            width: '80%',
            backgroundColor: 'blue',
            borderColor: 'black',
            borderWidth: 3,
          }}
          key="blue"
        />
      </ScrollView>
    </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>
  );
}