(function ($) {
	$( window ).on( 'elementor/frontend/init', () => {
		class VideoGalleryWidget extends elementorModules.frontend.handlers.Base {
			getDefaultSettings() {
				return {
					selectors: {
						gallery: '.pp-video-gallery',
						swiperContainer: '.pp-swiper-slider',
						swiperSlide: '.swiper-slide',
						itemWrap: '.pp-grid-item-wrap',
						pagination: '.pp-video-gallery-pagination',
					},
					slidesPerView: {
						widescreen: 3,
						desktop: 3,
						laptop: 3,
						tablet_extra: 3,
						tablet: 2,
						mobile_extra: 2,
						mobile: 1
					},
					effect: 'slide'
				};
			}

			getDefaultElements() {
				const selectors = this.getSettings( 'selectors' );
				return {
					$gallery: this.$element.find( selectors.gallery ),
					$swiperContainer: this.$element.find( selectors.swiperContainer ),
					$swiperSlide: this.$element.find( selectors.swiperSlide ),
					$itemWrap: this.$element.find( selectors.itemWrap ),
					$pagination: this.$element.find( selectors.pagination ),
				};
			}

			getSliderSettings(prop) {
				const sliderSettings = ( undefined !== this.elements.$swiperContainer.data('slider-settings') ) ? this.elements.$swiperContainer.data('slider-settings') : '';

				if ( 'undefined' !== typeof prop && 'undefined' !== sliderSettings[prop] ) {
					return sliderSettings[prop];
				}

				return sliderSettings;
			}

			getSlidesCount() {
				return this.elements.$swiperSlide.length;
			}

			getEffect() {
				return ( this.getSliderSettings('effect') || this.getSettings('effect') );
			}

			getDeviceSlidesPerView(device) {
				const slidesPerViewKey = 'slides_per_view' + ('desktop' === device ? '' : '_' + device);
				return Math.min(this.getSlidesCount(), +this.getSliderSettings(slidesPerViewKey) || this.getSettings('slidesPerView')[device]);
			}

			getSlidesPerView(device) {
				if ('slide' === this.getEffect()) {
					return this.getDeviceSlidesPerView(device);
				}
				return 1;
			}

			getDeviceSlidesToScroll(device) {
				const slidesToScrollKey = 'slides_to_scroll' + ('desktop' === device ? '' : '_' + device);
				return Math.min(this.getSlidesCount(), +this.getElementSettings(slidesToScrollKey) || 1);
			}

			getSlidesToScroll(device) {
				if ('slide' === this.getEffect()) {
					return this.getDeviceSlidesToScroll(device);
				}
				return 1;
			}

			getSpaceBetween(device) {
				let propertyName = 'space_between';
				if (device && 'desktop' !== device) {
					propertyName += '_' + device;
				}
				return elementorFrontend.utils.controls.getResponsiveControlValue(this.getSliderSettings(), 'space_between', 'size', device) || 0;
			}

			getSwiperOptions() {
				const isEditMode         = this.isEdit,
					sliderSettings       = this.getSliderSettings(),
					effect               = this.getEffect(),
					isLoopEnabled        = 'yes' === sliderSettings.loop,
					isCenteredSlides     = 'yes' === sliderSettings.centered_slides,
					isGrabCursor         = 'yes' === sliderSettings.grab_cursor,
					isAutoplayEnabled    = sliderSettings.autoplay,
					isPauseOnInteraction = !!sliderSettings.pause_on_interaction;

				const swiperOptions = {
					grabCursor:                isGrabCursor,
					slidesPerView:              this.getSlidesPerView('desktop'),
					slidesPerGroup:             this.getSlidesToScroll('desktop'),
					spaceBetween:               this.getSpaceBetween(),
					loop:                       isLoopEnabled,
					centeredSlides:             isCenteredSlides,
					speed:                      sliderSettings.speed,
					autoHeight:                 sliderSettings.auto_height,
					effect:                     effect,
					watchSlidesVisibility:      true,
					watchSlidesProgress:        true,
					preventClicksPropagation:   false,
					slideToClickedSlide:        true,
					handleElementorBreakpoints: true
				};

				if ( 'fade' === effect ) {
					swiperOptions.fadeEffect = { crossFade: true };
				}

				if ( sliderSettings.show_arrows ) {
					let prevEle = (isEditMode) ? '.elementor-swiper-button-prev' : '.swiper-button-prev-' + this.getID();
					let nextEle = (isEditMode) ? '.elementor-swiper-button-next' : '.swiper-button-next-' + this.getID();

					swiperOptions.navigation = {
						prevEl: prevEle,
						nextEl: nextEle,
					};
				}

				if ( sliderSettings.pagination ) {
					let paginationEle = (isEditMode) ? '.swiper-pagination' : '.swiper-pagination-' + this.getID();

					swiperOptions.pagination = {
						el: paginationEle,
						type: sliderSettings.pagination,
						clickable: true
					};
				}

				if ( 'cube' !== effect ) {
					const breakpointsSettings = {},
					breakpoints = elementorFrontend.config.responsive.activeBreakpoints;

					Object.keys(breakpoints).forEach(breakpointName => {
						const breakpointValue = breakpoints[breakpointName].value;
						breakpointsSettings[breakpointValue] = {
							slidesPerView: this.getSlidesPerView(breakpointName),
							slidesPerGroup: this.getSlidesToScroll(breakpointName),
						};

						if ( this.getSpaceBetween(breakpointName) ) {
							breakpointsSettings[breakpointValue].spaceBetween = this.getSpaceBetween(breakpointName);
						}
					});

					swiperOptions.breakpoints = breakpointsSettings;
				}

				if ( !isEditMode && isAutoplayEnabled ) {
					swiperOptions.autoplay = {
						delay: sliderSettings.autoplay_speed,
						disableOnInteraction: isPauseOnInteraction
					};
				}

				return swiperOptions;
			}

			bindEvents() {
				const elementSettings = this.getElementSettings(),
					$action = this.elements.$gallery.data( 'action' );

				if ( $action === 'inline') {
					this.inlineVideoPlay();
				} else if ( $action === 'lightbox') {
					this.lightboxVideoPlay();
				}

				if ( 'grid' === elementSettings.layout ) {
					this.initFilters();
				}

				if ( 'carousel' === elementSettings.layout ) {
					this.initSlider();
				} else {
					this.initPagination();
				}
			}

			inlineVideoPlay() {
				const videoPlay = this.$element.find('.pp-video-play'),
					elementSettings = this.getElementSettings(),
					items = (elementSettings.layout === 'carousel') ? this.elements.$swiperSlide : this.elements.$itemWrap;

				videoPlay.off('click').on('click', function (e) {
					e.preventDefault();
			
					// Remove any existing iframe and show video thumbnails
					items.each(function () {
						const $this = $(this);
						const $videoPlayer = $this.find('.pp-video-player');
						const $videoThumb = $this.find('.pp-video-thumb-wrap');
						const $iframe = $videoPlayer.find('iframe');
			
						if ($iframe.length) {
							$iframe.remove();
							$videoThumb.show();
						}
					});
			
					// Get video data
					const $clickedElement = $(this),
						vidSrc = $clickedElement.data('src'),
						$videoPlayer = $clickedElement.find('.pp-video-player'),
						$videoThumb = $clickedElement.find('.pp-video-thumb-wrap');
			
					// Create iframe with attributes
					const $iframe = $('<iframe/>', {
						src: vidSrc,
						frameborder: '0',
						allowfullscreen: '1',
						allow: 'autoplay;encrypted-media;',
					});
			
					// Hide the thumbnail and append the iframe
					$videoThumb.hide();
					$videoPlayer.append($iframe);
				});
			}

			lightboxVideoPlay() {
				$.fancybox.defaults.media.dailymotion = {
					matcher: /dailymotion.com\/video\/(.*)\/?(.*)/,
					params: {
						additionalInfos : 0,
						autoStart : 1
					},
					type: 'iframe',
					url: '//www.dailymotion.com/embed/video/$1'
				};
			}

			initFilters() {
				const gallery = this.elements.$gallery,
					self      = this;

				if ( ! gallery.hasClass('pp-video-gallery-filter-enabled') ) {
					return;
				}

				this.$element.on( 'click', '.pp-gallery-filter', function () {
					const $this = $( this ),
						filterValue = $this.attr('data-filter');

					$this.siblings().removeClass('pp-active');
					$this.addClass('pp-active');

					self.applyFilter( filterValue, true );
				} );
			}

			/**
			 * Filter animation duration in ms, read from the `--pp-gallery-filter-duration` CSS
			 * var (default 400ms). Returns 0 when the user prefers reduced motion.
			 */
			getFilterDuration() {
				if ( window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches ) {
					return 0;
				}

				const raw = getComputedStyle( this.elements.$gallery[0] ).getPropertyValue('--pp-gallery-filter-duration').trim();

				if ( raw.endsWith('ms') ) {
					return parseFloat( raw ) || 0;
				}
				if ( raw.endsWith('s') ) {
					return ( parseFloat( raw ) || 0 ) * 1000;
				}
				return 400;
			}

			/**
			 * Show/hide items for a filter value with a FLIP animation (position morph + scale/fade
			 * for items entering and leaving). The CSS grid reflows on its own; we just animate the
			 * visual transition between the old and new positions.
			 *
			 * @param {string}  filterValue A selector (e.g. '.youtube') or '*' for all.
			 * @param {boolean} animate     Animate the transition. Off for the initial state.
			 */
			applyFilter( filterValue, animate ) {
				const gallery   = this.elements.$gallery,
					galleryEl   = gallery[0],
					allItems    = Array.prototype.slice.call( gallery.children('.pp-grid-item-wrap') ),
					matches     = ( el ) => ! filterValue || '*' === filterValue || el.matches( filterValue ),
					duration    = animate ? this.getFilterDuration() : 0;

				this.finalizeFilter();

				if ( ! duration ) {
					allItems.forEach( ( el ) => el.classList.toggle( 'pp-grid-item-hidden', ! matches( el ) ) );
					return;
				}

				const galleryRect = galleryEl.getBoundingClientRect(),
					nowVisible    = allItems.filter( ( el ) => ! el.classList.contains('pp-grid-item-hidden') ),
					first         = new Map();

				// FIRST: remember where the currently-visible items are.
				nowVisible.forEach( ( el ) => first.set( el, el.getBoundingClientRect() ) );

				const staying  = nowVisible.filter( matches ),
					exiting    = nowVisible.filter( ( el ) => ! matches( el ) ),
					entering   = allItems.filter( ( el ) => el.classList.contains('pp-grid-item-hidden') && matches( el ) );

				// Pin exiting items out of flow at their current spot so the grid reflows without
				// them, then fade + scale them out.
				exiting.forEach( ( el ) => {
					const r = first.get( el );
					el.classList.add('pp-grid-item-exiting');
					el.style.transition = 'none';
					el.style.position = 'absolute';
					el.style.margin = '0';
					el.style.zIndex = '0';
					el.style.width = r.width + 'px';
					el.style.top = ( r.top - galleryRect.top ) + 'px';
					el.style.left = ( r.left - galleryRect.left ) + 'px';
					el.style.right = 'auto';
				} );

				// Bring entering items into the grid (still invisible; animated in below).
				entering.forEach( ( el ) => el.classList.remove('pp-grid-item-hidden') );

				// PLAY: exit.
				exiting.forEach( ( el ) => {
					el.style.transition = `transform ${duration}ms ease, opacity ${duration}ms ease`;
					el.style.transformOrigin = 'center center';
					el.style.transform = 'scale(0.5)';
					el.style.opacity = '0';
					el.style.pointerEvents = 'none';
				} );

				// LAST + INVERT: place staying items back at their old spot with a transform.
				staying.forEach( ( el ) => {
					const f = first.get( el ),
						last = el.getBoundingClientRect(),
						dx = f.left - last.left,
						dy = f.top - last.top;

					el.style.transition = 'none';
					el.style.transform = `translate(${dx}px, ${dy}px)`;
				} );

				// Entering items start shrunk/invisible.
				entering.forEach( ( el ) => {
					el.style.transition = 'none';
					el.style.transformOrigin = 'center center';
					el.style.transform = 'scale(0.5)';
					el.style.opacity = '0';
				} );

				// Force a single reflow so the inverted/initial states are committed…
				galleryEl.offsetWidth; // eslint-disable-line no-unused-expressions

				// …then PLAY the staying + entering items to their natural state.
				staying.forEach( ( el ) => {
					el.style.transition = `transform ${duration}ms ease`;
					el.style.transform = '';
				} );
				entering.forEach( ( el ) => {
					el.style.transition = `transform ${duration}ms ease, opacity ${duration}ms ease`;
					el.style.transform = '';
					el.style.opacity = '';
				} );

				this._filterAnim = { exiting, staying, entering };
				this._filterAnimTimer = setTimeout( () => this.finalizeFilter(), duration + 60 );
			}

			/**
			 * Clear any in-progress filter animation: strip inline animation styles and settle
			 * exiting items into their final hidden state. Safe to call when nothing is running.
			 */
			finalizeFilter() {
				if ( this._filterAnimTimer ) {
					clearTimeout( this._filterAnimTimer );
					this._filterAnimTimer = null;
				}

				const anim = this._filterAnim;
				if ( ! anim ) {
					return;
				}
				this._filterAnim = null;

				anim.exiting.forEach( ( el ) => {
					el.classList.remove('pp-grid-item-exiting');
					el.classList.add('pp-grid-item-hidden');
					el.style.cssText = '';
				} );
				anim.staying.forEach( ( el ) => {
					el.style.transition = '';
					el.style.transform = '';
				} );
				anim.entering.forEach( ( el ) => {
					el.style.transition = '';
					el.style.transform = '';
					el.style.opacity = '';
					el.style.transformOrigin = '';
				} );
			}

			async initSlider() {
				const elementSettings = this.getElementSettings();
				const Swiper = elementorFrontend.utils.swiper;

    			this.swiper = await new Swiper(this.elements.$swiperContainer, this.getSwiperOptions());

				if ('yes' === elementSettings.pause_on_hover) {
					this.togglePauseOnHover(true);
				}
			}

			togglePauseOnHover(toggleOn) {
				if (toggleOn) {
					this.elements.$swiperContainer.on({
						mouseenter: () => {
							this.swiper.autoplay.stop();
						},
						mouseleave: () => {
							this.swiper.autoplay.start();
						}
					});
				} else {
					this.elements.$swiperContainer.off('mouseenter mouseleave');
				}
			}

			initPagination() {
				if ( ! this.elements.$pagination.length ) {
					return;
				}

				const self    = this,
					$items    = $( this.$element.find( 'script.pp-video-gallery-pagination-items' ).html() ).filter( '.pp-grid-item-wrap' ),
					perPage   = parseInt( this.elements.$pagination.data( 'per-page' ), 10 ) || $items.length,
					duration  = this.getFilterDuration();
				let offset    = 0;

				this.elements.$pagination.find('a').on( 'click', function ( e ) {
					e.preventDefault();

					const batch = $items.slice( offset, offset + perPage );

					// Append into the CSS grid (it reflows automatically) and fade the batch in.
					batch.appendTo( self.elements.$gallery );

					if ( duration ) {
						batch.each( function () {
							const el = this;
							el.style.transition = 'none';
							el.style.transformOrigin = 'center center';
							el.style.transform = 'scale(0.5)';
							el.style.opacity = '0';
							el.offsetWidth; // eslint-disable-line no-unused-expressions
							el.style.transition = `transform ${duration}ms ease, opacity ${duration}ms ease`;
							el.style.transform = '';
							el.style.opacity = '';
							setTimeout( function () {
								el.style.transition = '';
								el.style.transform = '';
								el.style.opacity = '';
								el.style.transformOrigin = '';
							}, duration + 60 );
						} );
					}

					// Re-bind inline video playback for the newly-added items.
					if ( 'inline' === self.elements.$gallery.data('action') ) {
						self.inlineVideoPlay();
					}

					offset += batch.length;

					if ( offset >= $items.length ) {
						self.elements.$pagination.remove();
					}
				} );
			}
		}

		elementorFrontend.elementsHandler.attachHandler( 'pp-video-gallery', VideoGalleryWidget );
	} );
})(jQuery);