/**
 * 支持长按拖拽排序的flatlist，右边加个固定的按钮，拖拽排序。
 * 考虑到方便实现+节省性能，整个app内的拖拽排序都遵守以下实现。
 * 点击会出现
 */

import {iconSizeConst} from '@/constants/uiConst';
import useTextColor from '@/hooks/useTextColor';
import rpx from '@/utils/rpx';
import {FlashList} from '@shopify/flash-list';
import React, {
    ForwardedRef,
    forwardRef,
    memo,
    useEffect,
    useMemo,
    useRef,
    useState,
} from 'react';
import {
    LayoutRectangle,
    Pressable,
    StyleSheet,
    View,
    ViewToken,
} from 'react-native';
import {
    runOnJS,
    useDerivedValue,
    useSharedValue,
} from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

const WINDOW_WIDTH = rpx(750);
const defaultZIndex = 10;

interface ISortableFlatListProps<T> {
    data: T[];
    renderItem: (props: {item: T; index: number}) => JSX.Element;
    // 高度
    itemHeight: number;
    // 滚动list距离顶部的距离, 这里写的不好
    marginTop: number;
    /** 拖拽时的背景色 */
    activeBackgroundColor?: string;
    /** 交换结束 */
    onSortEnd?: (newData: T[]) => void;
}

