<!--
    #VUE3 Todo
-->

<template>
    <div :class="bem({ hasOffset, orientation, ...variantsMap })" role="region">
        <div
            v-for="(item, index) in items"
            :key="getItemKey(item)"
            ref="slide"
            :inert="!isItemSelected(item)"
            :class="bem('slide-wrapper', { selected: isItemSelected(item) })"
            :tabindex="-1"
            role="region"
            :aria-label="item.label ? item.label : undefined"
        >
            <px-slide-transition
                :class="bem('transitioner')"
                :disable-animations="disableAnimations"
                :direction="direction"
                :orientation="orientation"
                :active="isItemSelected(item)"
                @before-leave="transitionStart"
                @after-leave="transitionEnd"
            >
                <div :class="bem('slide')">
                    <slot
                        name="slide"
                        :selected="isItemSelected(item)"
                        :item="item"
                        :index="index"
                    ></slot>
                </div>
            </px-slide-transition>
        </div>

        <slot name="scrollpreview" :direction="scrollPreview">
            <div :class="bem('scroll-preview', { prev: true, visible: scrollPreview == -1 })">
                <px-icon :src="arrowStartSrc" :size="2" />
            </div>

            <div :class="bem('scroll-preview', { next: true, visible: scrollPreview == 1 })">
                <px-icon :src="arrowEndSrc" :size="2" />
            </div>
        </slot>
    </div>
</template>

<script>
import PxBaseItemsSelect from '../bases/PxBaseItemsSelect.vue';
import PxSlideTransition from './PxSlideTransition.vue';
import PxIcon from './PxIcon.vue';
import PxBaseVariants from '../bases/PxBaseVariants.vue';

import arrowStartSrcDefault from '../assets/feather/arrow-left.svg?url';
import arrowEndSrcDefault from '../assets/feather/arrow-right.svg?url';

import gsap from 'gsap';
import Drag from '../utils/drag.js';
import Lethargy from '../utils/lethargy.js';

import iconArrowLeft from '../assets/feather/arrow-left.svg?url';
import iconArrowRight from '../assets/feather/arrow-right.svg?url';

