( function( $ ) {
	$( window ).on( 'elementor/frontend/init', () => {

		class WooProductTabsWidget extends elementorModules.frontend.handlers.Base {

			getDefaultSettings() {
				return {
					selectors: {
						widgetContainer: '.pp-woo-product-tabs-wrapper',
						tabWrapper: '.woocommerce-tabs',
						tabListWrapper: '.wc-tabs-wrapper',
						tabList: '.wc-tabs',
					},
					classes: {
						tabListWrapper: 'wc-tabs-wrapper',
						tabList: 'wc-tabs',
						itemActive: 'active',
					},
				};
			}

			getDefaultElements() {
				const selectors = this.getSettings( 'selectors' );

				return {
					$widgetContainer: this.$element.find( selectors.widgetContainer ),
					$tabList: this.$element.find( selectors.tabList ),
					$tabWrapper: this.$element.find( selectors.tabWrapper ),
				};
			}

			bindEvents() {
				this.eventNamespace = '.ppWooProductTabs-' + this.getID();

				// Delegate from the list so this is one handler rather than one per tab,
				// and namespace it so onDestroy() can take it back off again. focusin and
				// focusout ride along with the pointer events so a keyboard user gets the
				// same indicator and hover styling a mouse user does.
				this.elements.$tabList
					.off( this.eventNamespace )
					.on( 'mouseenter' + this.eventNamespace + ' focusin' + this.eventNamespace, 'li', function() {
						$( this ).addClass( 'hover' );
					} )
					.on( 'mouseleave' + this.eventNamespace + ' focusout' + this.eventNamespace, 'li', function() {
						$( this ).removeClass( 'hover' );
					} );
			}

			unbindEvents() {
				if ( ! this.eventNamespace ) {
					return;
				}

				this.elements.$tabList.off( this.eventNamespace );
			}

			activeTabIndicator() {
				const mode = this.getElementSettings( 'woo_products_tab__active_tab_indicator_horizontal' ) ||
					this.getElementSettings( 'woo_products_tab__active_tab_indicator_vertical' );

				const position = this.getElementSettings( 'woo-products-tab__active-tab-indicator-position-horizontal' ) ||
					this.getElementSettings( 'woo_products_tab__active_tab_indicator_position_vertical' ) ||
					'';

				this.elements.$tabList.toggleClass( mode );
				this.elements.$tabList.toggleClass( position );
			}

			setActiveTab( index ) {
				/*
				 * The comma in the old selector - '.wc-tabs, ul.tabs li:nth-child(n) a' -
				 * made this a selector list, so it clicked every .wc-tabs on the page as
				 * well as the nth link of every tab component on the page, not the nth
				 * link of this one. Scope it to this instance and index the children
				 * directly. WooCommerce's delegated click handler does the rest,
				 * including aria-selected and the roving tabindex.
				 */
				this.elements.$tabList
					.children( 'li' )
					.eq( index - 1 )
					.children( 'a' )
					.trigger( 'click' );
			}

			tabMode() {
				// Check position of icon and add tab flex-class top, bottom, left, right.
				const iconPosition = this.getElementSettings( 'woo-product-tabs__icon-position' );

				this.elements.$tabList.toggleClass( iconPosition );
			}

			layoutMode() {
				const mode = this.getElementSettings( 'woo_product_tabs__tab_layout' );

				this.elements.$tabWrapper.toggleClass( mode );
			}

			/**
			 * Build the icon element for a repeater row, or null when it has none.
			 *
			 * Built with jQuery rather than an HTML string so the stored value is set
			 * as an attribute and cannot break out of the class list. An empty icon
			 * returns null so no element is emitted at all - the old string build
			 * produced `<i class="pp-icon undefined undefined">` for a row with no
			 * icon, and `[object Object]` for an uploaded SVG, whose value is
			 * `{ id, url }` rather than a class name.
			 */
			createIcon( setting ) {
				if ( ! setting || ! setting.value ) {
					return null;
				}

				if ( 'svg' === setting.library || 'object' === typeof setting.value ) {
					if ( ! setting.value.url ) {
						return null;
					}

					return $( '<img>', {
						'class': 'pp-icon',
						src: setting.value.url,
						alt: '',
						'aria-hidden': 'true',
					} );
				}

				return $( '<i>', {
					'class': 'pp-icon ' + setting.value + ' ' + ( setting.library || '' ),
					'aria-hidden': 'true',
				} );
			}

			addIcons() {
				const isIconOnly = 'icon' === this.getElementSettings( 'woo-product-tabs__tab-style' );

				// Key the repeater control's settings by row ID, so the per-tab loop
				// below is a lookup rather than a scan over every row.
				const iconsById = {};

				this.getElementSettings( 'product_tabs--repeater-section' ).forEach( ( item ) => {
					iconsById[ item._id ] = item.product_tab_icon;
				} );

				// Iterate over all the elements in the tab list to get the right icon.
				this.elements.$tabList.children( 'li' ).each( ( index, item ) => {
					const $link = $( item ).children( 'a' );

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

					/*
					 * Icon-only mode used to blank the label outright, via
					 * childNodes[1].childNodes[0].data = '', which left every role="tab"
					 * without an accessible name and broke outright on any theme that
					 * overrides tabs.php and wraps the title in an element. Wrapping the
					 * existing nodes keeps the name for assistive tech while hiding it
					 * visually, and does not care how the title is marked up.
					 */
					if ( isIconOnly ) {
						$link.wrapInner( '<span class="elementor-screen-only"></span>' );
					}

					const $icon = this.createIcon( iconsById[ item.id.replace( 'tab-title-', '' ) ] );

					if ( $icon ) {
						$link.prepend( $icon );
					}
				} );
			}

			initJS() {
				// Trigger WooCommerce's Single Product JS manually on widget load.
				this.elements.$tabWrapper.trigger( 'init' );
			}

			run() {
				// Initialize WooCommerce JS for tabs.
				this.initJS();

				// Add icons to tabs.
				if ( 'title' !== this.getElementSettings( 'woo-product-tabs__tab-style' ) ) {
					this.addIcons();
				}

				// Add layout class.
				this.layoutMode();

				// Add icon class.
				this.tabMode();

				// Init active tab indicator. This used to be guarded on
				// 'woo-products-tab__active-tab-indicator', which is not a control that
				// exists, so getElementSettings() returned undefined and the guard was
				// always true. activeTabIndicator() resolves the real per-layout keys
				// itself, so the guard was doing nothing either way.
				this.activeTabIndicator();

				// Activate default tab.
				if ( this.getElementSettings( 'woo-product-tabs__default-tab' ) > 0 ) {
					this.setActiveTab( this.getElementSettings( 'woo-product-tabs__default-tab' ) );
				}
			}

			onInit() {
				this.initElements();
				this.bindEvents();

				$( document ).ready( () => this.run() );
			}
		}

		elementorFrontend.elementsHandler.attachHandler( 'pp-woo-product-tabs', WooProductTabsWidget );
	} );

}( jQuery ) );
