import {
    Swiper,
    Navigation,
    Lazy,
    Keyboard,
    A11y,
    Pagination,
} from 'swiper/swiper.esm';

import { extractPrefixedAttributesFromElement } from '@nimius/dom-utility';
import { fireEvent } from '@nimius/event-utility';

Swiper.use([ Navigation, Lazy, Keyboard, A11y, Pagination ]);

const defaultConfig = {
    slidesPerView: 3,
    slidesPerGroup: 1,
    loop: false,
    limitShake: false,
    lazyLoad: true,
    spaceBetween: 0,
    freeMode: false,
    autoHeight: false,
};

export default class ToubizSwiper {

    /**
     * @param {HTMLElement} node
     */
    constructor(node) {

        /** @private {HTMLElement} */
        this.node = node;

        const extractedConfig = extractPrefixedAttributesFromElement(node, 'data-toubiz-swiper-');
        const config = this.legacyConfigurationTransformation({
            ...defaultConfig,
            ...extractedConfig,
        });

        if (!config.id) {
            console.error(
                '## Toubiz Swiper Error: ID is required. Please configure an ID for your swiper instance via markup.'
            );
            return;
        }

        /** @private {Swiper} */
        this.swiper = new Swiper(node, this.getFullConfig(config));
    }

    /**
     * @param {object} config
     * @returns {object}
     * @private
     */
    getFullConfig(config) {
        const node = this.node;

        // Slides to scroll at the same time (should not be > 1 if slidesPerView <= 1)
        const slidesPerGroup = (config.slidesPerGroup > config.slidesPerView)
            ? config.slidesPerView
            : config.slidesPerGroup;

        const slidesPerView = config.slidesPerView;
        let breakpoints = {};
        if (config.breakpoints) {
            breakpoints = config.breakpoints;
        } else if (slidesPerView > 1) {
            breakpoints = {
                // When window is <= 480px
                0: {
                    slidesPerView: 1,
                    slidesPerGroup: 1,
                },

                // When windows is <= 992px
                480: {
                    slidesPerView: 2,
                    slidesPerGroup: 2,
                },

                // Big screen
                992: {
                    slidesPerView,
                    slidesPerGroup,
                },
            };
        }

        return {
            freeMode: config.freeMode,

            autoHeight: config.autoHeight,

            // Disable loop (otherwise lightgallery doesnt work correctly because of duplicate slides)
            loop: config.loop,

            // Accessibility settings
            a11y: { notificationClass: 'tb-swiper__notification' },

            // Set custom classnames to align with CGL
            containerModifierClass: 'tb-swiper-',
            wrapperClass: 'tb-swiper__wrapper',
            slideClass: 'tb-swiper__slide',
            slideNextClass: 'tb-swiper__slide--next',
            slidePrevClass: 'tb-swiper__slide--prev',
            slideVisibleClass: 'tb-swiper__slide--visible',
            slideActiveClass: 'tb-swiper__slide--active',

            pagination: config.enablePagination ? {
                el: `.js-swiper-pagination-${config.id}`,
                type: 'bullets',
                clickable: true,
            } : {},

            // Disable preloading of all images
            preloadImages: false,

            // Enable lazy loading
            lazy: config.lazyLoad ? {
                loadPrevNext: true,
                loadPrevNextAmount: config.slidesPerView,
                loadOnTransitionStart: false,
                elementClass: 'tb-swiper__lazy-image',
                loadingClass: 'tb-swiper__lazy-image--loading',
                loadedClass: 'tb-swiper__lazy-image--loaded',
                preloaderClass: 'tb-swiper__lazy-preloader',
            } : false,

            // Threshold value in px. If "touch distance" will be lower than this value then swiper will not move
            threshold: 80,

            // Navigation arrows
            navigation: {
                nextEl: `.js-swiper-next-${config.id}`,
                prevEl: `.js-swiper-prev-${config.id}`,
                disabledClass: 'tb-swiper__button--disabled',
                hiddenClass: 'tb-swiper__button--hidden',
            },

            // Slides visible at the same time
            slidesPerView,

            // Slides to scroll at the same time (should not be > 1 if slidesPerView <= 1)
            slidesPerGroup,

            // Gutters between items
            spaceBetween: config.spaceBetween,

            // Responsive breakpoints
            breakpoints,

            on: {
                init() {
                    if (!config.loop && config.limitShake) {
                        const swiperPrevButton = document.querySelector(`.js-swiper-prev-${config.id}`);
                        const swiperNextButton = document.querySelector(`.js-swiper-next-${config.id}`);

                        /*
                         * Do the limit shake (indicates to the user that the end/beginning
                         * of the swiper was reached and he can't go further)
                         */
                        if (swiperPrevButton && swiperNextButton) {
                            swiperPrevButton.addEventListener('click', () => {
                                if (this.isBeginning) {
                                    node.classList.add('js-animation--limit-shake');
                                    setTimeout(() => node.classList.remove('js-animation--limit-shake'), 300);
                                }
                            });
                            swiperNextButton.addEventListener('click', () => {
                                if (this.isEnd) {
                                    node.classList.add('js-animation--limit-shake');
                                    setTimeout(() => node.classList.remove('js-animation--limit-shake'), 300);
                                }
                            });
                        }
                    }

                    /*
                     * Trigger the window resize event as a workaround for issues with initial
                     * layout calculation. Might be resolved in future swiper releases.
                     *
                     * https://github.com/nolimits4web/swiper/issues/2218
                     */
                    let resizeCount = 0;
                    const resizeInterval = () => {
                        if (resizeCount > 2) {
                            clearInterval(resizeInterval);
                            return;
                        }
                        fireEvent(window, 'resize');
                        resizeCount++;
                    };
                    setInterval(resizeInterval, 500);
                },
            },
        };
    }


