/**
 * 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 React, { useEffect, useMemo, useRef, useState } from 'react';
import {
  View,
  SectionList,
  StyleSheet,
  Text,
  SectionListProps,
  RefreshControl,
  Platform,
  ViewabilityConfig,
} from 'react-native';
import { TestCase, TestSuite } from '@rnoh/testerino';
import { Button, Modal, ObjectDisplayer } from '../components';

interface SectionData {
  id: string;
  title: string;
  data: string[];
}

const DATA: SectionData[] = [
  {
    id: '0',
    title: 'Main dishes',
    data: ['Pizza', 'Burger', 'Risotto'],
  },
  {
    id: '1',
    title: 'Sides',
    data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
  },
  {
    id: '2',
    title: 'Drinks',
    data: ['Water', 'Coke', 'Beer'],
  },
  {
    id: '3',
    title: 'Desserts',
    data: ['Cheese Cake', 'Ice Cream'],
  },
];

const commonProps = {
  style: { width: 256 },
  sections: DATA,
  keyExtractor: (item, _index) => item.id,
  renderSectionHeader: ({ section }) => (
    <Text style={styles.title}>{section.title}</Text>
  ),
  renderItem: ({ item }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item}</Text>
    </View>
  ),
} satisfies SectionListProps<any>;

export const SectionListTest = () => {
  return (
    <TestSuite name="SectionList">
      <TestCase itShould="display items in the SectionList">
        <Modal>
          <View style={styles.container}>
            <SectionList
              sections={DATA}
              keyExtractor={(item, index) => item + index}
              renderItem={({ item }) => (
                <View style={styles.item}>
                  <Text style={styles.title}>{item}</Text>
                </View>
              )}
              renderSectionHeader={({ section: { title } }) => (
                <Text style={styles.title}>{title}</Text>
              )}
            />
          </View>
        </Modal>
      </TestCase>
      <TestCase itShould="display an array of visible items">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  viewabilityConfig={{ viewAreaCoveragePercentThreshold: 100 }}
                  onViewableItemsChanged={item => {
                    setObject(item.viewableItems.map(i => i.item));
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="render no more than 2 new items per batch when scrolling down">
        <Modal>
          <SectionList
            {...commonProps}
            windowSize={2}
            renderItem={({ item }) => {
              return (
                <DelayedDisplayer
                  delayInMs={1000}
                  renderContent={() => {
                    return (
                      <View style={styles.item}>
                        <Text style={styles.title}>{item}</Text>
                      </View>
                    );
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="display nativeEvent when onMomentumScrollBegin">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  onMomentumScrollBegin={e => {
                    setObject({ nativeEvent: e.nativeEvent });
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="display nativeEvent when onMomentumScrollEnd">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  onMomentumScrollEnd={e => {
                    setObject({ nativeEvent: e.nativeEvent });
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="display event sent to by onScrollToIndexFailed when pressing the button before scrolling">
        <Modal>
          <ScrollToIndexFailureTestCase />
        </Modal>
      </TestCase>
      {/* sticky headers seems to work on Android when App.tsx was replaced with content of this test */}
      <TestCase
        itShould="stick section headers (fails on Android when fabric is enabled)"
        skip={Platform.OS === 'android'}>
        <Modal>
          <SectionList {...commonProps} stickySectionHeadersEnabled />
        </Modal>
      </TestCase>
      <TestCase itShould="support viewOffset when scrolling to location">
        <Modal>
          <ScrollToLocationOffset />
        </Modal>
      </TestCase>
      <TestCase itShould="show vertical scroll indicator">
        <Modal>
          <SectionList {...commonProps} showsVerticalScrollIndicator={true} />
        </Modal>
      </TestCase>
      <TestCase itShould="hide vertical scroll indicator">
        <Modal>
          <SectionList {...commonProps} showsVerticalScrollIndicator={false} />
        </Modal>
      </TestCase>
      <TestCase itShould="show horizontal scroll indicator">
        <Modal>
          <View style={{ width: 200, height: '100%' }}>
            <SectionList
              {...commonProps}
              showsHorizontalScrollIndicator={true}
              horizontal
            />
          </View>
        </Modal>
      </TestCase>
      <TestCase itShould="hide horizontal scroll indicator">
        <Modal>
          <View style={{ width: 200, height: '100%' }}>
            <SectionList
              {...commonProps}
              showsHorizontalScrollIndicator={false}
              horizontal
            />
          </View>
        </Modal>
      </TestCase>
      <TestCase itShould="[SKIP] display overscroll effect">
        <Modal>
          {/* On Android this settings enables stretching the ScrollView content. On OpenHarmony `bounces` prop can be used instead. */}
          <SectionList {...commonProps} overScrollMode="always" />
        </Modal>
      </TestCase>
      <TestCase itShould="render custom RefreshControl on pull to refresh">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  refreshControl={
                    // only RefreshControl can be provided
                    <RefreshControl
                      onRefresh={() => {
                        setObject({ onRefreshCalled: true });
                      }}
                      refreshing={false}
                      tintColor={'red'} // iOS. It's unknown how to set the color of the refreshing widget in ArkUI.
                      colors={['red', 'green']} // Android
                    />
                  }
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="render standard RefreshControl on pull to refresh">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  onRefresh={() => {
                    setObject({ onRefreshCalled: true });
                  }}
                  refreshing={false}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase
        itShould="display onScroll native event throttled every second"
        skip={Platform.select({
          android: 'RN bug',
          harmony: "doesn't work on Android",
        })}>
        {/* https://github.com/facebook/react-native/issues/18441 */}
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  scrollEventThrottle={1000}
                  onScroll={e => {
                    setObject(e.nativeEvent);
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="allow scrolling beneath the content due to large lengths returned in getItemLayout">
        <Modal>
          <SectionList
            {...commonProps}
            getItemLayout={(data, index) => {
              const ITEM_HEIGHT = 1000;
              return {
                length: ITEM_HEIGHT,
                offset: ITEM_HEIGHT * index,
                index,
              };
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="display onEndReached event when scroll reached bottom">
        <Modal>
          <ObjectDisplayer
            renderContent={setObject => {
              return (
                <SectionList
                  {...commonProps}
                  onEndReached={e => {
                    setObject(e);
                  }}
                />
              );
            }}
          />
        </Modal>
      </TestCase>
      <TestCase itShould="click on 'Record interaction' button changes the first three items background color to blue">
        <Modal contentContainerStyle={{ width: '85%' }}>
          <SectionListRecordInteractionTest />
        </Modal>
      </TestCase>
    </TestSuite>
  );
};

function ScrollToIndexFailureTestCase() {
  const ref = useRef<SectionList>(null);

  return (
    <>
      <Button
        label="Scroll to index"
        onPress={() => {
          if (ref.current) {
            ref.current.scrollToLocation({
              sectionIndex: 1,
              itemIndex: 10,
              animated: true,
            });
          }
        }}
      />
      <ObjectDisplayer
        renderContent={setObject => {
          return (
            <SectionList
              ref={ref}
              {...commonProps}
              windowSize={1}
              onScrollToIndexFailed={info => {
                setObject(info);
              }}
            />
          );
        }}
      />
    </>
  );
}

function ScrollToLocationOffset() {
  const ref = useRef<SectionList>(null);

  return (
    <>
      <Button
        label="Scroll to onion rings"
        onPress={() => {
          ref.current?.scrollToLocation({
            itemIndex: 1,
            sectionIndex: 1,
            viewOffset: -100,
          });
        }}
      />
      <SectionList ref={ref} {...commonProps} />
    </>
  );
}

function DelayedDisplayer(props: {
  renderContent: () => any;
  delayInMs: number;
}) {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const timeout = setTimeout(() => {
      setIsVisible(true);
    }, props.delayInMs);
    return () => {
      clearTimeout(timeout);
    };
  }, []);

  return <>{isVisible ? props.renderContent() : null}</>;
}

