/**
 * Canonical order for persisted close-trigger slugs (`close_popup_triggers`).
 */
const DSM_POPUP_CLOSE_TRIGGER_ORDER = Object.freeze(
	/** @type {const} */ ( [
		'overlay_click',
		'esc_key',
		'close_button',
		'css_class',
	] )
);

/**
 * Normalize close-trigger slugs from `close_popup_triggers` (array) or legacy `close_popup_trigger` (string).
 *
 * @param {Record<string, unknown>|null|undefined} raw Raw popup options from `data-dsm-popup-options`.
 * @return {string[]} Deduplicated slugs from {@link DSM_POPUP_CLOSE_TRIGGER_ORDER}; fallback `['overlay_click']`.
 */
function dsmNormalizeClosePopupTriggers( raw ) {
	const valid = new Set( DSM_POPUP_CLOSE_TRIGGER_ORDER );
	const picked = [];

	if ( raw && typeof raw === 'object' ) {
		const triggers = raw.close_popup_triggers;
		if ( Array.isArray( triggers ) ) {
			for ( let i = 0; i < triggers.length; i++ ) {
				const id =
					triggers[ i ] !== null && triggers[ i ] !== undefined
						? String( triggers[ i ] )
						: '';
				if ( id && valid.has( id ) && ! picked.includes( id ) ) {
					picked.push( id );
				}
			}
		}
		const legacy = raw.close_popup_trigger;
		if (
			typeof legacy === 'string' &&
			legacy.trim() !== '' &&
			valid.has( legacy ) &&
			! picked.includes( legacy )
		) {
			picked.push( legacy );
		}
	}

	if ( picked.length === 0 ) {
		return [ 'overlay_click' ];
	}

	return DSM_POPUP_CLOSE_TRIGGER_ORDER.filter( ( id ) =>
		picked.includes( id )
	);
}

/**
 * DSM Popup Class
 * Handles the initialization and management of popups in the Divi theme.
 */
class DSM_Popup {
	/**
	 * The container element.
	 *
	 * @type {HTMLElement}
	 */
	container;

	/**
	 * The root element.
	 *
	 * @type {HTMLElement}
	 */
	root;

	/**
	 * The config object containing popup settings.
	 *
	 * @type {Object}
	 */
	config;

	/**
	 * The jQuery object.
	 *
	 * @type {jQuery}
	 */
	$;

	/**
	 * Timer for popup reopening.
	 *
	 * @type {number|null}
	 */
	reopenTimer = null;

	/**
	 * Constructor.
	 *
	 * @param {HTMLElement} container The container element.
	 */
	constructor(container) {
		this.container = container;
		this.root = this.container.parentNode;
		this.config = this.getConfig();
		this.$ = jQuery;
		this.init();
	}

	/**
	 * Get a parameter from the config.
	 *
	 * @param {string} key The key to get.
	 * @param {any} defaultValue The default value to return if the key is not found.
	 * @return {any} The value of the key.
	 */

	getParam(key, defaultValue = null) {
		const value = this.config[key] || defaultValue;
		if (value === 'on') {
			return true;
		}
		if (value === 'off') {
			return false;
		}
		return this.config[key] || defaultValue;
	}

	/**
	 * Get the config from the container's data attribute.
	 *
	 * @return {Object} The parsed config object.
	 */
	getConfig() {
		try {
			return JSON.parse(this.container.dataset.dsmPopupOptions);
		} catch (error) {
			console.error('Failed to parse popup config:', error);
			return {};
		}
	}