    /**
     * @param {object} config
     * @returns {object}
     * @private
     */
    legacyConfigurationTransformation(config) {
        for (const transformation of this.getLegacyTransformationRules()) {
            if (transformation.check(config)) {
                transformation.transform(config);
                console.warn(`
                    ## Toubiz Swiper Deprecation Notice
                    ${transformation.warn(config)}
                    This warning will turn into an error with the next version.
                `);
            }
        }

        return config;
    }


    /**
     * Returns transformations that each contain a check if the transformation should be applied,
     * a transformation step and a warn step that returns a warning message.
     *
     * @returns {{ check: Function, transform: Function, warn: Function }[]}
     * @private
     */
    getLegacyTransformationRules() {
        // Helper for int cast syntax
        const intCast = attribute => ({
            check: config => typeof config[attribute] === 'string' && !isNaN(parseInt(config[attribute], 10)),
            transform: config => config[attribute] = parseInt(config[attribute], 10),
            warn: () => `${attribute}: Please use cast syntax (e.g. "(int) 3" instead of "3"`,
        });

        // Helper for bool cast syntax
        const boolCast = attribute => ({
            check: config => typeof config[attribute] === 'string',
            transform: config => config[attribute] = config[attribute] === 'true',
            warn: () => `${attribute}: Please use cast syntax (e.g. "(bool) true" instead of "true"`,
        });

        return [
            intCast('slidesPerView'),
            intCast('slidesPerGroup'),
            intCast('spaceBetween'),
            boolCast('loop'),
            boolCast('enablePagination'),
            boolCast('lazyLoad'),
            boolCast('limitShake'),
            boolCast('autoHeight'),
            {
                check: config => config.disableLazyLoad !== undefined,
                transform: config => {
                    if (typeof config.disableLazyLoad === 'string') {
                        config.disableLazyLoad = config.disableLazyLoad === 'true';
                    }

                    config.lazyLoad = !config.disableLazyLoad;
                },
                warn: () => 'Please use lazyLoad instead of disableLazyLoad',
            },
        ];
    }

}