export interface ViewTokenItem<TItem> {
  item: TItem;
  key: string;
  index: number | null;
  isViewable: boolean;
  section?: any | undefined;
}

type OnViewableItemsChangedType<TItem> = {
  viewableItems: Array<ViewTokenItem<TItem>>;
  changed: Array<ViewTokenItem<TItem>>;
};

function SectionListRecordInteractionTest() {
  const [visibleItems, setVisibleItems] = useState<string[]>([]);

  const ref = useRef<SectionList>(null);

  const defaultViewabilityConfig: ViewabilityConfig = useMemo(() => {
    return {
      waitForInteraction: true,
      minimumViewTime: 100,
      itemVisiblePercentThreshold: 70,
    };
  }, []);

  const handleOnPress = () => {
    if (ref.current) {
      ref.current.recordInteraction();
    }
  };

  const onViewableItemsChanged = ({
    viewableItems,
  }: OnViewableItemsChangedType<string>) => {
    const newVisibleItems = viewableItems
      .filter(viewableItem => viewableItem.index != null)
      .map(viewableItem => viewableItem.item);

    setVisibleItems(newVisibleItems);
  };

  return (
    <>
      <View style={{ marginBottom: 10 }}>
        <Button label="Record interaction" onPress={handleOnPress} />
        <Text style={{ padding: 10 }}>
          Visible Items are: {JSON.stringify(visibleItems)}
        </Text>
      </View>
      <SectionList
        ref={ref}
        sections={DATA}
        scrollEnabled={false}
        keyExtractor={(_, index) => String(index)}
        renderSectionHeader={({ section }) => (
          <Text style={styles.title}>{section.title}</Text>
        )}
        renderItem={({ item }) => (
          <View
            style={[
              {
                height: 200,
                backgroundColor: 'lightblue',
                marginBottom: 10,
              },
              visibleItems.includes(item) && { backgroundColor: 'blue' },
            ]}>
            <Text
              style={{
                fontSize: 32,
                height: 200,
                textAlign: 'center',
                textAlignVertical: 'center',
              }}>
              {item}
            </Text>
          </View>
        )}
        viewabilityConfig={defaultViewabilityConfig}
        onViewableItemsChanged={onViewableItemsChanged}
      />
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    height: 120,
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
    height: 40,
  },
});

export default SectionListTest;