export default {
    name: 'px-slides',
    mixins: [PxBaseItemsSelect, PxBaseVariants],
    components: { PxSlideTransition, PxIcon },

    props: {
        /**
         * The source for the left arrow icon. See PxIcon for details about creating app-level icons.
         */
        arrowStartSrc: { type: String, default: arrowStartSrcDefault },

        /**
         * The source for the left arrow icon. See PxIcon for details about creating app-level icons.
         */
        arrowEndSrc: { type: String, default: arrowEndSrcDefault },

        // vertical or horizontal
        orientation: { type: String, default: 'horizontal' },

        // whether drag interaction is enabled
        dragEnabled: { type: Boolean, default: true },

        // whether scroll interaction is enabled
        scrollEnabled: { type: Boolean, default: false },

        // whether to show an icon or other indicator that scrolling has started
        scrollPreviewEnabled: { type: Boolean, default: false },

        // if true, slides loop to the first slide once last slide is reached
        loop: { type: Boolean, default: true },

        // selector used to find drag elements in the dom
        dragSelector: { type: String, default: '[data-drag]' },

        // selector used to find drag elements in the dom that should animate in a reverse direction in vertical transitions
        dragReverseSelector: { type: String, default: '[data-drag-reverse]' },

        // exclude from drag
        dragExcludeSelector: { type: String, default: 'input, button, [data-no-drag]' },

        // exclude from scroll
        scrollExcludeSelector: { type: String, default: '[data-no-slidescroll]' },

        // override default
        autoSelectFirst: { type: Boolean, default: true },

        // disable animations
        disableAnimations: { type: Boolean, default: false },
    },

    data() {
        return {
            direction: 1,
            lastInteractionType: null,
            activeTransitions: 0,
            scrollPreview: 0,

            // true whenver the selected item is in an offset by dragging or a transition
            hasOffset: false,

            iconArrowLeft,
            iconArrowRight,
        };
    },
    computed: {
        lethargyOverrides() {
            // query strings overrides

            const stability = parseFloat(this.$route.query?.scrollStability);
            const sensitivity = parseFloat(this.$route.query?.scrollSensitivity);
            const tolerance = parseFloat(this.$route.query?.scrollTolerance);

            if (stability || sensitivity || tolerance) {
                console.log('[Custom scroll values enabled.]');
                console.log(`stability: ${stability || 8}`);
                console.log(`sensitivity: ${sensitivity || 100}`);
                console.log(`tolerance: ${tolerance || 1.1}`);

                return {
                    stability: stability || 8,
                    sensitivity: sensitivity || 100,
                    tolerance: tolerance || 1.1,
                };
            }

            // windows overrides

            if (
                navigator.userAgent.indexOf('Win') != -1 ||
                this.$route.query?.scroll == 'windows'
            ) {
                return {
                    stability: stability || 8,
                    sensitivity: sensitivity || 20,
                    tolerance: tolerance || 0.1,
                };
            }

            return null;
        },
    },
    watch: {
        hasOffset(nv) {
            if (nv) {
                this.$emit('offset-start');
            } else {
                this.$emit('offset-end');
            }
        },
        selectedKey(nv, ov) {
            let newIndex = this.itemKeys.indexOf(nv);
            let oldIndex = this.itemKeys.indexOf(ov);

            const slideChangeArgs = {
                key: nv,
                oldKey: ov,
                index: newIndex,
                previousIndex: oldIndex,
                interactionType: this.lastInteractionType,
            };

            this.$emit('slide-change', slideChangeArgs);

            this.lastInteractionType = null;

            newIndex = this.selectedRelativeIndex(newIndex);
            oldIndex = this.selectedRelativeIndex(oldIndex);

            this.direction = oldIndex > newIndex ? 1 : -1;

            this.$nextTick(() => {
                this.$refs.slide[this.selectedIndex]?.focus();
            });
        },
        dragEnabled(nv) {
            if (!this.drag) {
                return;
            }
            if (!nv) {
                this.drag.disable();
            } else {
                this.drag.enable();
            }
        },
        orientation(nv) {
            if (this.drag) {
                this.drag.updateOptions({ lock: nv === 'horizontal' ? 'x' : 'y' });
            }
        },
    },
    mounted() {
        if (this.scrollEnabled) {
            let overrides = this.lethargyOverrides;

            if (overrides) {
                this.lethargy = new Lethargy(
                    overrides.stability,
                    overrides.sensitivity,
                    overrides.tolerance,
                );
            } else {
                this.lethargy = new Lethargy();
            }

            this.lethargyPrevCheck = false;

            this.$el.addEventListener('wheel', this.onWheel, { passive: true });
        }

        this.drag = new Drag(this.$el, {
            lock: this.orientation === 'horizontal' ? 'x' : 'y',
            onStart: this.onDragStart,
            onDrag: this.onDrag,
            onEnd: this.onDragEnd,
            excludeSelector: this.dragExcludeSelector,
            disabled: !this.dragEnabled,
        });
    },
    beforeUnmount() {
        if (this.drag) {
            this.drag.destroy();
        }

        this.$el.removeEventListener('wheel', this.onWheel);
    },
    methods: {
        transitionStart() {
            if (!this.activeTransitions) {
                this.$emit('transitions-start');
                this.hasOffset = true;
            }
            this.activeTransitions++;
        },

        transitionEnd() {
            this.activeTransitions--;
            if (!this.activeTransitions) {
                this.$emit('transitions-end', {
                    toIndex: this.itemKeys.indexOf(this.selectedKey),
                });
                this.hasOffset = false;
            }
        },

        onDragStart() {
            // get drag elements
            let slide = this.$refs.slide[this.selectedIndex];

            if (slide) {
                this.dragElements = [...slide.querySelectorAll(this.dragSelector)];
                this.dragElementsReverse = [...slide.querySelectorAll(this.dragReverseSelector)];
            }

            this.hasOffset = true;
        },

        onDrag(ev) {
            if (this.dragElements?.length || this.dragElementsReverse?.length) {
                if (gsap.isTweening(this.dragElements[0])) {
                    gsap.killTweensOf(this.dragElements);
                }

                if (gsap.isTweening(this.dragElementsReverse[0])) {
                    gsap.killTweensOf(this.dragElementsReverse);
                }

                if (this.orientation === 'vertical') {
                    gsap.set(this.dragElements || [], { y: ev.startDelta.y });
                    if (this.dragElementsReverse?.length) {
                        gsap.set(this.dragElementsReverse, { y: -1 * ev.startDelta.y });
                    }
                } else {
                    gsap.set(this.dragElements, { x: ev.startDelta.x });
                }

                this.hasOffset = true;
            }
        },

        onDragEnd(ev) {
            const delta = this.orientation === 'vertical' ? ev.delta.y : ev.delta.x;
            if (Math.abs(delta) < 10) {
                this.hasOffset = false;

                // snap back
                const to = {};
                to[this.orientation === 'vertical' ? 'y' : 'x'] = 0;
                gsap.to(this.dragElements, { ...to });

                // snap back reverse elements (vertical only)
                if (this.orientation === 'vertical') {
                    gsap.to(this.dragElementsReverse, { y: 0 });
                }
            } else {
                this.lastInteractionType = 'drag';
                this.$emit('drag-complete');
                if (delta > 0) {
                    this.previous();
                } else {
                    this.next();
                }
            }
        },

        onWheel(e) {
            if (!this.scrollEnabled) {
                return;
            }

            if (this.scrollPreviewEnabled) {
                const delta = (e.deltaY + e.deltaX) / 2;
                if (Math.abs(delta) > 5) {
                    this.scrollPreview = Math.max(Math.min(delta, 1), -1);
                }

                clearTimeout(this.scrollPreviewTimeout);

                this.scrollPreviewTimeout = setTimeout(() => {
                    this.scrollPreview = 0;
                }, 200);
            }

            if (this.scrollExcludeSelector) {
                const excludeElements = this.$el.querySelectorAll(this.scrollExcludeSelector);

                for (let i = 0; i < excludeElements.length; i++) {
                    if (excludeElements[i].contains(e.target)) return;
                }
            }

            const check = this.lethargy.check(e);

            if (check !== this.lethargyPrevCheck) {
                if (check) {
                    this.lastInteractionType = 'scroll';
                    this.$emit('scroll-complete');

                    if (check > 0) {
                        this.previous(false);
                    } else {
                        this.next(false);
                    }

                    e.stopPropagation();
                }
            }

            this.lethargyPrevCheck = check;
        },

        next(loopOverride = null) {
            this.selectOffset(1, loopOverride === null ? this.loop : loopOverride);
        },

        previous(loopOverride = null) {
            this.selectOffset(-1, loopOverride === null ? this.loop : loopOverride);
        },

        // returns the index of the item relative to the selected item, if looped
        // then half of the items will be before and half after
        selectedRelativeIndex(idx) {
            if (this.selectedIndex == -1) {
                return 0;
            }

            idx = idx - this.selectedIndex;

            if (this.loop) {
                let half = Math.floor(this.items.length / 2);

                if (idx >= half) {
                    idx = idx - this.items.length;
                }

                if (idx < -half) {
                    idx = idx + this.items.length;
                }
            }

            return idx;
        },
    },
};
</script>

