(function ($) {
    'use strict';

    // Wheel listeners are bound natively, so they are kept here by widget ID to
    // be removed when the editor re-renders the widget.
    var wheelHandlers = {};

    var OnePageNavHandler = function ($scope, $) {
        var onepage_nav_elem = $scope.find('.pp-one-page-nav').eq(0);

        if ( ! onepage_nav_elem.length ) {
            return;
        }

        var $section_id      = '#' + onepage_nav_elem.data('section-id'),
            $top_offset      = parseInt( onepage_nav_elem.data('top-offset'), 10 ) || 0,
            $scroll_speed    = onepage_nav_elem.data('scroll-speed'),
            $scroll_wheel    = onepage_nav_elem.data('scroll-wheel'),
            $scroll_touch    = onepage_nav_elem.data('scroll-touch'),
            $scroll_keys     = onepage_nav_elem.data('scroll-keys'),
            $target_dot      = $section_id + ' .pp-one-page-nav-item a',
            $active_item     = $section_id + ' .pp-one-page-nav-item.active',
            $items           = onepage_nav_elem.find('.pp-one-page-nav-item'),
            ns               = '.ppOnePageNav' + onepage_nav_elem.attr('id'),
            startY;

        /**
         * Element a dot points to, looked up by its data-row-id.
         *
         * Resolved by ID rather than by container class so that sections,
         * containers and plain markup are all supported.
         */
        function getTarget( $item ) {
            var rowId = $item.find('a').data('row-id');

            if ( ! rowId ) {
                return $();
            }

            return $( document.getElementById( rowId ) );
        }

        /**
         * Scroll to the section a dot points to.
         *
         * Returns false when there is nothing to scroll to, so that callers can
         * leave the browser's own scrolling alone.
         */
        function scrollToItem( $item ) {
            var $target = getTarget( $item );

            if ( ! $target.length || $( 'html, body' ).is(':animated') ) {
                return false;
            }

            $items.removeClass('active');
            $item.addClass('active');

            $( 'html, body' ).animate({
                scrollTop: $target.offset().top - $top_offset
            }, $scroll_speed);

            return true;
        }

        /**
         * Mark the dot of the section currently in view as active.
         *
         * Exactly one dot is always active, so that wheel, touch and keyboard
         * navigation always have a position to step from.
         */
        function updateDot() {
            // Don't fight the scroll animation started by a dot click.
            if ( $( 'html, body' ).is(':animated') ) {
                return;
            }

            var threshold = $(window).scrollTop() + $(window).height() / 2,
                $current  = $();

            $items.each(function () {
                var $item   = $(this),
                    $target = getTarget( $item );

                if ( ! $target.length ) {
                    return;
                }

                if ( $target.offset().top - $top_offset <= threshold ) {
                    $current = $item;
                } else if ( ! $current.length ) {
                    // Above the first section - keep its dot active.
                    $current = $item;
                }
            });

            $items.removeClass('active');
            $current.addClass('active');
        }

        $( $target_dot ).off( 'click' + ns ).on( 'click' + ns, function(e) {
            e.preventDefault();
            e.stopPropagation();

            scrollToItem( $(this).parent() );

            return false;
        } );

        updateDot();

        $(window).off( 'scroll' + ns + ' resize' + ns ).on( 'scroll' + ns + ' resize' + ns, function() {
            updateDot();
        });

        if ( $scroll_wheel === 'on' ) {
            var lastAnimation = 0,
                quietPeriod = 500,
                animationTime = 800;

            var widgetId = onepage_nav_elem.attr('id');

            // Bound natively and non-passively: browsers make wheel listeners on
            // the document passive by default, which would ignore preventDefault().
            if ( wheelHandlers[ widgetId ] ) {
                document.removeEventListener( 'wheel', wheelHandlers[ widgetId ], { passive: false } );
            }

            wheelHandlers[ widgetId ] = function(e) {
                var timeNow = new Date().getTime();

                if ( timeNow - lastAnimation < quietPeriod + animationTime ) {
                    e.preventDefault();
                    return;
                }

                if ( ! e.deltaY || $('html, body').is(':animated') ) {
                    return;
                }

                var $active = $( $active_item ),
                    scrolled = e.deltaY > 0 ? scrollToItem( $active.next() ) : scrollToItem( $active.prev() );

                // Past the first or the last section there is nothing to snap
                // to - let the browser scroll normally instead of locking up.
                if ( ! scrolled ) {
                    return;
                }

                e.preventDefault();
                lastAnimation = timeNow;
            };

            document.addEventListener( 'wheel', wheelHandlers[ widgetId ], { passive: false } );

            if ( $scroll_touch === 'on' ) {
                $(document).off( 'pointerdown' + ns + ' touchstart' + ns + ' touchmove' + ns + ' pointerup' + ns + ' touchend' + ns )
                    .on('pointerdown' + ns + ' touchstart' + ns, function(e) {
                    var touches = e.originalEvent.touches;
                    if (touches && touches.length) {
                        startY = touches[0].screenY;
                    }
                }).on('touchmove' + ns, function(e) {
                    if ($('html, body').is(':animated')) {
                        e.preventDefault();
                    }
                }).on('pointerup' + ns + ' touchend' + ns, function(e) {
                    var touches = e.originalEvent;
                    if (touches.pointerType === 'touch' || e.type === 'touchend') {
                        var Y = touches.screenY || touches.changedTouches[0].screenY;
                        var deltaY = startY - Y;

                        if (Math.abs(deltaY) < 2) {
                            return;
                        }

                        // swipe up.
                        if (deltaY < 0) {
                            scrollToItem( $( $active_item ).prev() );
                        }

                        // swipe down.
                        if (deltaY > 0) {
                            scrollToItem( $( $active_item ).next() );
                        }
                    }
                });
            }
        }

        if ( $scroll_keys === 'on' ) {
            $(document).off( 'keydown' + ns ).on( 'keydown' + ns, function(e) {
                var tag = e.target.tagName.toLowerCase();

                if ( tag === 'input' || tag === 'textarea' || tag === 'select' || e.target.isContentEditable ) {
                    return;
                }

                switch(e.which) {
                    case 38: // up arrow key.
                        scrollToItem( $( $active_item ).prev() );
                    break;
                    case 40: // down arrow key.
                        scrollToItem( $( $active_item ).next() );
                    break;
                    case 33: // pageup key.
                        scrollToItem( $( $active_item ).prev() );
                    break;
                    case 34: // pagedown key.
                        scrollToItem( $( $active_item ).next() );
                    break;
                    default: return;
                }
            });
        }
    };

    $(window).on('elementor/frontend/init', function () {
        elementorFrontend.hooks.addAction('frontend/element_ready/pp-one-page-nav.default', OnePageNavHandler);
    });

}(jQuery));
