/* eslint-disable react/require-default-props */
import { animated, to, useInView, useSpring } from '@react-spring/web';
import React, { useEffect } from 'react';
import { Doc } from '../../../../fields/Doc';
import { NumCast } from '../../../../fields/Types';
import { PresEffect, PresEffectDirection } from './PresEnums';
import './SlideEffect.scss';
import { emptyFunction } from '../../../../Utils';

interface SlideEffectProps {
    doc?: Doc; // pass in doc to extract width, height, bg
    dir: PresEffectDirection;
    presEffect: PresEffect;
    springSettings: {
        stiffness: number;
        damping: number;
        mass: number;
    };
    children: React.ReactNode;
    infinite?: boolean;
    startOpacity?: number; // set to zero to linearly fade in while animating
}

const DEFAULT_WIDTH = 40;
const PREVIEW_OFFSET = 60;
const ACTUAL_OFFSET = 200;

/**
 * This component wraps around the doc to create an effect animation, and also wraps the preview animations
 * for the effects as well.
 */
export default function SpringAnimation({ doc, dir, springSettings, presEffect, children, infinite, startOpacity }: SlideEffectProps) {
    const expandConfig = {
        to: { scale: 1, x: 0, y: 0 },
        from: { scale: 0, x: 0, y: 0 },
    };
    const fadeConfig = {
        to: { x: 0, y: 0 },
        from: { x: 0, y: 0 },
    };
    const rotateConfig = {
        to: { x: 360, y: 0 },
        from: { x: 0, y: 0 },
    };
    const flipConfig = {
        to: { x: 180, y: 0 },
        from: { x: 0, y: 0 },
    };
    const bounceConfig = {
        to: { x: 0, y: 0 },
        from: (() => {
            const offset = infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET;
            switch (dir) {
                case PresEffectDirection.Left:  return { x: -offset, y:       0, };
                case PresEffectDirection.Right: return { x:  offset, y:       0, };
                case PresEffectDirection.Top:   return { x:       0, y: -offset, };
                case PresEffectDirection.Bottom:return { x:       0, y:  offset, };
                default:                        return { x:       0, y:       0, }; // no movement for center
            }})(), // prettier-ignore
    };
    const rollConfig = {
        to: { x: 0, y: 0 },
        from: (() => {
            switch (dir) {
                case PresEffectDirection.Left:   return { x: -100, y: -120, };
                case PresEffectDirection.Right:  return { x:  100, y:  120, };
                case PresEffectDirection.Top:    return { x: -100, y: -120, };
                case PresEffectDirection.Bottom: return { x: -100, y: -120, };
                default:                         return { x:    0, y:    0, };  // no movement for center
            }})(), // prettier-ignore
    };

    // prettier-ignore
    const effectConfig = (() => {
        switch (presEffect) {
            case PresEffect.Fade:       return fadeConfig;
            case PresEffect.Bounce:     return bounceConfig;
            case PresEffect.Rotate:     return rotateConfig;
            case PresEffect.Flip:       return flipConfig;
            case PresEffect.Roll:       return rollConfig;
            case PresEffect.Lightspeed: return { from: {}, to: {} };
            case PresEffect.Expand:     
            default:                    return expandConfig;
        } // prettier-ignore
    })();

    const [springs, api] = useSpring(
        () => ({
            to: { ...effectConfig.to, opacity: 1 },
            from: { ...effectConfig.from, opacity: startOpacity ?? 1 },
            config: { tension: springSettings.stiffness, friction: springSettings.damping, mass: springSettings.mass },
            onStart: emptyFunction,
            onRest: emptyFunction,
        }),
        [springSettings]
    );

    const [ref, inView] = useInView({
        once: true,
    });
    useEffect(() => {
        if (inView) {
            api.start({ loop: infinite, delay: infinite ? 500 : 0 });
        }
    }, [inView]);
    const animatedDiv = (style: any) => (
        <animated.div ref={ref} style={{ ...style, opacity: to(springs.opacity, val => `${val}`) }}>
            {children}
        </animated.div>
    );
    const [width, height] = [NumCast(doc?.width, DEFAULT_WIDTH), NumCast(doc?.height, DEFAULT_WIDTH)];
    const flipAxis = dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? 'X' : 'Y';
    const [rotateX, rotateY] = flipAxis === 'X' ? ['180deg', undefined] : [undefined, '180deg'];
    switch (presEffect) {
        case PresEffect.Flip:  return animatedDiv({ transform: to(springs.x, val => `perspective(600px) rotate${flipAxis}(${val}deg)`), width, height, rotateX, rotateY })
        case PresEffect.Rotate:return animatedDiv({ transform: to(springs.x, val => `rotate(${val}deg)`) });
        case PresEffect.Roll:  return animatedDiv({ transform: to([springs.x, springs.y], (val, val2) => `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) });
        default:               return animatedDiv(springs);
    } // prettier-ignore
}