<style lang="scss">
@use '../styles/px.scss' as *;

.px-slides {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: 'content';
    @include no-select();

    -webkit-transform-style: preserve-3d;

    video,
    img {
        -webkit-user-drag: none;
    }

    &__slide-wrapper {
        grid-area: content;
        width: 100%;
        height: 100%;
        &--selected {
            z-index: 1;
            pointer-events: initial;
        }
    }

    &__slide {
        width: 100%;
        height: 100%;
    }

    &__scroll-preview {
        position: absolute;
        top: 50%;
        width: 5em;
        height: 5em;
        border-radius: 50%;
        background-color: rgba(black, 0.07);
        background-color: #f2f2f2;
        backdrop-filter: blur(6px);
        z-index: 100;
        pointer-events: none;
        opacity: 0;
        transform: translate(0, -50%);

        @include transition(opacity transform, $dur: 500ms);
        @include depth-shadow(55, 0.05);

        svg {
            @include center();
            color: currentColor;
        }

        &--prev {
            left: 2.5em;
        }

        &--next {
            right: 2.5em;
        }

        &--visible {
            opacity: 1;
        }
    }

    // variant (replaces 1fr row/columns with 100%)

    &--anchored {
        grid-template-columns: 100%;
        grid-template-rows: 100%;
    }
}
</style>
