import { Platform, Animated } from 'react-native';
import type { CarouselProps } from 'src/carousel/types';

const IS_ANDROID = Platform.OS === 'android';

// Get scroll interpolator's input range from an array of slide indexes
// Indexes are relative to the current active slide (index 0)
// For example, using [3, 2, 1, 0, -1] will return:
// [
//     (index - 3) * sizeRef, // active + 3
//     (index - 2) * sizeRef, // active + 2
//     (index - 1) * sizeRef, // active + 1
//     index * sizeRef, // active
//     (index + 1) * sizeRef // active - 1
// ]
export function getInputRangeFromIndexes<TData> (
    range: number[],
    index: number,
    carouselProps: CarouselProps<TData>
) {
    const sizeRef = carouselProps.vertical ?
        carouselProps.itemHeight :
        carouselProps.itemWidth;
    const inputRange = [];

    for (let i = 0; i < range.length; i++) {
        inputRange.push((index - range[i]) * sizeRef);
    }

    return inputRange;
}

// Default behavior
// Scale and/or opacity effect
// Based on props 'inactiveSlideOpacity' and 'inactiveSlideScale'
export function defaultScrollInterpolator<TData> (
    index: number,
    carouselProps: CarouselProps<TData>
) {
    const range = [1, 0, -1];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = [0, 1, 0];

    return { inputRange, outputRange };
}
export function defaultAnimatedStyles<TData> (
    _index: number,
    animatedValue: Animated.AnimatedInterpolation,
    carouselProps: CarouselProps<TData>
) {
    let animatedOpacity = {};
    let animatedScale = {};

    if (carouselProps.inactiveSlideOpacity < 1) {
        animatedOpacity = {
            opacity: animatedValue.interpolate({
                inputRange: [0, 1],
                outputRange: [carouselProps.inactiveSlideOpacity, 1]
            })
        };
    }

    if (carouselProps.inactiveSlideScale < 1) {
        animatedScale = {
            transform: [
                {
                    scale: animatedValue.interpolate({
                        inputRange: [0, 1],
                        outputRange: [carouselProps.inactiveSlideScale, 1]
                    })
                }
            ]
        };
    }

    return {
        ...animatedOpacity,
        ...animatedScale
    };
}

// Shift animation
// Same as the default one, but the active slide is also shifted up or down
// Based on prop 'inactiveSlideShift'
export function shiftAnimatedStyles<TData> (
    _index: number,
    animatedValue: Animated.AnimatedInterpolation,
    carouselProps: CarouselProps<TData>
) {
    let animatedOpacity = {};
    let animatedScale = {};
    let animatedTranslate = {};

    if (carouselProps.inactiveSlideOpacity < 1) {
        animatedOpacity = {
            opacity: animatedValue.interpolate({
                inputRange: [0, 1],
                outputRange: [carouselProps.inactiveSlideOpacity, 1]
            })
        };
    }

    if (carouselProps.inactiveSlideScale < 1) {
        animatedScale = {
            scale: animatedValue.interpolate({
                inputRange: [0, 1],
                outputRange: [carouselProps.inactiveSlideScale, 1]
            })
        };
    }

    if (carouselProps.inactiveSlideShift !== 0) {
        const translateProp = carouselProps.vertical ? 'translateX' : 'translateY';
        animatedTranslate = {
            [translateProp]: animatedValue.interpolate({
                inputRange: [0, 1],
                outputRange: [carouselProps.inactiveSlideShift, 0]
            })
        };
    }

    return {
        ...animatedOpacity,
        transform: [{ ...animatedScale }, { ...animatedTranslate }]
    };
}