	/**
	 * Check if popup should be shown based on last close time.
	 * This check only applies for page load and exit intent popups.
	 *
	 * @param {string} popupId The popup ID.
	 * @param {string} popupAppear The popup appear setting.
	 * @param {number} popupAppearAfterXHours Hours to wait before showing again.
	 * @param {boolean} isTimerTriggered Whether this check was triggered by a timer.
	 * @return {boolean} Whether the popup should be shown.
	 */
	shouldShowPopup(
		popupId,
		popupAppear,
		popupAppearAfterXHours,
		popupAppearAfterXDays,
		isTimerTriggered = false
	) {
		// If timer triggered it, bypass the time check
		if (isTimerTriggered) {
			return true;
		}

		// If popup appears always, show it
		if (popupAppear === 'always') {
			return true;
		}

		const storageKey = `dsm_popup_${popupId}_last_close`;
		const lastCloseTime = localStorage.getItem(storageKey);

		// If no last close time exists, show the popup
		if (!lastCloseTime) {
			return true;
		}

		if (popupAppear === 'after_x_hours') {
			const waitTime =
				parseInt(popupAppearAfterXHours) * 60 * 60 * 1000; // Convert hours to milliseconds
			const timeSinceLastClose = Date.now() - parseInt(lastCloseTime);
			return timeSinceLastClose >= waitTime;
		}

		if (popupAppear === 'after_x_days') {
			const waitTime =
				parseInt(popupAppearAfterXDays) * 24 * 60 * 60 * 1000; // Convert days to milliseconds
			const timeSinceLastClose = Date.now() - parseInt(lastCloseTime);
			return timeSinceLastClose >= waitTime;
		}

		return true;
	}

	/**
	 * Initialize the popup.
	 */
	init() {
		const popupType = this.getParam('popup_type');

		window.addEventListener('DOMContentLoaded', () => {
			switch (popupType) {
				case 'module':
					this.initModulePopup();
					break;
				case 'page_load':
					this.initPageLoadPopup();
					break;
				case 'exit_intent':
					this.initExitIntentPopup();
					break;
				case 'targetted':
					this.initTargetByClassPopup();
					this.initTargetByIdPopup();
					break;
			}
		});
	}


	/**
	 * Show the popup.
	 */
	show() {
		const { $ } = this;

		// [Divi 5 Change] Prevent hierarchy errors and recursive loops by checking if already open
		if ($.magnificPopup.instance && $.magnificPopup.instance.isOpen && $.magnificPopup.instance.currItem && $.magnificPopup.instance.currItem.src === this.container) {
			return;
		}

		const enterAnimation = this.getParam('enterance_animation');
		const exitAnimation = this.getParam('exit_animation');
		const closePopupTriggers =
			dsmNormalizeClosePopupTriggers( this.config );
		const popupClass = this.getParam('popup_class');
		const autoClosePopup = this.getParam('auto_close_popup');
		const autoClosePopupDelay = this.getParam('auto_close_popup_delay');
		const popupId = this.getParam('popup_id');

		$.magnificPopup.open({
			items: {
				src: this.container,
				type: 'inline',
			},
			mainClass: `dsm-popup-modal dsm-popup-${popupId}`,
			closeOnBgClick:
				closePopupTriggers.includes( 'overlay_click' ),
			enableEscapeKey: closePopupTriggers.includes( 'esc_key' ),
			closeBtnInside: false,
			showCloseBtn: false,
			tClose: '',
			removalDelay: 1000,
			callbacks: {
				open: () =>
					this.handlePopupOpen(
						enterAnimation,
						exitAnimation,
						closePopupTriggers,
						popupClass,
						autoClosePopup,
						autoClosePopupDelay
					),
				beforeClose: () =>
					this.handlePopupBeforeClose(
						enterAnimation,
						exitAnimation
					),
				afterClose: () =>
					this.handlePopupAfterClose(enterAnimation, exitAnimation),
			},
		});
	}