export default function SortableFlatList<T extends any = any>(
    props: ISortableFlatListProps<T>,
) {
    const {
        data,
        renderItem,
        itemHeight,
        marginTop,
        activeBackgroundColor,
        onSortEnd,
    } = props;

    // 不要干扰原始数据
    const [_data, _setData] = useState([...(data ?? [])]);
    // 是否禁止滚动
    const [scrollEnabled, setScrollEnabled] = useState(true);
    // 是否处在激活状态, -1表示无，其他表示当前激活的下标
    const activeRef = useRef(-1);
    const [activeItem, setActiveItem] = useState<T | null>(null);

    const viewableItemsRef = useRef<ViewToken[] | null>(null);

    const layoutRef = useRef<LayoutRectangle>();
    // listref
    const listRef = useRef<FlashList<T> | null>(null);
    // fakeref
    const fakeItemRef = useRef<View | null>(null);
    // contentoffset
    const contentOffsetYRef = useRef<number>(0);
    const targetOffsetYRef = useRef<number>(0);

    const direction = useSharedValue(0);

    useEffect(() => {
        _setData([...(data ?? [])]);
    }, [data]);

    const initDragPageY = useRef<number>(0);
    const initDragLocationY = useRef<number>(0);
    const offsetRef = useRef<number>(0);

    //#region 滚动
    const scrollingRef = useRef(false);

    // 列表整体的高度
    const getListContentHeight = useMemo(
        () => () => itemHeight * data.length,
        [data],
    );

    function scrollToTarget(forceScroll = false) {
        // 未选中
        if (activeRef.current === -1) {
            scrollingRef.current = false;
            return;
        }

        // 滚动中就不滚了 /
        if (scrollingRef.current && !forceScroll) {
            scrollingRef.current = true;
            return;
        }
        // 方向是0
        if (direction.value === 0) {
            scrollingRef.current = false;
            return;
        }

        const nextTarget =
            Math.sign(direction.value) *
                Math.max(Math.abs(direction.value), 0.3) *
                300 +
            contentOffsetYRef.current;
        // 当前到极限了
        if (
            (contentOffsetYRef.current <= 2 &&
                nextTarget < contentOffsetYRef.current) ||
            (contentOffsetYRef.current >=
                getListContentHeight() - (layoutRef.current?.height ?? 0) - 2 &&
                nextTarget > contentOffsetYRef.current)
        ) {
            scrollingRef.current = false;
            return;
        }
        scrollingRef.current = true;
        // 超出区域
        targetOffsetYRef.current = Math.min(
            Math.max(0, nextTarget),
            getListContentHeight() - (layoutRef.current?.height ?? 0),
        );
        listRef.current?.scrollToOffset({
            animated: true,
            offset: targetOffsetYRef.current,
        });
    }

    useDerivedValue(() => {
        // 正在滚动
        if (scrollingRef.current) {
            return;
        } else if (direction.value !== 0) {
            // 开始滚动
            runOnJS(scrollToTarget)();
        }
    }, []);

    //#endregion

    const onViewRef = useRef((vi: any) => {
        viewableItemsRef.current = vi.viewableItems;
    });
    return (
        <View style={style.flex1}>
            {/* 纯展示 */}
            <FakeFlatListItem
                ref={_ => (fakeItemRef.current = _)}
                backgroundColor={activeBackgroundColor}
                renderItem={renderItem}
                itemHeight={itemHeight}
                item={activeItem}
            />
            <FlashList
                scrollEnabled={scrollEnabled}
                onViewableItemsChanged={onViewRef.current}
                ref={_ => {
                    listRef.current = _;
                }}
                onLayout={evt => {
                    layoutRef.current = evt.nativeEvent.layout;
                }}
                data={_data}
                estimatedItemSize={itemHeight}
                scrollEventThrottle={16}
                onTouchStart={e => {
                    if (activeRef.current !== -1) {
                        // 相对于整个页面顶部的距离
                        initDragPageY.current = e.nativeEvent.pageY;
                        initDragLocationY.current = e.nativeEvent.locationY;
                    }
                }}
                onTouchMove={e => {
                    if (activeRef.current !== -1) {
                        offsetRef.current =
                            e.nativeEvent.pageY -
                            (marginTop ?? layoutRef.current?.y ?? 0) -
                            itemHeight / 2;

                        if (offsetRef.current < 0) {
                            offsetRef.current = 0;
                        } else if (
                            offsetRef.current >
                            (layoutRef.current?.height ?? 0) - itemHeight
                        ) {
                            offsetRef.current =
                                (layoutRef.current?.height ?? 0) - itemHeight;
                        }
                        fakeItemRef.current!.setNativeProps({
                            top: offsetRef.current,
                            opacity: 1,
                            zIndex: 100,
                        });

                        // 如果超出范围，停止
                        if (offsetRef.current < itemHeight * 2) {
                            // 上滑
                            direction.value =
                                offsetRef.current / itemHeight / 2 - 1;
                        } else if (
                            offsetRef.current >
                            (layoutRef.current?.height ?? 0) - 3 * itemHeight
                        ) {
                            // 下滑
                            direction.value =
                                (offsetRef.current -
                                    (layoutRef.current?.height ?? 0) +
                                    3 * itemHeight) /
                                itemHeight /
                                2;
                        } else {
                            // 不滑动
                            direction.value = 0;
                        }
                    }
                }}
                onTouchEnd={e => {
                    if (activeRef.current !== -1) {
                        // 计算最终的位置，触发onSortEnd
                        let index = activeRef.current;
                        if (contentOffsetYRef.current) {
                            index = Math.round(
                                (contentOffsetYRef.current +
                                    offsetRef.current) /
                                    itemHeight,
                            );
                        } else {
                            // 拖动的距离
                            index =
                                activeRef.current +
                                Math.round(
                                    (e.nativeEvent.pageY -
                                        initDragPageY.current +
                                        initDragLocationY.current) /
                                        itemHeight,
                                );
                        }
                        index = Math.min(data.length, Math.max(index, 0));
                        // from: activeRef.current to: index
                        if (activeRef.current !== index) {
                            let nData = _data
                                .slice(0, activeRef.current)
                                .concat(_data.slice(activeRef.current + 1));
                            nData.splice(index, 0, activeItem as T);
                            onSortEnd?.(nData);
                            // 测试用，正式时移除掉
                            // _setData(nData);
                        }
                    }
                    scrollingRef.current = false;
                    activeRef.current = -1;
                    setScrollEnabled(true);
                    setActiveItem(null);
                    fakeItemRef.current!.setNativeProps({
                        top: 0,
                        opacity: 0,
                        zIndex: -1,
                    });
                }}
                onTouchCancel={() => {
                    // todo: 滑动很快的时候会触发取消，native的flatlist就这样
                    activeRef.current = -1;
                    scrollingRef.current = false;
                    setScrollEnabled(true);
                    setActiveItem(null);
                    fakeItemRef.current!.setNativeProps({
                        top: 0,
                        opacity: 0,
                        zIndex: -1,
                    });
                    contentOffsetYRef.current = 0;
                }}
                onScroll={e => {
                    contentOffsetYRef.current = e.nativeEvent.contentOffset.y;
                    if (
                        activeRef.current !== -1 &&
                        Math.abs(
                            contentOffsetYRef.current -
                                targetOffsetYRef.current,
                        ) < 2
                    ) {
                        scrollToTarget(true);
                    }
                }}
                renderItem={({item, index}) => {
                    return (
                        <SortableFlatListItem
                            setScrollEnabled={setScrollEnabled}
                            activeRef={activeRef}
                            renderItem={renderItem}
                            item={item}
                            index={index}
                            setActiveItem={setActiveItem}
                            itemHeight={itemHeight}
                        />
                    );
                }}
            />
        </View>
    );
}