// Stack animation
// Imitate a deck/stack of cards (see #195)
// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property
// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item
// The `elevation` property compensates for that only visually, which is not good enough
export function stackScrollInterpolator<TData> (
    index: number,
    carouselProps: CarouselProps<TData>
) {
    const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;

    return { inputRange, outputRange };
}
export function stackAnimatedStyles<TData> (
    index: number,
    animatedValue: Animated.AnimatedInterpolation,
    carouselProps: CarouselProps<TData>,
    cardOffset?: number
) {
    const sizeRef = carouselProps.vertical ?
        carouselProps.itemHeight :
        carouselProps.itemWidth;
    const translateProp = carouselProps.vertical ? 'translateY' : 'translateX';

    const card1Scale = 0.9;
    const card2Scale = 0.8;

    const newCardOffset = cardOffset ?? 18;

    const getTranslateFromScale = (cardIndex: number, scale: number) => {
        const centerFactor = (1 / scale) * cardIndex;
        const centeredPosition = -Math.round(sizeRef * centerFactor);
        const edgeAlignment = Math.round((sizeRef - sizeRef * scale) / 2);
        const offset = Math.round((newCardOffset * Math.abs(cardIndex)) / scale);

        return IS_ANDROID ?
            centeredPosition - edgeAlignment - offset :
            centeredPosition + edgeAlignment + offset;
    };

    const opacityOutputRange =
    carouselProps.inactiveSlideOpacity === 1 ? [1, 1, 1, 0] : [1, 0.75, 0.5, 0];

    return IS_ANDROID ?
        {
        // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view
            opacity: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0],
                outputRange: opacityOutputRange.reverse(),
                extrapolate: 'clamp'
            }),
            transform: [
                {
                    scale: animatedValue.interpolate({
                        inputRange: [-2, -1, 0, 1],
                        outputRange: [card2Scale, card1Scale, 1, card1Scale],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [translateProp]: animatedValue.interpolate({
                        inputRange: [-3, -2, -1, 0, 1],
                        outputRange: [
                            getTranslateFromScale(-3, card2Scale),
                            getTranslateFromScale(-2, card2Scale),
                            getTranslateFromScale(-1, card1Scale),
                            0,
                            sizeRef * 0.5
                        ],
                        extrapolate: 'clamp'
                    })
                }
            ]
        } :
        {
            zIndex: carouselProps.data.length - index,
            opacity: animatedValue.interpolate({
                inputRange: [0, 1, 2, 3],
                outputRange: opacityOutputRange,
                extrapolate: 'clamp'
            }),
            transform: [
                {
                    scale: animatedValue.interpolate({
                        inputRange: [-1, 0, 1, 2],
                        outputRange: [card1Scale, 1, card1Scale, card2Scale],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [translateProp]: animatedValue.interpolate({
                        inputRange: [-1, 0, 1, 2, 3],
                        outputRange: [
                            -sizeRef * 0.5,
                            0,
                            getTranslateFromScale(1, card1Scale),
                            getTranslateFromScale(2, card2Scale),
                            getTranslateFromScale(3, card2Scale)
                        ],
                        extrapolate: 'clamp'
                    })
                }
            ]
        };
}

// Tinder animation
// Imitate the popular Tinder layout
// WARNING: The effect had to be visually inverted on Android because this OS doesn't honor the `zIndex`property
// This means that the item with the higher zIndex (and therefore the tap receiver) remains the one AFTER the currently active item
// The `elevation` property compensates for that only visually, which is not good enough
export function tinderScrollInterpolator<TData> (
    index: number,
    carouselProps: CarouselProps<TData>
) {
    const range = IS_ANDROID ? [1, 0, -1, -2, -3] : [3, 2, 1, 0, -1];
    const inputRange = getInputRangeFromIndexes(range, index, carouselProps);
    const outputRange = range;

    return { inputRange, outputRange };
}
export function tinderAnimatedStyles<TData> (
    index: number,
    animatedValue: Animated.AnimatedInterpolation,
    carouselProps: CarouselProps<TData>,
    cardOffset?: number
) {
    const sizeRef = carouselProps.vertical ?
        carouselProps.itemHeight :
        carouselProps.itemWidth;
    const mainTranslateProp = carouselProps.vertical ?
        'translateY' :
        'translateX';
    const secondaryTranslateProp = carouselProps.vertical ?
        'translateX' :
        'translateY';

    const card1Scale = 0.96;
    const card2Scale = 0.92;
    const card3Scale = 0.88;

    const peekingCardsOpacity = IS_ANDROID ? 0.92 : 1;

    const newCardOffset = cardOffset ?? 9;

    const getMainTranslateFromScale = (cardIndex: number, scale: number) => {
        const centerFactor = (1 / scale) * cardIndex;
        return -Math.round(sizeRef * centerFactor);
    };

    const getSecondaryTranslateFromScale = (cardIndex: number, scale: number) => {
        return Math.round((newCardOffset * Math.abs(cardIndex)) / scale);
    };

    return IS_ANDROID ?
        {
        // elevation: carouselProps.data.length - index, // fix zIndex bug visually, but not from a logic point of view
            opacity: animatedValue.interpolate({
                inputRange: [-3, -2, -1, 0, 1],
                outputRange: [0, peekingCardsOpacity, peekingCardsOpacity, 1, 0],
                extrapolate: 'clamp'
            }),
            transform: [
                {
                    scale: animatedValue.interpolate({
                        inputRange: [-3, -2, -1, 0],
                        outputRange: [card3Scale, card2Scale, card1Scale, 1],
                        extrapolate: 'clamp'
                    })
                },
                {
                    rotate: animatedValue.interpolate({
                        inputRange: [0, 1],
                        outputRange: ['0deg', '22deg'],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [mainTranslateProp]: animatedValue.interpolate({
                        inputRange: [-3, -2, -1, 0, 1],
                        outputRange: [
                            getMainTranslateFromScale(-3, card3Scale),
                            getMainTranslateFromScale(-2, card2Scale),
                            getMainTranslateFromScale(-1, card1Scale),
                            0,
                            sizeRef * 1.1
                        ],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [secondaryTranslateProp]: animatedValue.interpolate({
                        inputRange: [-3, -2, -1, 0],
                        outputRange: [
                            getSecondaryTranslateFromScale(-3, card3Scale),
                            getSecondaryTranslateFromScale(-2, card2Scale),
                            getSecondaryTranslateFromScale(-1, card1Scale),
                            0
                        ],
                        extrapolate: 'clamp'
                    })
                }
            ]
        } :
        {
            zIndex: carouselProps.data.length - index,
            opacity: animatedValue.interpolate({
                inputRange: [-1, 0, 1, 2, 3],
                outputRange: [0, 1, peekingCardsOpacity, peekingCardsOpacity, 0],
                extrapolate: 'clamp'
            }),
            transform: [
                {
                    scale: animatedValue.interpolate({
                        inputRange: [0, 1, 2, 3],
                        outputRange: [1, card1Scale, card2Scale, card3Scale],
                        extrapolate: 'clamp'
                    })
                },
                {
                    rotate: animatedValue.interpolate({
                        inputRange: [-1, 0],
                        outputRange: ['-22deg', '0deg'],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [mainTranslateProp]: animatedValue.interpolate({
                        inputRange: [-1, 0, 1, 2, 3],
                        outputRange: [
                            -sizeRef * 1.1,
                            0,
                            getMainTranslateFromScale(1, card1Scale),
                            getMainTranslateFromScale(2, card2Scale),
                            getMainTranslateFromScale(3, card3Scale)
                        ],
                        extrapolate: 'clamp'
                    })
                },
                {
                    [secondaryTranslateProp]: animatedValue.interpolate({
                        inputRange: [0, 1, 2, 3],
                        outputRange: [
                            0,
                            getSecondaryTranslateFromScale(1, card1Scale),
                            getSecondaryTranslateFromScale(2, card2Scale),
                            getSecondaryTranslateFromScale(3, card3Scale)
                        ],
                        extrapolate: 'clamp'
                    })
                }
            ]
        };
}