	/**
	 * Handle popup open event.
	 *
	 * @param {string} enterAnimation The entrance animation class.
	 * @param {string} exitAnimation The exit animation class.
	 * @param {string[]} closePopupTriggers Enabled close-trigger slugs.
	 * @param {string} popupClass The custom popup class.
	 * @param {string} autoClosePopup Whether to auto close.
	 * @param {number} autoClosePopupDelay Auto close delay in seconds.
	 */
	handlePopupOpen(
		enterAnimation,
		exitAnimation,
		closePopupTriggers,
		popupClass,
		autoClosePopup,
		autoClosePopupDelay
	) {
		const { $ } = this;
		const $content = $('.mfp-content');
		const popupId = this.getParam('popup_id');
		const popupAppear = this.getParam('popup_appear');

		// Clear any existing timer
		if (this.reopenTimer) {
			clearTimeout(this.reopenTimer);
			this.reopenTimer = null;
		}

		// Set timer to reopen popup after wait time
		if (popupAppear === 'after_x_hours') {
			const waitTime =
				this.getParam('popup_appear_after_x_hours') * 60 * 60 * 1000;
			this.reopenTimer = setTimeout(() => {
				this.show();
			}, waitTime);
		}

		// Add custom close button
		this.addCloseButton(closePopupTriggers, popupClass);

		// Auto close popup if enabled
		if (autoClosePopup) {
			setTimeout(
				() => {
					if ($.magnificPopup.instance.isOpen) {
						$.magnificPopup.close();
					}
				},
				Number(autoClosePopupDelay) * 1000
			);
		}

		// Get the popup element and clean up any existing animation classes
		const $popup = $('.mfp-content .dsm-popup');
		$popup.removeClass('animated');
		$popup.removeClass(enterAnimation);
		$popup.removeClass(exitAnimation);

		// Force a reflow to ensure the class removal is processed
		$popup[0].offsetHeight;

		// Add entrance animation classes
		$popup.addClass('animated');
		$popup.addClass(enterAnimation);
	}

	/**
	 * Add close button to popup.
	 *
	 * @param {string[]} closePopupTriggers Enabled close-trigger slugs.
	 * @param {string} popupClass The custom popup class.
	 */
	addCloseButton(closePopupTriggers, popupClass) {
		const { $ } = this;
		const hideCloseButton = this.getParam('hide_close_button');

		if (!hideCloseButton) {
			$('.mfp-content .dsm-popup').prepend(
				'<div class="mfp-close dsm-custom-close dsm-popup-close-icon"></div>'
			);

			// Add click handler for custom close button
			$('.dsm-custom-close').on('click', () => {
				if (closePopupTriggers.includes('close_button')) {
					$.magnificPopup.close();
				}
			});

			// Add click handler for custom class if trigger is css_class
			if (
				closePopupTriggers.includes('css_class') &&
				popupClass
			) {
				$('.mfp-content .dsm-popup')
					.find(`.${popupClass}`)
					.on('click', (e) => {
						e.preventDefault();
						$.magnificPopup.close();
					});
			}
		}
	}

	/**
	 * Handle popup before close event.
	 *
	 * @param {string} enterAnimation The entrance animation class.
	 * @param {string} exitAnimation The exit animation class.
	 */
	handlePopupBeforeClose(enterAnimation, exitAnimation) {
		const { $ } = this;
		const popupId = this.getParam('popup_id');

		// Save close time in localStorage
		const closeTime = Date.now().toString();
		const storageKey = `dsm_popup_${popupId}_last_close`;
		localStorage.setItem(storageKey, closeTime);

		// Only apply exit animation if popup is actually closing
		const $popup = $('.mfp-content .dsm-popup');
		if ($popup.length && $.magnificPopup.instance.isOpen) {
			// Remove all animation classes first
			$popup.removeClass('animated');
			$popup.removeClass(enterAnimation);
			$popup.removeClass(exitAnimation);

			// Force a reflow to ensure the class removal is processed
			$popup[0].offsetHeight;

			// Add exit animation classes
			$popup.addClass('animated');
			$popup.addClass(exitAnimation);
		}
	}