interface ISortableFlatListItemProps<T extends any = any> {
    item: T;
    index: number;
    // 高度
    itemHeight: number;
    setScrollEnabled: (scrollEnabled: boolean) => void;
    renderItem: (props: {item: T; index: number}) => JSX.Element;
    setActiveItem: (item: T | null) => void;
    activeRef: React.MutableRefObject<number>;
}
function _SortableFlatListItem(props: ISortableFlatListItemProps) {
    const {
        itemHeight,
        setScrollEnabled,
        renderItem,
        setActiveItem,
        item,
        index,
        activeRef,
    } = props;

    // 省一点性能，height是顺着传下来的，放ref就好了
    const styleRef = useRef(
        StyleSheet.create({
            viewWrapper: {
                height: itemHeight,
                width: WINDOW_WIDTH,
                flexDirection: 'row',
                justifyContent: 'flex-end',
                zIndex: defaultZIndex,
            },
            btn: {
                height: itemHeight,
                paddingHorizontal: rpx(28),
                justifyContent: 'center',
                alignItems: 'center',
            },
        }),
    );
    const textColor = useTextColor();

    return (
        <View style={styleRef.current.viewWrapper}>
            {renderItem({item, index})}
            <Pressable
                onTouchStart={() => {
                    if (activeRef.current !== -1) {
                        return;
                    }
                    /** 使用ref避免其它组件重新渲染; 由于事件冒泡，这里会先触发 */
                    activeRef.current = index;
                    /** 锁定滚动 */
                    setScrollEnabled(false);
                    setActiveItem(item);
                }}
                style={styleRef.current.btn}>
                <Icon
                    name="menu"
                    size={iconSizeConst.normal}
                    color={textColor}
                />
            </Pressable>
        </View>
    );
}

const SortableFlatListItem = memo(
    _SortableFlatListItem,
    (prev, curr) => prev.index === curr.index && prev.item === curr.item,
);

const FakeFlatListItem = forwardRef(function (
    props: Pick<
        ISortableFlatListItemProps,
        'itemHeight' | 'renderItem' | 'item'
    > & {
        backgroundColor?: string;
    },
    ref: ForwardedRef<View>,
) {
    const {itemHeight, renderItem, item, backgroundColor} = props;

    const styleRef = useRef(
        StyleSheet.create({
            viewWrapper: {
                height: itemHeight,
                width: WINDOW_WIDTH,
                flexDirection: 'row',
                justifyContent: 'flex-end',
                zIndex: defaultZIndex,
            },
            btn: {
                height: itemHeight,
                paddingHorizontal: rpx(28),
                justifyContent: 'center',
                alignItems: 'center',
            },
        }),
    );
    const textColor = useTextColor();

    return (
        <View
            ref={ref}
            style={[
                styleRef.current.viewWrapper,
                style.activeItemDefault,
                backgroundColor ? {backgroundColor} : {},
            ]}>
            {item ? renderItem({item, index: -1}) : <></>}
            <Pressable style={styleRef.current.btn}>
                <Icon
                    name="menu"
                    size={iconSizeConst.normal}
                    color={textColor}
                />
            </Pressable>
        </View>
    );
});

const style = StyleSheet.create({
    flex1: {
        flex: 1,
        width: WINDOW_WIDTH,
    },
    activeItemDefault: {
        opacity: 0,
        zIndex: -1,
        position: 'absolute',
        top: 0,
        left: 0,
    },
});