	/**
	 * Handle popup after close event.
	 *
	 * @param {string} enterAnimation The entrance animation class.
	 * @param {string} exitAnimation The exit animation class.
	 */
	handlePopupAfterClose(enterAnimation, exitAnimation) {
		const { $ } = this;
		const $popup = $('.mfp-content .dsm-popup');

		// Clean up ALL animation classes and styles
		$popup.removeClass('animated');
		$popup.removeClass(enterAnimation);
		$popup.removeClass(exitAnimation);
		$('.mfp-bg').removeAttr('style');
		$('.mfp-wrap').removeAttr('style');
	}

	/**
	 * Initialize the module popup.
	 */
	initModulePopup() {
		const { $ } = this;
		const openPopupTrigger = this.getParam('open_popup_trigger');
		const popupTarget = this.getParam('popup_target');
		const popupTargetByClass = this.getParam('popup_target_by_class');
		const popupTargetById = this.getParam('popup_target_by_id');
		const popupViewportDelay = this.getParam(
			'popup_viewport_delay',
			'0s'
		);

		// Setup triggers based on target type
		const setupTriggers = (targetElement) => {
			switch (openPopupTrigger) {
				case 'click':
					targetElement.on('click', (e) => {
						e.preventDefault();
						this.show();
					});
					break;

				case 'hover':
					targetElement.on('mouseenter', () => {
						this.show();
					});
					break;

				case 'enter_viewport':
					const enterObserver = new IntersectionObserver(
						(entries) => {
							entries.forEach((entry) => {
								if (entry.isIntersecting) {
									setTimeout(
										() => {
											this.show();
											enterObserver.disconnect();
										},
										parseInt(popupViewportDelay) * 1000
									);
								}
							});
						}
					);
					enterObserver.observe(targetElement[0]);
					break;

				case 'leave_viewport':
					let hasBeenInViewport = false;
					const leaveObserver = new IntersectionObserver(
						(entries) => {
							entries.forEach((entry) => {
								if (entry.isIntersecting) {
									hasBeenInViewport = true;
								} else if (hasBeenInViewport) {
									setTimeout(
										() => {
											this.show();
											leaveObserver.disconnect();
										},
										parseInt(popupViewportDelay) * 1000
									);
								}
							});
						}
					);
					leaveObserver.observe(targetElement[0]);
					break;
			}
		};

		// Always setup triggers on root element first
		setupTriggers($(this.root));

		// Then handle additional targets if specified
		if (popupTarget === 'target_by_class' && popupTargetByClass) {
			const targetElements = $(`.${popupTargetByClass}`);
			if (targetElements.length > 0) {
				targetElements.each((_, element) => {
					setupTriggers($(element));
				});
			}
		} else if (popupTarget === 'target_by_id' && popupTargetById) {
			const targetElement = $(`#${popupTargetById}`);
			if (targetElement.length > 0) {
				setupTriggers(targetElement);
			}
		}
	}

	/**
	 * Initialize the page load popup.
	 */
	initPageLoadPopup() {
		const popupAppear = this.getParam('popup_appear');
		const popupId = this.getParam('popup_id');
		const popupAppearAfterXHours = this.getParam(
			'popup_appear_after_x_hours'
		);
		const popupAppearAfterXDays = this.getParam(
			'popup_appear_after_x_days'
		);

		if (
			this.shouldShowPopup(
				popupId,
				popupAppear,
				popupAppearAfterXHours,
				popupAppearAfterXDays
			)
		) {
			setTimeout(() => this.show(), 100);
		}
	}

	/**
	 * Initialize the exit intent popup.
	 */
	initExitIntentPopup() {
		const popupAppear = this.getParam('popup_appear');
		const popupId = this.getParam('popup_id');
		const popupAppearAfterXHours = this.getParam(
			'popup_appear_after_x_hours'
		);
		let exitIntentTriggered = false;

		document.addEventListener('mouseout', (e) => {
			if (
				!exitIntentTriggered &&
				e.clientY <= 0 &&
				this.shouldShowPopup(
					popupId,
					popupAppear,
					popupAppearAfterXHours
				)
			) {
				exitIntentTriggered = true;
				this.show();
			}
		});
	}

	/**
	 * Initialize popup triggered by class selector.
	 */
	initTargetByClassPopup() {
		const { $ } = this;
		const popupTargetByClass = this.getParam('popup_target_by_class');
		const openPopupTrigger = this.getParam('open_popup_trigger');
		const popupViewportDelay = this.getParam(
			'popup_viewport_delay',
			'0s'
		);

		if (!popupTargetByClass) {
			return;
		}

		const targetElements = $(`.${popupTargetByClass}`);

		if (targetElements.length === 0) {
			return;
		}

		// Setup triggers based on target type
		const setupTriggers = (targetElement) => {
			switch (openPopupTrigger) {
				case 'click':
					targetElement.on('click', (e) => {
						e.preventDefault();
						this.show();
					});
					break;

				case 'hover':
					targetElement.on('mouseenter', () => {
						this.show();
					});
					break;

				case 'enter_viewport':
					const enterObserver = new IntersectionObserver(
						(entries) => {
							entries.forEach((entry) => {
								if (entry.isIntersecting) {
									setTimeout(
										() => {
											this.show();
											enterObserver.disconnect();
										},
										parseInt(popupViewportDelay) * 1000
									);
								}
							});
						}
					);
					enterObserver.observe(targetElement[0]);
					break;

				case 'leave_viewport':
					let hasBeenInViewport = false;
					const leaveObserver = new IntersectionObserver(
						(entries) => {
							entries.forEach((entry) => {
								if (entry.isIntersecting) {
									hasBeenInViewport = true;
								} else if (hasBeenInViewport) {
									setTimeout(
										() => {
											this.show();
											leaveObserver.disconnect();
										},
										parseInt(popupViewportDelay) * 1000
									);
								}
							});
						}
					);
					leaveObserver.observe(targetElement[0]);
					break;
			}
		};

		// Setup triggers for each matching element
		targetElements.each((_, element) => {
			setupTriggers($(element));
		});
	}

	/**
	 * Initialize popup triggered by ID selector.
	 */
	initTargetByIdPopup() {
		const { $ } = this;
		const popupTargetById = this.getParam('popup_target_by_id');
		const openPopupTrigger = this.getParam('open_popup_trigger');
		const popupViewportDelay = this.getParam(
			'popup_viewport_delay',
			'0s'
		);

		if (!popupTargetById) {
			return;
		}

		const targetElement = $(`#${popupTargetById}`);
		if (targetElement.length === 0) {
			return;
		}

		// Setup triggers based on target type
		switch (openPopupTrigger) {
			case 'click':
				targetElement.on('click', (e) => {
					e.preventDefault();
					this.show();
				});
				break;

			case 'hover':
				targetElement.on('mouseenter', () => {
					this.show();
				});
				break;

			case 'enter_viewport':
				const enterObserver = new IntersectionObserver((entries) => {
					entries.forEach((entry) => {
						if (entry.isIntersecting) {
							setTimeout(
								() => {
									this.show();
									enterObserver.disconnect();
								},
								parseInt(popupViewportDelay) * 1000
							);
						}
					});
				});
				enterObserver.observe(targetElement[0]);
				break;

			case 'leave_viewport':
				let hasBeenInViewport = false;
				const leaveObserver = new IntersectionObserver((entries) => {
					entries.forEach((entry) => {
						if (entry.isIntersecting) {
							hasBeenInViewport = true;
						} else if (hasBeenInViewport) {
							setTimeout(
								() => {
									this.show();
									leaveObserver.disconnect();
								},
								parseInt(popupViewportDelay) * 1000
							);
						}
					});
				});
				leaveObserver.observe(targetElement[0]);
				break;
		}
	}
}

// Initialize all popups on the page
const popupContainers = document.querySelectorAll('.dsm-popup');
popupContainers.forEach((container) => {
	new DSM_Popup(container);
});
